From f69c7496c49f1f117ed732280e9bf2e281a9ba0e Mon Sep 17 00:00:00 2001 From: Aleh Zasypkin Date: Mon, 12 Feb 2024 17:24:31 +0200 Subject: [PATCH 01/83] chore: bump `socks` transitive dependency from `2.7.1` to `2.7.3` (#176693) ## Summary Bump `socks` transitive dependency from `2.7.1` to `2.7.3` to reduce a number of packages that depend on outdated `ip` package. --- yarn.lock | 26 ++++++++++++++++++++++---- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/yarn.lock b/yarn.lock index 7222c93888236..fbd3e8ca5979a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -19274,6 +19274,14 @@ io-ts@^2.0.5: resolved "https://registry.yarnpkg.com/io-ts/-/io-ts-2.0.5.tgz#e6e3db9df8b047f9cbd6b69e7d2ad3e6437a0b13" integrity sha512-pL7uUptryanI5Glv+GUv7xh+aLBjxGEDmLwmEYNSx0yOD3djK0Nw5Bt0N6BAkv9LadOUU7QKpRsLcqnTh3UlLA== +ip-address@^9.0.5: + version "9.0.5" + resolved "https://registry.yarnpkg.com/ip-address/-/ip-address-9.0.5.tgz#117a960819b08780c3bd1f14ef3c1cc1d3f3ea5a" + integrity sha512-zHtQzGojZXTwZTHQqra+ETKd4Sn3vgi7uBmlPoXVWZqYvuKmtI0l/VZTjqGmJY9x88GGOaZ9+G9ES8hC4T4X8g== + dependencies: + jsbn "1.1.0" + sprintf-js "^1.1.3" + ip-regex@^4.1.0: version "4.3.0" resolved "https://registry.yarnpkg.com/ip-regex/-/ip-regex-4.3.0.tgz#687275ab0f57fa76978ff8f4dddc8a23d5990db5" @@ -20763,6 +20771,11 @@ js-yaml@^3.13.1, js-yaml@^3.14.1: argparse "^1.0.7" esprima "^4.0.0" +jsbn@1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-1.1.0.tgz#b01307cb29b618a1ed26ec79e911f803c4da0040" + integrity sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A== + jsbn@~0.1.0: version "0.1.1" resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513" @@ -28153,11 +28166,11 @@ socks-proxy-agent@^8.0.1, socks-proxy-agent@^8.0.2: socks "^2.7.1" socks@^2.7.1: - version "2.7.1" - resolved "https://registry.yarnpkg.com/socks/-/socks-2.7.1.tgz#d8e651247178fde79c0663043e07240196857d55" - integrity sha512-7maUZy1N7uo6+WVEX6psASxtNlKaNVMlGQKkG/63nEDdLOWNbiUMoLK7X4uYoLhQstau72mLgfEWcXcwsaHbYQ== + version "2.7.3" + resolved "https://registry.yarnpkg.com/socks/-/socks-2.7.3.tgz#7d8a75d7ce845c0a96f710917174dba0d543a785" + integrity sha512-vfuYK48HXCTFD03G/1/zkIls3Ebr2YNa4qU9gHDZdblHLiqhJrJGkY3+0Nx0JpN9qBhJbVObc1CNciT1bIZJxw== dependencies: - ip "^2.0.0" + ip-address "^9.0.5" smart-buffer "^4.2.0" sonic-boom@^1.0.2: @@ -28443,6 +28456,11 @@ split2@^4.0.0: resolved "https://registry.yarnpkg.com/split2/-/split2-4.1.0.tgz#101907a24370f85bb782f08adaabe4e281ecf809" integrity sha512-VBiJxFkxiXRlUIeyMQi8s4hgvKCSjtknJv/LVYbrgALPwf5zSKmEwV9Lst25AkvMDnvxODugjdl6KZgwKM1WYQ== +sprintf-js@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.1.3.tgz#4914b903a2f8b685d17fdf78a70e917e872e444a" + integrity sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA== + sprintf-js@~1.0.2: version "1.0.3" resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" From 459dff178d8d50e99b45497bc09aa1f1bf72f907 Mon Sep 17 00:00:00 2001 From: Samiul Monir <150824886+Samiul-TheSoccerFan@users.noreply.github.com> Date: Mon, 12 Feb 2024 10:24:58 -0500 Subject: [PATCH 02/83] Make Indices discoverable in Kibana Global Search (#175473) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary Added a search provider to return only visible indices and land in the overview page once clicked or selected. ### No indices available Screenshot 2024-01-24 at 2 48 58 PM ### Matched index shows up Screenshot 2024-01-24 at 2 42 13 PM ### Shows all matched index Screenshot 2024-01-24 at 2 50 09 PM ### 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) - [ ] [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 - [ ] [Flaky Test Runner](https://ci-stats.kibana.dev/trigger_flaky_test_runner/1) was used on any tests changed - [x] Any UI touched in this PR is usable by keyboard only (learn more about [keyboard accessibility](https://webaim.org/techniques/keyboard/)) - [x] 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) - [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) ### 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) --------- Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../public/assets/source_icons/index.svg | 3 + .../enterprise_search/server/plugin.ts | 2 + .../indices_search_result_provider.test.ts | 212 ++++++++++++++++++ .../utils/indices_search_result_provider.ts | 68 ++++++ x-pack/plugins/global_search/common/types.ts | 6 + .../global_search/public/services/types.ts | 6 + .../global_search/server/routes/find.ts | 3 +- .../routes/integration_tests/find.test.ts | 1 + x-pack/plugins/global_search/server/types.ts | 6 + .../public/lib/result_to_option.test.ts | 9 + .../public/lib/result_to_option.tsx | 3 +- 11 files changed, 317 insertions(+), 2 deletions(-) create mode 100644 x-pack/plugins/enterprise_search/public/assets/source_icons/index.svg create mode 100644 x-pack/plugins/enterprise_search/server/utils/indices_search_result_provider.test.ts create mode 100644 x-pack/plugins/enterprise_search/server/utils/indices_search_result_provider.ts diff --git a/x-pack/plugins/enterprise_search/public/assets/source_icons/index.svg b/x-pack/plugins/enterprise_search/public/assets/source_icons/index.svg new file mode 100644 index 0000000000000..6af8b7e92afe9 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/assets/source_icons/index.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/x-pack/plugins/enterprise_search/server/plugin.ts b/x-pack/plugins/enterprise_search/server/plugin.ts index 7a764e9ef6fb5..8d493c0a4d59b 100644 --- a/x-pack/plugins/enterprise_search/server/plugin.ts +++ b/x-pack/plugins/enterprise_search/server/plugin.ts @@ -80,6 +80,7 @@ import { workplaceSearchTelemetryType } from './saved_objects/workplace_search/t import { GlobalConfigService } from './services/global_config_service'; import { uiSettings as enterpriseSearchUISettings } from './ui_settings'; +import { getIndicesSearchResultProvider } from './utils/indices_search_result_provider'; import { getSearchResultProvider } from './utils/search_result_provider'; import { ConfigType } from '.'; @@ -343,6 +344,7 @@ export class EnterpriseSearchPlugin implements Plugin { if (globalSearch) { globalSearch.registerResultProvider(getSearchResultProvider(http.basePath, config)); + globalSearch.registerResultProvider(getIndicesSearchResultProvider(http.basePath)); } } diff --git a/x-pack/plugins/enterprise_search/server/utils/indices_search_result_provider.test.ts b/x-pack/plugins/enterprise_search/server/utils/indices_search_result_provider.test.ts new file mode 100644 index 0000000000000..facae46be72a4 --- /dev/null +++ b/x-pack/plugins/enterprise_search/server/utils/indices_search_result_provider.test.ts @@ -0,0 +1,212 @@ +/* + * 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 { NEVER, lastValueFrom } from 'rxjs'; + +import { IScopedClusterClient } from '@kbn/core/server'; + +import { ENTERPRISE_SEARCH_CONTENT_PLUGIN } from '../../common/constants'; + +import { getIndicesSearchResultProvider } from './indices_search_result_provider'; + +describe('Enterprise Search - indices search provider', () => { + const basePathMock = { + prepend: (input: string) => `/kbn${input}`, + } as any; + + const indicesSearchResultProvider = getIndicesSearchResultProvider(basePathMock); + + const regularIndexResponse = { + 'search-github-api': { + aliases: {}, + }, + 'search-msft-sql-index': { + aliases: {}, + }, + }; + + const mockClient = { + asCurrentUser: { + count: jest.fn().mockReturnValue({ count: 100 }), + indices: { + get: jest.fn(), + stats: jest.fn(), + }, + security: { + hasPrivileges: jest.fn(), + }, + }, + asInternalUser: {}, + }; + const client = mockClient as unknown as IScopedClusterClient; + mockClient.asCurrentUser.indices.get.mockResolvedValue(regularIndexResponse); + + const githubIndex = { + id: 'search-github-api', + score: 75, + title: 'search-github-api', + type: 'Index', + url: { + path: `${ENTERPRISE_SEARCH_CONTENT_PLUGIN.URL}/search_indices/search-github-api`, + prependBasePath: true, + }, + icon: '/kbn/plugins/enterpriseSearch/assets/source_icons/index.svg', + }; + + const msftIndex = { + id: 'search-msft-sql-index', + score: 75, + title: 'search-msft-sql-index', + type: 'Index', + url: { + path: `${ENTERPRISE_SEARCH_CONTENT_PLUGIN.URL}/search_indices/search-msft-sql-index`, + prependBasePath: true, + }, + icon: '/kbn/plugins/enterpriseSearch/assets/source_icons/index.svg', + }; + + beforeEach(() => {}); + + afterEach(() => { + jest.clearAllMocks(); + }); + + describe('find', () => { + it('returns formatted results', async () => { + const results = await lastValueFrom( + indicesSearchResultProvider.find( + { term: 'search-github-api' }, + { + aborted$: NEVER, + client, + maxResults: 100, + preference: '', + }, + {} as any + ) + ); + expect(results).toEqual([{ ...githubIndex, score: 100 }]); + }); + + it('returns all matched results', async () => { + const results = await lastValueFrom( + indicesSearchResultProvider.find( + { term: 'search' }, + { + aborted$: NEVER, + client, + maxResults: 100, + preference: '', + }, + {} as any + ) + ); + expect(results).toEqual([ + { ...githubIndex, score: 90 }, + { ...msftIndex, score: 90 }, + ]); + }); + + it('returns all indices on empty string', async () => { + const results = await lastValueFrom( + indicesSearchResultProvider.find( + { term: '' }, + { + aborted$: NEVER, + client, + maxResults: 100, + preference: '', + }, + {} as any + ) + ); + expect(results).toHaveLength(0); + }); + + it('respect maximum results', async () => { + const results = await lastValueFrom( + indicesSearchResultProvider.find( + { term: 'search' }, + { + aborted$: NEVER, + client, + maxResults: 1, + preference: '', + }, + {} as any + ) + ); + expect(results).toEqual([{ ...githubIndex, score: 90 }]); + }); + + describe('returns empty results', () => { + it('when term does not match with created indices', async () => { + const results = await lastValueFrom( + indicesSearchResultProvider.find( + { term: 'sample' }, + { + aborted$: NEVER, + client, + maxResults: 100, + preference: '', + }, + {} as any + ) + ); + expect(results).toEqual([]); + }); + + it('if client is undefined', async () => { + const results = await lastValueFrom( + indicesSearchResultProvider.find( + { term: 'sample' }, + { + aborted$: NEVER, + client: undefined, + maxResults: 100, + preference: '', + }, + {} as any + ) + ); + expect(results).toEqual([]); + }); + + it('if tag is specified', async () => { + const results = await lastValueFrom( + indicesSearchResultProvider.find( + { term: 'search', tags: ['tag'] }, + { + aborted$: NEVER, + client, + maxResults: 100, + preference: '', + }, + {} as any + ) + ); + expect(results).toEqual([]); + }); + + it('if unknown type is specified', async () => { + const results = await lastValueFrom( + indicesSearchResultProvider.find( + { term: 'search', types: ['tag'] }, + { + aborted$: NEVER, + client, + maxResults: 100, + preference: '', + }, + {} as any + ) + ); + expect(results).toEqual([]); + }); + }); + }); +}); diff --git a/x-pack/plugins/enterprise_search/server/utils/indices_search_result_provider.ts b/x-pack/plugins/enterprise_search/server/utils/indices_search_result_provider.ts new file mode 100644 index 0000000000000..c826be87fcdfc --- /dev/null +++ b/x-pack/plugins/enterprise_search/server/utils/indices_search_result_provider.ts @@ -0,0 +1,68 @@ +/* + * 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 { from, takeUntil } from 'rxjs'; + +import { IBasePath } from '@kbn/core-http-server'; +import { + GlobalSearchProviderResult, + GlobalSearchResultProvider, +} from '@kbn/global-search-plugin/server'; +import { i18n } from '@kbn/i18n'; + +import { ENTERPRISE_SEARCH_CONTENT_PLUGIN } from '../../common/constants'; + +import { getIndexData } from '../lib/indices/utils/get_index_data'; + +export function getIndicesSearchResultProvider(basePath: IBasePath): GlobalSearchResultProvider { + return { + find: ({ term, types, tags }, { aborted$, client, maxResults }) => { + if (!client || !term || tags || (types && !types.includes('indices'))) { + return from([[]]); + } + const fetchIndices = async (): Promise => { + const { indexNames } = await getIndexData(client, false, false, term); + + const searchResults: GlobalSearchProviderResult[] = indexNames + .map((indexName) => { + let score = 0; + const searchTerm = (term || '').toLowerCase(); + const searchName = indexName.toLowerCase(); + if (!searchTerm) { + score = 80; + } else if (searchName === searchTerm) { + score = 100; + } else if (searchName.startsWith(searchTerm)) { + score = 90; + } else if (searchName.includes(searchTerm)) { + score = 75; + } + + return { + id: indexName, + title: indexName, + icon: basePath.prepend('/plugins/enterpriseSearch/assets/source_icons/index.svg'), + type: i18n.translate('xpack.enterpriseSearch.searchIndexProvider.type.name', { + defaultMessage: 'Index', + }), + url: { + path: `${ENTERPRISE_SEARCH_CONTENT_PLUGIN.URL}/search_indices/${indexName}`, + prependBasePath: true, + }, + score, + }; + }) + .filter(({ score }) => score > 0) + .slice(0, maxResults); + return searchResults; + }; + return from(fetchIndices()).pipe(takeUntil(aborted$)); + }, + getSearchableTypes: () => ['indices'], + id: 'enterpriseSearchIndices', + }; +} diff --git a/x-pack/plugins/global_search/common/types.ts b/x-pack/plugins/global_search/common/types.ts index fabae7ea01e8f..676cb421c4a15 100644 --- a/x-pack/plugins/global_search/common/types.ts +++ b/x-pack/plugins/global_search/common/types.ts @@ -7,6 +7,7 @@ import { Observable } from 'rxjs'; import { Serializable } from '@kbn/utility-types'; +import { IScopedClusterClient } from '@kbn/core/server'; /** * Options provided to {@link GlobalSearchResultProvider | a result provider}'s `find` method. @@ -26,6 +27,11 @@ export interface GlobalSearchProviderFindOptions { * this can (and should) be used to cancel any pending asynchronous task and complete the result observable from within the provider. */ aborted$: Observable; + /** + * A ES client of type IScopedClusterClient is passed to the `find` call. + * When performing calls to ES, the interested provider can utilize this parameter to identify the specific cluster. + */ + client?: IScopedClusterClient; /** * The total maximum number of results (including all batches, not per emission) that should be returned by the provider for a given `find` request. * Any result emitted exceeding this quota will be ignored by the service and not emitted to the consumer. diff --git a/x-pack/plugins/global_search/public/services/types.ts b/x-pack/plugins/global_search/public/services/types.ts index 169b1538d13d9..7cc622b82fba8 100644 --- a/x-pack/plugins/global_search/public/services/types.ts +++ b/x-pack/plugins/global_search/public/services/types.ts @@ -6,6 +6,7 @@ */ import { Observable } from 'rxjs'; +import { IScopedClusterClient } from '@kbn/core/server'; /** * Options for the server-side {@link GlobalSearchPluginStart.find | find API} @@ -25,4 +26,9 @@ export interface GlobalSearchFindOptions { * If/when provided and emitting, the result observable will be completed and no further result emission will be performed. */ aborted$?: Observable; + /** + * A ES client of type IScopedClusterClient is passed to the `find` call. + * When performing calls to ES, the interested provider can utilize this parameter to identify the specific cluster. + */ + client?: IScopedClusterClient; } diff --git a/x-pack/plugins/global_search/server/routes/find.ts b/x-pack/plugins/global_search/server/routes/find.ts index ec491454f7a88..64aa76ee64a70 100644 --- a/x-pack/plugins/global_search/server/routes/find.ts +++ b/x-pack/plugins/global_search/server/routes/find.ts @@ -33,8 +33,9 @@ export const registerInternalFindRoute = (router: GlobalSearchRouter) => { const { params, options } = req.body; try { const globalSearch = await ctx.globalSearch; + const { client } = (await ctx.core).elasticsearch; const allResults = await globalSearch - .find(params, { ...options, aborted$: req.events.aborted$ }) + .find(params, { ...options, aborted$: req.events.aborted$, client }) .pipe( map((batch) => batch.results), reduce((acc, results) => [...acc, ...results]) diff --git a/x-pack/plugins/global_search/server/routes/integration_tests/find.test.ts b/x-pack/plugins/global_search/server/routes/integration_tests/find.test.ts index fc53c8d01ebfa..0551e4338c1d3 100644 --- a/x-pack/plugins/global_search/server/routes/integration_tests/find.test.ts +++ b/x-pack/plugins/global_search/server/routes/integration_tests/find.test.ts @@ -76,6 +76,7 @@ describe('POST /internal/global_search/find', () => { { preference: 'custom-pref', aborted$: expect.any(Object), + client: expect.any(Object), } ); }); diff --git a/x-pack/plugins/global_search/server/types.ts b/x-pack/plugins/global_search/server/types.ts index 10a7bafe850dd..21de10af6a72f 100644 --- a/x-pack/plugins/global_search/server/types.ts +++ b/x-pack/plugins/global_search/server/types.ts @@ -13,6 +13,7 @@ import type { Capabilities, IRouter, CustomRequestHandlerContext, + IScopedClusterClient, } from '@kbn/core/server'; import { GlobalSearchBatchedResults, @@ -92,6 +93,11 @@ export interface GlobalSearchFindOptions { * If/when provided and emitting, no further result emission will be performed and the result observable will be completed. */ aborted$?: Observable; + /** + * A ES client of type IScopedClusterClient is passed to the `find` call. + * When performing calls to ES, the interested provider can utilize this parameter to identify the specific cluster. + */ + client?: IScopedClusterClient; } /** diff --git a/x-pack/plugins/global_search_bar/public/lib/result_to_option.test.ts b/x-pack/plugins/global_search_bar/public/lib/result_to_option.test.ts index d60453ca37d85..92589202cd16a 100644 --- a/x-pack/plugins/global_search_bar/public/lib/result_to_option.test.ts +++ b/x-pack/plugins/global_search_bar/public/lib/result_to_option.test.ts @@ -52,6 +52,15 @@ describe('resultToOption', () => { ); }); + it('uses icon for `index` type', () => { + const input = createSearchResult({ type: 'index', icon: 'index-icon' }); + expect(resultToOption(input, [])).toEqual( + expect.objectContaining({ + icon: { type: 'index-icon' }, + }) + ); + }); + it('does not use icon for other types', () => { const input = createSearchResult({ type: 'dashboard', icon: 'dash-icon' }); expect(resultToOption(input, [])).toEqual( diff --git a/x-pack/plugins/global_search_bar/public/lib/result_to_option.tsx b/x-pack/plugins/global_search_bar/public/lib/result_to_option.tsx index 88fa86edbd395..555434a85404f 100644 --- a/x-pack/plugins/global_search_bar/public/lib/result_to_option.tsx +++ b/x-pack/plugins/global_search_bar/public/lib/result_to_option.tsx @@ -25,7 +25,8 @@ export const resultToOption = ( type === 'application' || type === 'integration' || type.toLowerCase() === 'enterprise search' || - type.toLowerCase() === 'search'; + type.toLowerCase() === 'search' || + type.toLowerCase() === 'index'; const option: EuiSelectableTemplateSitewideOption = { key: id, label: title, From 4566ef71eca07bf0b6e386e277d6364bfe8cd4fa Mon Sep 17 00:00:00 2001 From: Walter Rafelsberger Date: Mon, 12 Feb 2024 16:33:17 +0100 Subject: [PATCH 03/83] [ML] AIOps: Use `ml_standard` tokenizer for log rate analysis. (#176587) ## Summary Fixes #176387. The `standard` analyser for log pattern analysis introduced in #172188 might return patterns that mess with the identifying of significant patterns across time ranges, for example if a pattern matches different parts of a date or time. This adds an update that allows to set the analyser for log rate analysis to `ml_standard` but keep `standard` for log pattern analysis. ### 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 - [x] [Flaky Test Runner](https://ci-stats.kibana.dev/trigger_flaky_test_runner/1) was used on any tests changed - [x] 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) --- .../create_category_request.ts | 5 +- .../queries/fetch_categories.test.ts | 55 ------------------- .../queries/fetch_categories.ts | 5 +- .../apps/aiops/log_rate_analysis.ts | 3 +- 4 files changed, 8 insertions(+), 60 deletions(-) diff --git a/x-pack/plugins/aiops/common/api/log_categorization/create_category_request.ts b/x-pack/plugins/aiops/common/api/log_categorization/create_category_request.ts index 1c5a4745064b5..a5a3efae6f450 100644 --- a/x-pack/plugins/aiops/common/api/log_categorization/create_category_request.ts +++ b/x-pack/plugins/aiops/common/api/log_categorization/create_category_request.ts @@ -31,7 +31,8 @@ export function createCategoryRequest( queryIn: QueryDslQueryContainer, wrap: ReturnType['wrap'], intervalMs?: number, - additionalFilter?: CategorizationAdditionalFilter + additionalFilter?: CategorizationAdditionalFilter, + useStandardTokenizer: boolean = true ) { const query = createCategorizeQuery(queryIn, timeField, timeRange); const aggs = { @@ -39,7 +40,7 @@ export function createCategoryRequest( categorize_text: { field, size: CATEGORY_LIMIT, - categorization_analyzer: categorizationAnalyzer, + ...(useStandardTokenizer ? { categorization_analyzer: categorizationAnalyzer } : {}), }, aggs: { examples: { diff --git a/x-pack/plugins/aiops/server/routes/log_rate_analysis/queries/fetch_categories.test.ts b/x-pack/plugins/aiops/server/routes/log_rate_analysis/queries/fetch_categories.test.ts index e6e79108435e2..280ffd9ed907f 100644 --- a/x-pack/plugins/aiops/server/routes/log_rate_analysis/queries/fetch_categories.test.ts +++ b/x-pack/plugins/aiops/server/routes/log_rate_analysis/queries/fetch_categories.test.ts @@ -85,61 +85,6 @@ describe('getCategoryRequest', () => { aggs: { categories: { categorize_text: { - categorization_analyzer: { - char_filter: ['first_line_with_letters'], - tokenizer: 'standard', - filter: [ - { - type: 'stop', - stopwords: [ - 'Monday', - 'Tuesday', - 'Wednesday', - 'Thursday', - 'Friday', - 'Saturday', - 'Sunday', - 'Mon', - 'Tue', - 'Wed', - 'Thu', - 'Fri', - 'Sat', - 'Sun', - 'January', - 'February', - 'March', - 'April', - 'May', - 'June', - 'July', - 'August', - 'September', - 'October', - 'November', - 'December', - 'Jan', - 'Feb', - 'Mar', - 'Apr', - 'May', - 'Jun', - 'Jul', - 'Aug', - 'Sep', - 'Oct', - 'Nov', - 'Dec', - 'GMT', - 'UTC', - ], - }, - { - type: 'limit', - max_token_count: '100', - }, - ], - }, field: 'the-field-name', size: 1000, }, diff --git a/x-pack/plugins/aiops/server/routes/log_rate_analysis/queries/fetch_categories.ts b/x-pack/plugins/aiops/server/routes/log_rate_analysis/queries/fetch_categories.ts index bbb64bc95cd30..20fd551873b1c 100644 --- a/x-pack/plugins/aiops/server/routes/log_rate_analysis/queries/fetch_categories.ts +++ b/x-pack/plugins/aiops/server/routes/log_rate_analysis/queries/fetch_categories.ts @@ -76,7 +76,10 @@ export const getCategoryRequest = ( timeFieldName, undefined, query, - wrap + wrap, + undefined, + undefined, + false ); // In this case we're only interested in the aggregation which diff --git a/x-pack/test/functional/apps/aiops/log_rate_analysis.ts b/x-pack/test/functional/apps/aiops/log_rate_analysis.ts index 9799d4418d729..45fef76fa7170 100644 --- a/x-pack/test/functional/apps/aiops/log_rate_analysis.ts +++ b/x-pack/test/functional/apps/aiops/log_rate_analysis.ts @@ -36,8 +36,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { await ml.jobSourceSelection.selectSourceForLogRateAnalysis(testData.sourceIndexOrSavedSearch); }); - // FLAKY: https://github.com/elastic/kibana/issues/176387 - it.skip(`${testData.suiteTitle} displays index details`, async () => { + it(`${testData.suiteTitle} displays index details`, async () => { await ml.testExecution.logTestStep(`${testData.suiteTitle} displays the time range step`); await aiops.logRateAnalysisPage.assertTimeRangeSelectorSectionExists(); From d89097a59d8d9b29f8ef85538174a3cad1ae4788 Mon Sep 17 00:00:00 2001 From: Alexi Doak <109488926+doakalexi@users.noreply.github.com> Date: Mon, 12 Feb 2024 08:22:29 -0800 Subject: [PATCH 04/83] [ResponseOps] Add telemetry for the es query rule types (#176451) Resolves https://github.com/elastic/kibana/issues/176237 ## Summary Adds new telemetry fields to track the ES Query rule search types. ### Checklist - [ ] [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 ### To verify - Create a couple of ES Query rules with the different search types - Create a rule that is not an ES Query rule - Change alerting telemetry task [schedule](https://github.com/doakalexi/kibana/blob/main/x-pack/plugins/alerting/server/usage/task.ts#L28) interval 1 min - Run [Telemetry usage payload API](https://docs.elastic.dev/telemetry/collection/snapshot-telemetry#telemetry-usage-payload-api) in your browser console to verify the new telemetry data under `count_by_type` and `count_active_by_type` --- .../lib/get_telemetry_from_kibana.test.ts | 42 ++++++++++++ .../usage/lib/get_telemetry_from_kibana.ts | 31 ++++++++- .../lib/group_rules_by_search_type.test.ts | 33 ++++++++++ .../usage/lib/group_rules_by_search_type.ts | 18 +++++ .../alerting_and_actions_telemetry.ts | 66 ++++++++++++++++--- 5 files changed, 178 insertions(+), 12 deletions(-) create mode 100644 x-pack/plugins/alerting/server/usage/lib/group_rules_by_search_type.test.ts create mode 100644 x-pack/plugins/alerting/server/usage/lib/group_rules_by_search_type.ts diff --git a/x-pack/plugins/alerting/server/usage/lib/get_telemetry_from_kibana.test.ts b/x-pack/plugins/alerting/server/usage/lib/get_telemetry_from_kibana.test.ts index 58a449d5f7004..f29602458fd50 100644 --- a/x-pack/plugins/alerting/server/usage/lib/get_telemetry_from_kibana.test.ts +++ b/x-pack/plugins/alerting/server/usage/lib/get_telemetry_from_kibana.test.ts @@ -139,6 +139,24 @@ describe('kibana index telemetry', () => { }, ], }, + by_search_type: { + doc_count_error_upper_bound: 0, + sum_other_doc_count: 0, + buckets: [ + { + key: 'esQuery', + doc_count: 0, + }, + { + key: 'searchSource', + doc_count: 1, + }, + { + key: 'esqlQuery', + doc_count: 3, + }, + ], + }, max_throttle_time: { value: 60 }, min_throttle_time: { value: 0 }, avg_throttle_time: { value: 30 }, @@ -174,6 +192,9 @@ describe('kibana index telemetry', () => { document__test__: 1, // eslint-disable-next-line @typescript-eslint/naming-convention logs__alert__document__count: 1, + '__es-query_es_query': 0, + '__es-query_esql_query': 3, + '__es-query_search_source': 1, }, count_total: 4, hasErrors: false, @@ -328,6 +349,24 @@ describe('kibana index telemetry', () => { }, ], }, + by_search_type: { + doc_count_error_upper_bound: 0, + sum_other_doc_count: 0, + buckets: [ + { + key: 'esQuery', + doc_count: 0, + }, + { + key: 'searchSource', + doc_count: 1, + }, + { + key: 'esqlQuery', + doc_count: 3, + }, + ], + }, }, }); @@ -345,6 +384,9 @@ describe('kibana index telemetry', () => { document__test__: 1, // eslint-disable-next-line @typescript-eslint/naming-convention logs__alert__document__count: 1, + '__es-query_es_query': 0, + '__es-query_esql_query': 3, + '__es-query_search_source': 1, }, countNamespaces: 1, countTotal: 4, diff --git a/x-pack/plugins/alerting/server/usage/lib/get_telemetry_from_kibana.ts b/x-pack/plugins/alerting/server/usage/lib/get_telemetry_from_kibana.ts index a1055aa075521..ecaf99ffc44a3 100644 --- a/x-pack/plugins/alerting/server/usage/lib/get_telemetry_from_kibana.ts +++ b/x-pack/plugins/alerting/server/usage/lib/get_telemetry_from_kibana.ts @@ -22,6 +22,7 @@ import { groupRulesByStatus } from './group_rules_by_status'; import { AlertingUsage } from '../types'; import { NUM_ALERTING_RULE_TYPES } from '../alerting_usage_collector'; import { parseSimpleRuleTypeBucket } from './parse_simple_rule_type_bucket'; +import { groupRulesBySearchType } from './group_rules_by_search_type'; interface Opts { esClient: ElasticsearchClient; @@ -258,6 +259,11 @@ export async function getTotalCountAggregations({ }, }, }, + by_search_type: { + terms: { + field: 'alert.params.searchType', + }, + }, sum_rules_with_tags: { sum: { field: 'rule_with_tags' } }, sum_rules_snoozed: { sum: { field: 'rule_snoozed' } }, sum_rules_muted: { sum: { field: 'rule_muted' } }, @@ -285,6 +291,7 @@ export async function getTotalCountAggregations({ by_execution_status: AggregationsTermsAggregateBase; by_notify_when: AggregationsTermsAggregateBase; connector_types_by_consumers: AggregationsTermsAggregateBase; + by_search_type: AggregationsTermsAggregateBase; sum_rules_with_tags: AggregationsSingleMetricAggregateBase; sum_rules_snoozed: AggregationsSingleMetricAggregateBase; sum_rules_muted: AggregationsSingleMetricAggregateBase; @@ -306,10 +313,17 @@ export async function getTotalCountAggregations({ aggregations.connector_types_by_consumers.buckets ); + const countRulesBySearchType = groupRulesBySearchType( + parseSimpleRuleTypeBucket(aggregations.by_search_type.buckets) + ); + return { hasErrors: false, count_total: totalRulesCount ?? 0, - count_by_type: parseSimpleRuleTypeBucket(aggregations.by_rule_type_id.buckets), + count_by_type: { + ...parseSimpleRuleTypeBucket(aggregations.by_rule_type_id.buckets), + ...countRulesBySearchType, + }, count_rules_by_execution_status: countRulesByExecutionStatus, count_rules_with_tags: aggregations.sum_rules_with_tags.value ?? 0, count_rules_by_notify_when: countRulesByNotifyWhen, @@ -422,6 +436,11 @@ export async function getTotalCountInUse({ size: NUM_ALERTING_RULE_TYPES, }, }, + by_search_type: { + terms: { + field: 'alert.params.searchType', + }, + }, }, }, }; @@ -434,15 +453,23 @@ export async function getTotalCountInUse({ const aggregations = results.aggregations as { by_rule_type_id: AggregationsTermsAggregateBase; namespaces_count: AggregationsCardinalityAggregate; + by_search_type: AggregationsTermsAggregateBase; }; const totalEnabledRulesCount = typeof results.hits.total === 'number' ? results.hits.total : results.hits.total?.value; + const countRulesBySearchType = groupRulesBySearchType( + parseSimpleRuleTypeBucket(aggregations.by_search_type.buckets) + ); + return { hasErrors: false, countTotal: totalEnabledRulesCount ?? 0, - countByType: parseSimpleRuleTypeBucket(aggregations.by_rule_type_id.buckets), + countByType: { + ...parseSimpleRuleTypeBucket(aggregations.by_rule_type_id.buckets), + ...countRulesBySearchType, + }, countNamespaces: aggregations.namespaces_count.value ?? 0, }; } catch (err) { diff --git a/x-pack/plugins/alerting/server/usage/lib/group_rules_by_search_type.test.ts b/x-pack/plugins/alerting/server/usage/lib/group_rules_by_search_type.test.ts new file mode 100644 index 0000000000000..b82c8b49d1ba0 --- /dev/null +++ b/x-pack/plugins/alerting/server/usage/lib/group_rules_by_search_type.test.ts @@ -0,0 +1,33 @@ +/* + * 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 { groupRulesBySearchType } from './group_rules_by_search_type'; + +describe('groupRulesBySearchType', () => { + test('should correctly group search types', () => { + expect( + groupRulesBySearchType({ + esQuery: 1, + searchSource: 2, + esqlQuery: 3, + foo: 5, + }) + ).toEqual({ + '__es-query_es_query': 1, + '__es-query_search_source': 2, + '__es-query_esql_query': 3, + }); + }); + + test('should fallback to 0 if any of the expected search types are absent', () => { + expect(groupRulesBySearchType({ unknown: 100, bar: 300 })).toEqual({ + '__es-query_es_query': 0, + '__es-query_search_source': 0, + '__es-query_esql_query': 0, + }); + }); +}); diff --git a/x-pack/plugins/alerting/server/usage/lib/group_rules_by_search_type.ts b/x-pack/plugins/alerting/server/usage/lib/group_rules_by_search_type.ts new file mode 100644 index 0000000000000..b97ac049c2374 --- /dev/null +++ b/x-pack/plugins/alerting/server/usage/lib/group_rules_by_search_type.ts @@ -0,0 +1,18 @@ +/* + * 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 { AlertingUsage } from '../types'; + +export function groupRulesBySearchType( + rulesBySearchType: Record +): AlertingUsage['count_by_type'] { + return { + '__es-query_es_query': rulesBySearchType.esQuery ?? 0, + '__es-query_search_source': rulesBySearchType.searchSource ?? 0, + '__es-query_esql_query': rulesBySearchType.esqlQuery ?? 0, + }; +} diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/group2/tests/telemetry/alerting_and_actions_telemetry.ts b/x-pack/test/alerting_api_integration/security_and_spaces/group2/tests/telemetry/alerting_and_actions_telemetry.ts index 0823665f43f64..afe7275e808b1 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/group2/tests/telemetry/alerting_and_actions_telemetry.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/group2/tests/telemetry/alerting_and_actions_telemetry.ts @@ -28,7 +28,7 @@ export default function createAlertingAndActionsTelemetryTests({ getService }: F describe('test telemetry', () => { const objectRemover = new ObjectRemover(supertest); - const alwaysFiringRuleId: { [key: string]: string } = {}; + const esQueryRuleId: { [key: string]: string } = {}; beforeEach(async () => { await esTestIndexTool.destroy(); @@ -90,7 +90,7 @@ export default function createAlertingAndActionsTelemetryTests({ getService }: F connectorTypeId: 'test.excluded', }); - alwaysFiringRuleId[space.id] = await createRule({ + await createRule({ space: space.id, ruleOverwrites: { rule_type_id: 'test.patternFiring', @@ -158,6 +158,28 @@ export default function createAlertingAndActionsTelemetryTests({ getService }: F actions: [], }, }); + // ES query rule + esQueryRuleId[space.id] = await createRule({ + space: space.id, + ruleOverwrites: { + rule_type_id: '.es-query', + schedule: { interval: '1h' }, + throttle: null, + params: { + size: 100, + timeWindowSize: 5, + timeWindowUnit: 'm', + thresholdComparator: '>', + threshold: [0], + searchType: 'esqlQuery', + esqlQuery: { + esql: 'from .kibana-alerting-test-data | stats c = count(date) | where c < 0', + }, + timeField: 'date_epoch_millis', + }, + actions: [], + }, + }); } } @@ -220,7 +242,7 @@ export default function createAlertingAndActionsTelemetryTests({ getService }: F function verifyAlertingTelemetry(telemetry: any) { logger.info(`alerting telemetry - ${JSON.stringify(telemetry)}`); // total number of enabled rules - expect(telemetry.count_active_total).to.equal(9); + expect(telemetry.count_active_total).to.equal(12); // total number of disabled rules expect(telemetry.count_disabled_total).to.equal(3); @@ -230,18 +252,26 @@ export default function createAlertingAndActionsTelemetryTests({ getService }: F expect(telemetry.count_by_type.test__patternFiring).to.equal(3); expect(telemetry.count_by_type.test__multipleSearches).to.equal(3); expect(telemetry.count_by_type.test__throw).to.equal(3); + expect(telemetry.count_by_type['__es-query']).to.equal(3); + expect(telemetry.count_by_type['__es-query_es_query']).to.equal(0); + expect(telemetry.count_by_type['__es-query_search_source']).to.equal(0); + expect(telemetry.count_by_type['__es-query_esql_query']).to.equal(3); // total number of enabled rules broken down by rule type expect(telemetry.count_active_by_type.test__patternFiring).to.equal(3); expect(telemetry.count_active_by_type.test__multipleSearches).to.equal(3); expect(telemetry.count_active_by_type.test__throw).to.equal(3); + expect(telemetry.count_active_by_type['__es-query']).to.equal(3); + expect(telemetry.count_active_by_type['__es-query_es_query']).to.equal(0); + expect(telemetry.count_active_by_type['__es-query_search_source']).to.equal(0); + expect(telemetry.count_active_by_type['__es-query_esql_query']).to.equal(3); // throttle time stats expect(telemetry.throttle_time.min).to.equal('0s'); - expect(telemetry.throttle_time.avg).to.equal('0.4s'); + expect(telemetry.throttle_time.avg).to.equal('0.3333333333333333s'); expect(telemetry.throttle_time.max).to.equal('1s'); expect(telemetry.throttle_time_number_s.min).to.equal(0); - expect(telemetry.throttle_time_number_s.avg).to.equal(0.4); + expect(telemetry.throttle_time_number_s.avg).to.equal(0.3333333333333333); expect(telemetry.throttle_time_number_s.max).to.equal(1); // schedule interval stats @@ -254,7 +284,7 @@ export default function createAlertingAndActionsTelemetryTests({ getService }: F // attached connectors stats expect(telemetry.connectors_per_alert.min).to.equal(0); - expect(telemetry.connectors_per_alert.avg).to.equal(1); + expect(telemetry.connectors_per_alert.avg).to.equal(0.8); expect(telemetry.connectors_per_alert.max).to.equal(3); // number of spaces with rules @@ -269,6 +299,7 @@ export default function createAlertingAndActionsTelemetryTests({ getService }: F expect(telemetry.count_by_type.test__noop >= 3).to.be(true); expect(telemetry.count_by_type.test__multipleSearches >= 3).to.be(true); expect(telemetry.count_by_type.test__throw >= 3).to.be(true); + expect(telemetry.count_by_type['__es-query'] >= 3).to.be(true); // average execution time - just checking for non-zero as we can't set an exact number expect(telemetry.avg_execution_time_per_day > 0).to.be(true); @@ -277,6 +308,7 @@ export default function createAlertingAndActionsTelemetryTests({ getService }: F expect(telemetry.avg_execution_time_by_type_per_day.test__patternFiring > 0).to.be(true); expect(telemetry.avg_execution_time_by_type_per_day.test__multipleSearches > 0).to.be(true); expect(telemetry.avg_execution_time_by_type_per_day.test__throw > 0).to.be(true); + expect(telemetry.avg_execution_time_by_type_per_day['__es-query'] > 0).to.be(true); // average es search time - just checking for non-zero as we can't set an exact number expect(telemetry.avg_es_search_duration_per_day > 0).to.be(true); @@ -360,6 +392,16 @@ export default function createAlertingAndActionsTelemetryTests({ getService }: F telemetry.percentile_num_generated_actions_by_type_per_day.p99.test__multipleSearches ).to.equal(0); + expect(telemetry.percentile_num_generated_actions_by_type_per_day.p50['__es-query']).to.equal( + 0 + ); + expect(telemetry.percentile_num_generated_actions_by_type_per_day.p90['__es-query']).to.equal( + 0 + ); + expect(telemetry.percentile_num_generated_actions_by_type_per_day.p99['__es-query']).to.equal( + 0 + ); + // percentile calculations for number of alerts expect(telemetry.percentile_num_alerts_per_day.p50 >= 0).to.be(true); expect(telemetry.percentile_num_alerts_per_day.p90 >= 0).to.be(true); @@ -392,17 +434,21 @@ export default function createAlertingAndActionsTelemetryTests({ getService }: F 0 ); + expect(telemetry.percentile_num_alerts_by_type_per_day.p50['__es-query']).to.equal(0); + expect(telemetry.percentile_num_alerts_by_type_per_day.p90['__es-query']).to.equal(0); + expect(telemetry.percentile_num_alerts_by_type_per_day.p99['__es-query']).to.equal(0); + // rules grouped by execution status expect(telemetry.count_rules_by_execution_status.success > 0).to.be(true); expect(telemetry.count_rules_by_execution_status.error > 0).to.be(true); expect(telemetry.count_rules_by_execution_status.warning).to.equal(0); // number of rules that has tags - expect(telemetry.count_rules_with_tags).to.equal(12); + expect(telemetry.count_rules_with_tags).to.equal(15); // rules grouped by notify when expect(telemetry.count_rules_by_notify_when.on_action_group_change).to.equal(0); expect(telemetry.count_rules_by_notify_when.on_active_alert).to.equal(0); - expect(telemetry.count_rules_by_notify_when.on_throttle_interval).to.equal(12); + expect(telemetry.count_rules_by_notify_when.on_throttle_interval).to.equal(15); // rules snoozed expect(telemetry.count_rules_snoozed).to.equal(0); // rules muted @@ -427,7 +473,7 @@ export default function createAlertingAndActionsTelemetryTests({ getService }: F getService, spaceId: Spaces[2].id, type: 'alert', - id: alwaysFiringRuleId[Spaces[2].id], + id: esQueryRuleId[Spaces[2].id], provider: 'alerting', actions: new Map([['execute', { gte: 1 }]]), }); @@ -474,7 +520,7 @@ export default function createAlertingAndActionsTelemetryTests({ getService }: F expect(taskState).not.to.be(undefined); alertingTelemetry = JSON.parse(taskState!); expect(alertingTelemetry.runs > 0).to.be(true); - expect(alertingTelemetry.count_total).to.equal(12); + expect(alertingTelemetry.count_total).to.equal(15); }); verifyAlertingTelemetry(alertingTelemetry); From e542f0adff4e1b8fb95829b401642c149e7e7938 Mon Sep 17 00:00:00 2001 From: Adam Demjen Date: Mon, 12 Feb 2024 11:48:19 -0500 Subject: [PATCH 05/83] [Search] Show source fields on inference pipeline card (#176623) ## Summary Display source fields on inference pipeline cards under the Pipelines tab. For this we needed to add the `sourceFields` optional property to the `InferencePipeline` type, as well as some parsing code. Screenshot 2024-02-09 at 13 32 14 Rendering on small screen: Screenshot 2024-02-09 at 13 33 26 For reference, here's what the existing pipeline selection list looks like: Screenshot 2024-02-09 at 13 31 41 ### 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 - [x] Any UI touched in this PR is usable by keyboard only (learn more about [keyboard accessibility](https://webaim.org/techniques/keyboard/)) - [x] 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)) - [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)) --------- Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../common/types/pipelines.ts | 1 + .../inference_pipeline_card.test.tsx | 21 +++- .../pipelines/inference_pipeline_card.tsx | 5 + .../ml_inference/pipeline_select_option.tsx | 4 +- ...t_ml_inference_pipeline_processors.test.ts | 100 +++++++++++++++++- .../get_ml_inference_pipeline_processors.ts | 21 +++- 6 files changed, 141 insertions(+), 11 deletions(-) diff --git a/x-pack/plugins/enterprise_search/common/types/pipelines.ts b/x-pack/plugins/enterprise_search/common/types/pipelines.ts index 6ea8e6c46e3f8..068097611030f 100644 --- a/x-pack/plugins/enterprise_search/common/types/pipelines.ts +++ b/x-pack/plugins/enterprise_search/common/types/pipelines.ts @@ -16,6 +16,7 @@ export interface InferencePipeline { pipelineName: string; pipelineReferences: string[]; types: string[]; + sourceFields?: string[]; } export enum TrainedModelState { diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/inference_pipeline_card.test.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/inference_pipeline_card.test.tsx index ff234eacb77e4..3c3c118c86cd2 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/inference_pipeline_card.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/inference_pipeline_card.test.tsx @@ -25,6 +25,7 @@ export const DEFAULT_VALUES: InferencePipeline = { pipelineName: 'Sample Processor', pipelineReferences: [], types: ['pytorch', 'ner'], + sourceFields: ['title', 'body'], }; const mockValues = { ...DEFAULT_VALUES }; @@ -52,13 +53,13 @@ describe('InferencePipelineCard', () => { const wrapper = shallow(); expect(wrapper.find(EuiTitle)).toHaveLength(1); // does not render subtitle - expect(wrapper.find(EuiText)).toHaveLength(0); + expect(wrapper.find(EuiText)).toHaveLength(1); const title = wrapper.find(EuiTitle).at(0).children(); expect(title.text()).toBe(DEFAULT_VALUES.pipelineName); }); it('renders model ID as subtitle', () => { const wrapper = shallow(); - expect(wrapper.find(EuiText)).toHaveLength(1); + expect(wrapper.find(EuiText)).toHaveLength(2); const subtitle = wrapper.find(EuiText).at(0).children(); expect(subtitle.text()).toBe(DEFAULT_VALUES.modelId); }); @@ -68,7 +69,7 @@ describe('InferencePipelineCard', () => { modelId: '', }; const wrapper = shallow(); - expect(wrapper.find(EuiText)).toHaveLength(1); + expect(wrapper.find(EuiText)).toHaveLength(2); const subtitle = wrapper.find(EuiText).at(0).children(); expect(subtitle.text()).toBe(MODEL_REDACTED_VALUE); }); @@ -86,6 +87,20 @@ describe('InferencePipelineCard', () => { const wrapper = shallow(); expect(wrapper.find(MLModelTypeBadge)).toHaveLength(0); }); + it('renders source fields', () => { + const wrapper = shallow(); + expect(wrapper.find(EuiText)).toHaveLength(2); + const sourceFields = wrapper.find(EuiText).at(1).children(); + expect(sourceFields.text()).toBe('title, body'); + }); + it('does not render source fields if there are none', () => { + const values = { + ...DEFAULT_VALUES, + sourceFields: undefined, + }; + const wrapper = shallow(); + expect(wrapper.find(EuiText)).toHaveLength(1); // Model ID only + }); }); describe('TrainedModelHealthPopover', () => { diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/inference_pipeline_card.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/inference_pipeline_card.tsx index 22047858c79da..acba51ed497d2 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/inference_pipeline_card.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/inference_pipeline_card.tsx @@ -222,6 +222,11 @@ export const InferencePipelineCard: React.FC = (pipeline) => )} + {pipeline.sourceFields && pipeline.sourceFields.length > 0 && ( + + {pipeline.sourceFields.join(', ')} + + )} diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/pipeline_select_option.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/pipeline_select_option.tsx index f5fec5f54ac76..a458a2d6c2633 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/pipeline_select_option.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/pipeline_select_option.tsx @@ -52,7 +52,9 @@ export const PipelineSelectOption: React.FC = ({ pipe - {modelIdDisplay} + + {modelIdDisplay} + {pipeline.modelType.length > 0 && ( diff --git a/x-pack/plugins/enterprise_search/server/lib/indices/pipelines/ml_inference/pipeline_processors/get_ml_inference_pipeline_processors.test.ts b/x-pack/plugins/enterprise_search/server/lib/indices/pipelines/ml_inference/pipeline_processors/get_ml_inference_pipeline_processors.test.ts index faaf9cba8d2f6..020d717417820 100644 --- a/x-pack/plugins/enterprise_search/server/lib/indices/pipelines/ml_inference/pipeline_processors/get_ml_inference_pipeline_processors.test.ts +++ b/x-pack/plugins/enterprise_search/server/lib/indices/pipelines/ml_inference/pipeline_processors/get_ml_inference_pipeline_processors.test.ts @@ -48,6 +48,9 @@ const mockGetPipeline2 = { { inference: { inference_config: { regression: {} }, + field_map: { + title: 'text_field', + }, model_id: 'trained-model-id-1', }, }, @@ -59,6 +62,18 @@ const mockGetPipeline2 = { { inference: { inference_config: { regression: {} }, + field_map: { + title: 'text_field', + }, + model_id: 'trained-model-id-2', + }, + }, + { + inference: { + inference_config: { regression: {} }, + field_map: { + body: 'text_field', + }, model_id: 'trained-model-id-2', }, }, @@ -251,6 +266,7 @@ const trainedModelDataObject: Record = { pipelineName: 'ml-inference-pipeline-1', pipelineReferences: ['my-index@ml-inference'], types: ['pytorch', 'ner'], + sourceFields: [], }, 'trained-model-id-2': { modelId: 'trained-model-id-2', @@ -258,6 +274,7 @@ const trainedModelDataObject: Record = { pipelineName: 'ml-inference-pipeline-2', pipelineReferences: ['my-index@ml-inference'], types: ['pytorch', 'ner'], + sourceFields: [], }, 'ml-inference-pipeline-3': { modelId: 'trained-model-id-1', @@ -265,6 +282,7 @@ const trainedModelDataObject: Record = { pipelineName: 'ml-inference-pipeline-3', pipelineReferences: ['my-index@ml-inference'], types: ['pytorch', 'ner'], + sourceFields: [], }, }; @@ -305,7 +323,7 @@ describe('fetchMlInferencePipelines lib function', () => { expect(response).toEqual({}); }); - it('should return an empty object when getPipeline throws an error ', async () => { + it('should return an empty object when getPipeline throws an error', async () => { mockClient.ingest.getPipeline.mockImplementation(() => Promise.reject(notFoundError)); const response = await fetchMlInferencePipelines(mockClient as unknown as ElasticsearchClient); @@ -365,6 +383,7 @@ describe('fetchPipelineProcessorInferenceData lib function', () => { pipelineReferences: ['my-index@ml-inference', 'other-index@ml-inference'], trainedModelName: 'trained-model-id-1', types: [], + sourceFields: ['title'], }, { modelId: 'trained-model-id-2', @@ -373,6 +392,7 @@ describe('fetchPipelineProcessorInferenceData lib function', () => { pipelineReferences: ['my-index@ml-inference'], trainedModelName: 'trained-model-id-2', types: [], + sourceFields: ['title', 'body'], }, ]; @@ -390,6 +410,64 @@ describe('fetchPipelineProcessorInferenceData lib function', () => { }); expect(response).toEqual(expected); }); + + it('should return an empty array for a pipeline without an inference processor', async () => { + mockClient.ingest.getPipeline.mockImplementation(() => + Promise.resolve({ + 'ml-inference-pipeline-1': { + id: 'ml-inference-pipeline-1', + processors: [ + { + set: { + field: 'foo-field', + value: 'foo', + }, + }, + ], + }, + }) + ); + + const response = await fetchPipelineProcessorInferenceData( + mockClient as unknown as ElasticsearchClient, + ['ml-inference-pipeline-1', 'ml-inference-pipeline-2', 'non-ml-inference-pipeline'], + { + 'ml-inference-pipeline-1': ['my-index@ml-inference', 'other-index@ml-inference'], + 'ml-inference-pipeline-2': ['my-index@ml-inference'], + } + ); + + expect(response).toEqual([]); + }); + + it('should handle inference processors with no field mapping', async () => { + mockClient.ingest.getPipeline.mockImplementation(() => + Promise.resolve({ + 'ml-inference-pipeline-1': { + id: 'ml-inference-pipeline-1', + processors: [ + { + inference: { + inference_config: { regression: {} }, + model_id: 'trained-model-id-1', + }, + }, + ], + }, + }) + ); + + const response = await fetchPipelineProcessorInferenceData( + mockClient as unknown as ElasticsearchClient, + ['ml-inference-pipeline-1'], + { + 'ml-inference-pipeline-1': ['my-index@ml-inference'], + } + ); + + expect(response).toHaveLength(1); + expect(response[0].sourceFields).toEqual([]); + }); }); describe('getMlModelConfigsForModelIds lib function', () => { @@ -426,6 +504,7 @@ describe('getMlModelConfigsForModelIds lib function', () => { pipelineReferences: [], trainedModelName: 'trained-model-id-1', types: ['pytorch', 'ner'], + sourceFields: [], }, 'trained-model-id-2': { modelId: 'trained-model-id-2', @@ -434,6 +513,7 @@ describe('getMlModelConfigsForModelIds lib function', () => { pipelineReferences: [], trainedModelName: 'trained-model-id-2', types: ['pytorch', 'ner'], + sourceFields: [], }, }; @@ -464,6 +544,7 @@ describe('getMlModelConfigsForModelIds lib function', () => { pipelineReferences: [], trainedModelName: 'trained-model-id-1', types: ['pytorch', 'ner'], + sourceFields: [], }, 'trained-model-id-2': { modelId: 'trained-model-id-2', @@ -472,6 +553,7 @@ describe('getMlModelConfigsForModelIds lib function', () => { pipelineReferences: [], trainedModelName: 'trained-model-id-2', types: ['pytorch', 'ner'], + sourceFields: [], }, 'trained-model-id-3-in-other-space': { modelId: undefined, // Redacted @@ -480,6 +562,7 @@ describe('getMlModelConfigsForModelIds lib function', () => { pipelineReferences: [], trainedModelName: 'trained-model-id-3-in-other-space', types: ['pytorch', 'ner'], + sourceFields: [], }, }; @@ -537,6 +620,7 @@ describe('fetchAndAddTrainedModelData lib function', () => { pipelineReferences: [], trainedModelName: 'trained-model-id-1', types: [], + sourceFields: [], }, { modelId: 'trained-model-id-2', @@ -545,6 +629,7 @@ describe('fetchAndAddTrainedModelData lib function', () => { pipelineReferences: [], trainedModelName: 'trained-model-id-2', types: [], + sourceFields: [], }, { modelId: 'trained-model-id-3', @@ -553,6 +638,7 @@ describe('fetchAndAddTrainedModelData lib function', () => { pipelineReferences: [], trainedModelName: 'trained-model-id-3', types: [], + sourceFields: [], }, { modelId: 'trained-model-id-4', @@ -561,6 +647,7 @@ describe('fetchAndAddTrainedModelData lib function', () => { pipelineReferences: [], trainedModelName: 'trained-model-id-4', types: [], + sourceFields: [], }, ]; @@ -572,6 +659,7 @@ describe('fetchAndAddTrainedModelData lib function', () => { pipelineReferences: [], trainedModelName: 'trained-model-id-1', types: ['pytorch', 'ner'], + sourceFields: [], }, { modelId: 'trained-model-id-2', @@ -580,6 +668,7 @@ describe('fetchAndAddTrainedModelData lib function', () => { pipelineReferences: [], trainedModelName: 'trained-model-id-2', types: ['pytorch', 'ner'], + sourceFields: [], }, { modelId: 'trained-model-id-3', @@ -589,6 +678,7 @@ describe('fetchAndAddTrainedModelData lib function', () => { pipelineReferences: [], trainedModelName: 'trained-model-id-3', types: ['pytorch', 'text_classification'], + sourceFields: [], }, { modelId: 'trained-model-id-4', @@ -597,6 +687,7 @@ describe('fetchAndAddTrainedModelData lib function', () => { pipelineReferences: [], trainedModelName: 'trained-model-id-4', types: ['pytorch', 'fill_mask'], + sourceFields: [], }, ]; @@ -713,7 +804,12 @@ describe('fetchMlInferencePipelineProcessors lib function', () => { Promise.resolve(mockTrainedModelsInCurrentSpace) ); - const expected = [trainedModelDataObject['trained-model-id-1']] as InferencePipeline[]; + const expected = [ + { + ...trainedModelDataObject['trained-model-id-1'], + sourceFields: ['title'], + }, + ] as InferencePipeline[]; const response = await fetchMlInferencePipelineProcessors( mockClient as unknown as ElasticsearchClient, diff --git a/x-pack/plugins/enterprise_search/server/lib/indices/pipelines/ml_inference/pipeline_processors/get_ml_inference_pipeline_processors.ts b/x-pack/plugins/enterprise_search/server/lib/indices/pipelines/ml_inference/pipeline_processors/get_ml_inference_pipeline_processors.ts index e92396533e913..e6abf4ccd5464 100644 --- a/x-pack/plugins/enterprise_search/server/lib/indices/pipelines/ml_inference/pipeline_processors/get_ml_inference_pipeline_processors.ts +++ b/x-pack/plugins/enterprise_search/server/lib/indices/pipelines/ml_inference/pipeline_processors/get_ml_inference_pipeline_processors.ts @@ -19,6 +19,7 @@ import { getInferencePipelineNameFromIndexName } from '../../../../../utils/ml_i export type InferencePipelineData = InferencePipeline & { trainedModelName: string; + sourceFields: string[]; }; export const fetchMlInferencePipelines = async (client: ElasticsearchClient) => { @@ -84,15 +85,22 @@ export const fetchPipelineProcessorInferenceData = async ( return Object.keys(mlInferencePipelineProcessorConfigs).reduce( (pipelineProcessorData, pipelineProcessorName) => { - // Get the processors for the current pipeline processor of the ML Inference Processor. + // Get the processors for the current pipeline processor of the ML Inference Processor const subProcessors = mlInferencePipelineProcessorConfigs[pipelineProcessorName].processors || []; - // Find the inference processor, which we can assume there will only be one. - const inferenceProcessor = subProcessors.find((obj) => obj.hasOwnProperty('inference')); + // Get the inference processors; there is one per configured field, but they share the same model ID + const inferenceProcessors = subProcessors.filter((processor) => + processor.hasOwnProperty('inference') + ); + + const trainedModelName = inferenceProcessors[0]?.inference?.model_id; + if (trainedModelName) { + // Extract source fields from field mappings + const sourceFields = inferenceProcessors.flatMap((processor) => + Object.keys(processor.inference?.field_map ?? {}) + ); - const trainedModelName = inferenceProcessor?.inference?.model_id; - if (trainedModelName) pipelineProcessorData.push({ modelId: trainedModelName, modelState: TrainedModelState.NotDeployed, @@ -100,7 +108,9 @@ export const fetchPipelineProcessorInferenceData = async ( pipelineReferences: pipelineProcessorsMap?.[pipelineProcessorName] ?? [], trainedModelName, types: [], + sourceFields, }); + } return pipelineProcessorData; }, @@ -136,6 +146,7 @@ export const getMlModelConfigsForModelIds = async ( pipelineReferences: [], trainedModelName, types: getMlModelTypesForModelConfig(trainedModelData), + sourceFields: [], }; } }); From 3d95981a5984b034f143f1955b5804913c7ac3cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yulia=20=C4=8Cech?= <6585477+yuliacech@users.noreply.github.com> Date: Mon, 12 Feb 2024 17:50:24 +0100 Subject: [PATCH 06/83] [Console] Update autocomplete definitions (#176484) ## Summary This PR introduces changes after running the script to re-generate autocomplete definitions from the elasticsearch-specification repo. ### 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 - [ ] [Flaky Test Runner](https://ci-stats.kibana.dev/trigger_flaky_test_runner/1) was used on any tests changed - [ ] 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) --- .../json/generated/eql.get.json | 2 +- .../json/generated/eql.get_status.json | 2 +- .../json/generated/esql.query.json | 23 +++++++++++++++++++ .../json/generated/fleet.delete_secret.json | 15 ++++++++++++ .../json/generated/fleet.get_secret.json | 15 ++++++++++++ .../json/generated/fleet.post_secret.json | 15 ++++++++++++ .../indices.delete_index_template.json | 2 +- .../generated/indices.delete_template.json | 2 +- .../indices.exists_index_template.json | 2 +- .../generated/indices.exists_template.json | 2 +- .../generated/indices.get_index_template.json | 2 +- .../json/generated/indices.get_template.json | 2 +- .../generated/indices.put_index_template.json | 2 +- .../json/generated/indices.put_template.json | 2 +- .../indices.simulate_index_template.json | 2 +- .../generated/indices.simulate_template.json | 2 +- .../generated/search_application.delete.json | 2 +- ...security.create_cross_cluster_api_key.json | 2 +- .../json/generated/security.get_api_key.json | 3 ++- .../json/generated/security.get_settings.json | 15 ++++++++++++ ...security.update_cross_cluster_api_key.json | 2 +- .../generated/security.update_settings.json | 15 ++++++++++++ .../text_structure.find_structure.json | 1 + .../text_structure.test_grok_pattern.json | 23 +++++++++++++++++++ .../generated/transform.delete_transform.json | 1 + 25 files changed, 140 insertions(+), 16 deletions(-) create mode 100644 src/plugins/console/server/lib/spec_definitions/json/generated/esql.query.json create mode 100644 src/plugins/console/server/lib/spec_definitions/json/generated/fleet.delete_secret.json create mode 100644 src/plugins/console/server/lib/spec_definitions/json/generated/fleet.get_secret.json create mode 100644 src/plugins/console/server/lib/spec_definitions/json/generated/fleet.post_secret.json create mode 100644 src/plugins/console/server/lib/spec_definitions/json/generated/security.get_settings.json create mode 100644 src/plugins/console/server/lib/spec_definitions/json/generated/security.update_settings.json create mode 100644 src/plugins/console/server/lib/spec_definitions/json/generated/text_structure.test_grok_pattern.json diff --git a/src/plugins/console/server/lib/spec_definitions/json/generated/eql.get.json b/src/plugins/console/server/lib/spec_definitions/json/generated/eql.get.json index 78e7f48069cb8..5afbc9335217d 100644 --- a/src/plugins/console/server/lib/spec_definitions/json/generated/eql.get.json +++ b/src/plugins/console/server/lib/spec_definitions/json/generated/eql.get.json @@ -20,7 +20,7 @@ "patterns": [ "_eql/search/{id}" ], - "documentation": " https://www.elastic.co/guide/en/elasticsearch/reference/{branch}/get-async-eql-search-api.html", + "documentation": "https://www.elastic.co/guide/en/elasticsearch/reference/{branch}/get-async-eql-search-api.html", "availability": { "stack": true, "serverless": true diff --git a/src/plugins/console/server/lib/spec_definitions/json/generated/eql.get_status.json b/src/plugins/console/server/lib/spec_definitions/json/generated/eql.get_status.json index 74683e246654a..08854c55283f1 100644 --- a/src/plugins/console/server/lib/spec_definitions/json/generated/eql.get_status.json +++ b/src/plugins/console/server/lib/spec_definitions/json/generated/eql.get_status.json @@ -12,7 +12,7 @@ "patterns": [ "_eql/search/status/{id}" ], - "documentation": " https://www.elastic.co/guide/en/elasticsearch/reference/{branch}/get-async-eql-status-api.html", + "documentation": "https://www.elastic.co/guide/en/elasticsearch/reference/{branch}/get-async-eql-status-api.html", "availability": { "stack": true, "serverless": true diff --git a/src/plugins/console/server/lib/spec_definitions/json/generated/esql.query.json b/src/plugins/console/server/lib/spec_definitions/json/generated/esql.query.json new file mode 100644 index 0000000000000..452ab7c7b7eb9 --- /dev/null +++ b/src/plugins/console/server/lib/spec_definitions/json/generated/esql.query.json @@ -0,0 +1,23 @@ +{ + "esql.query": { + "url_params": { + "error_trace": "__flag__", + "filter_path": [], + "human": "__flag__", + "pretty": "__flag__", + "format": "", + "delimiter": "" + }, + "methods": [ + "POST" + ], + "patterns": [ + "_query" + ], + "documentation": "https://www.elastic.co/guide/en/elasticsearch/reference/{branch}/esql-rest.html", + "availability": { + "stack": true, + "serverless": false + } + } +} diff --git a/src/plugins/console/server/lib/spec_definitions/json/generated/fleet.delete_secret.json b/src/plugins/console/server/lib/spec_definitions/json/generated/fleet.delete_secret.json new file mode 100644 index 0000000000000..30424a461ceef --- /dev/null +++ b/src/plugins/console/server/lib/spec_definitions/json/generated/fleet.delete_secret.json @@ -0,0 +1,15 @@ +{ + "fleet.delete_secret": { + "methods": [ + "DELETE" + ], + "patterns": [ + "_fleet/secret/{id}" + ], + "documentation": null, + "availability": { + "stack": false, + "serverless": false + } + } +} diff --git a/src/plugins/console/server/lib/spec_definitions/json/generated/fleet.get_secret.json b/src/plugins/console/server/lib/spec_definitions/json/generated/fleet.get_secret.json new file mode 100644 index 0000000000000..55c4d2c10c013 --- /dev/null +++ b/src/plugins/console/server/lib/spec_definitions/json/generated/fleet.get_secret.json @@ -0,0 +1,15 @@ +{ + "fleet.get_secret": { + "methods": [ + "GET" + ], + "patterns": [ + "_fleet/secret/{id}" + ], + "documentation": null, + "availability": { + "stack": false, + "serverless": false + } + } +} diff --git a/src/plugins/console/server/lib/spec_definitions/json/generated/fleet.post_secret.json b/src/plugins/console/server/lib/spec_definitions/json/generated/fleet.post_secret.json new file mode 100644 index 0000000000000..546428df76bd3 --- /dev/null +++ b/src/plugins/console/server/lib/spec_definitions/json/generated/fleet.post_secret.json @@ -0,0 +1,15 @@ +{ + "fleet.post_secret": { + "methods": [ + "POST" + ], + "patterns": [ + "_fleet/secret" + ], + "documentation": null, + "availability": { + "stack": false, + "serverless": false + } + } +} diff --git a/src/plugins/console/server/lib/spec_definitions/json/generated/indices.delete_index_template.json b/src/plugins/console/server/lib/spec_definitions/json/generated/indices.delete_index_template.json index 55308d609009d..8144a6adbc8a0 100644 --- a/src/plugins/console/server/lib/spec_definitions/json/generated/indices.delete_index_template.json +++ b/src/plugins/console/server/lib/spec_definitions/json/generated/indices.delete_index_template.json @@ -22,7 +22,7 @@ "patterns": [ "_index_template/{name}" ], - "documentation": "https://www.elastic.co/guide/en/elasticsearch/reference/master/indices-templates.html", + "documentation": "https://www.elastic.co/guide/en/elasticsearch/reference/master/indices-delete-template.html", "availability": { "stack": true, "serverless": true diff --git a/src/plugins/console/server/lib/spec_definitions/json/generated/indices.delete_template.json b/src/plugins/console/server/lib/spec_definitions/json/generated/indices.delete_template.json index 99e379a038b9f..97f2be6b72999 100644 --- a/src/plugins/console/server/lib/spec_definitions/json/generated/indices.delete_template.json +++ b/src/plugins/console/server/lib/spec_definitions/json/generated/indices.delete_template.json @@ -22,7 +22,7 @@ "patterns": [ "_template/{name}" ], - "documentation": "https://www.elastic.co/guide/en/elasticsearch/reference/master/indices-templates.html", + "documentation": "https://www.elastic.co/guide/en/elasticsearch/reference/master/indices-delete-template-v1.html", "availability": { "stack": true, "serverless": false diff --git a/src/plugins/console/server/lib/spec_definitions/json/generated/indices.exists_index_template.json b/src/plugins/console/server/lib/spec_definitions/json/generated/indices.exists_index_template.json index d0a06f9a5387d..db69eca5efef5 100644 --- a/src/plugins/console/server/lib/spec_definitions/json/generated/indices.exists_index_template.json +++ b/src/plugins/console/server/lib/spec_definitions/json/generated/indices.exists_index_template.json @@ -17,7 +17,7 @@ "patterns": [ "_index_template/{name}" ], - "documentation": "https://www.elastic.co/guide/en/elasticsearch/reference/master/indices-templates.html", + "documentation": "https://www.elastic.co/guide/en/elasticsearch/reference/master/index-templates.html", "availability": { "stack": true, "serverless": true diff --git a/src/plugins/console/server/lib/spec_definitions/json/generated/indices.exists_template.json b/src/plugins/console/server/lib/spec_definitions/json/generated/indices.exists_template.json index ecfbd25d0b60d..4089c0f8b6a36 100644 --- a/src/plugins/console/server/lib/spec_definitions/json/generated/indices.exists_template.json +++ b/src/plugins/console/server/lib/spec_definitions/json/generated/indices.exists_template.json @@ -18,7 +18,7 @@ "patterns": [ "_template/{name}" ], - "documentation": "https://www.elastic.co/guide/en/elasticsearch/reference/master/indices-templates.html", + "documentation": "https://www.elastic.co/guide/en/elasticsearch/reference/master/indices-template-exists-v1.html", "availability": { "stack": true, "serverless": false diff --git a/src/plugins/console/server/lib/spec_definitions/json/generated/indices.get_index_template.json b/src/plugins/console/server/lib/spec_definitions/json/generated/indices.get_index_template.json index 9a7eb9a8c69a6..18f1cdf510134 100644 --- a/src/plugins/console/server/lib/spec_definitions/json/generated/indices.get_index_template.json +++ b/src/plugins/console/server/lib/spec_definitions/json/generated/indices.get_index_template.json @@ -21,7 +21,7 @@ "_index_template", "_index_template/{name}" ], - "documentation": "https://www.elastic.co/guide/en/elasticsearch/reference/master/indices-templates.html", + "documentation": "https://www.elastic.co/guide/en/elasticsearch/reference/master/indices-get-template.html", "availability": { "stack": true, "serverless": true diff --git a/src/plugins/console/server/lib/spec_definitions/json/generated/indices.get_template.json b/src/plugins/console/server/lib/spec_definitions/json/generated/indices.get_template.json index 9556552da5c9c..d257e18b62af4 100644 --- a/src/plugins/console/server/lib/spec_definitions/json/generated/indices.get_template.json +++ b/src/plugins/console/server/lib/spec_definitions/json/generated/indices.get_template.json @@ -20,7 +20,7 @@ "_template", "_template/{name}" ], - "documentation": "https://www.elastic.co/guide/en/elasticsearch/reference/master/indices-templates.html", + "documentation": "https://www.elastic.co/guide/en/elasticsearch/reference/master/indices-get-template-v1.html", "availability": { "stack": true, "serverless": false diff --git a/src/plugins/console/server/lib/spec_definitions/json/generated/indices.put_index_template.json b/src/plugins/console/server/lib/spec_definitions/json/generated/indices.put_index_template.json index a78ee9a8078b6..9ad7c1efb4a46 100644 --- a/src/plugins/console/server/lib/spec_definitions/json/generated/indices.put_index_template.json +++ b/src/plugins/console/server/lib/spec_definitions/json/generated/indices.put_index_template.json @@ -14,7 +14,7 @@ "patterns": [ "_index_template/{name}" ], - "documentation": "https://www.elastic.co/guide/en/elasticsearch/reference/master/indices-templates.html", + "documentation": "https://www.elastic.co/guide/en/elasticsearch/reference/master/indices-put-template.html", "availability": { "stack": true, "serverless": true diff --git a/src/plugins/console/server/lib/spec_definitions/json/generated/indices.put_template.json b/src/plugins/console/server/lib/spec_definitions/json/generated/indices.put_template.json index 79d1b509f3a87..9a9ca116f50c5 100644 --- a/src/plugins/console/server/lib/spec_definitions/json/generated/indices.put_template.json +++ b/src/plugins/console/server/lib/spec_definitions/json/generated/indices.put_template.json @@ -26,7 +26,7 @@ "patterns": [ "_template/{name}" ], - "documentation": "https://www.elastic.co/guide/en/elasticsearch/reference/master/indices-templates.html", + "documentation": "https://www.elastic.co/guide/en/elasticsearch/reference/master/indices-templates-v1.html", "availability": { "stack": true, "serverless": true diff --git a/src/plugins/console/server/lib/spec_definitions/json/generated/indices.simulate_index_template.json b/src/plugins/console/server/lib/spec_definitions/json/generated/indices.simulate_index_template.json index 5bdefa03e721e..f3e8070bdb999 100644 --- a/src/plugins/console/server/lib/spec_definitions/json/generated/indices.simulate_index_template.json +++ b/src/plugins/console/server/lib/spec_definitions/json/generated/indices.simulate_index_template.json @@ -19,7 +19,7 @@ "patterns": [ "_index_template/_simulate_index/{name}" ], - "documentation": "https://www.elastic.co/guide/en/elasticsearch/reference/master/indices-templates.html", + "documentation": "https://www.elastic.co/guide/en/elasticsearch/reference/master/indices-simulate-index.html", "availability": { "stack": true, "serverless": true diff --git a/src/plugins/console/server/lib/spec_definitions/json/generated/indices.simulate_template.json b/src/plugins/console/server/lib/spec_definitions/json/generated/indices.simulate_template.json index de04035e02ad0..d19188cbd4c41 100644 --- a/src/plugins/console/server/lib/spec_definitions/json/generated/indices.simulate_template.json +++ b/src/plugins/console/server/lib/spec_definitions/json/generated/indices.simulate_template.json @@ -20,7 +20,7 @@ "_index_template/_simulate", "_index_template/_simulate/{name}" ], - "documentation": "https://www.elastic.co/guide/en/elasticsearch/reference/master/indices-templates.html", + "documentation": "https://www.elastic.co/guide/en/elasticsearch/reference/master/indices-simulate-template.html", "availability": { "stack": true, "serverless": true diff --git a/src/plugins/console/server/lib/spec_definitions/json/generated/search_application.delete.json b/src/plugins/console/server/lib/spec_definitions/json/generated/search_application.delete.json index a8c3706ac4b4d..8e127e8d58995 100644 --- a/src/plugins/console/server/lib/spec_definitions/json/generated/search_application.delete.json +++ b/src/plugins/console/server/lib/spec_definitions/json/generated/search_application.delete.json @@ -12,7 +12,7 @@ "patterns": [ "_application/search_application/{name}" ], - "documentation": "https://www.elastic.co/guide/en/elasticsearch/reference/master/put-search-application.html", + "documentation": "https://www.elastic.co/guide/en/elasticsearch/reference/master/delete-search-application.html", "availability": { "stack": true, "serverless": true diff --git a/src/plugins/console/server/lib/spec_definitions/json/generated/security.create_cross_cluster_api_key.json b/src/plugins/console/server/lib/spec_definitions/json/generated/security.create_cross_cluster_api_key.json index 9319fee560ba4..75a98cd4373f3 100644 --- a/src/plugins/console/server/lib/spec_definitions/json/generated/security.create_cross_cluster_api_key.json +++ b/src/plugins/console/server/lib/spec_definitions/json/generated/security.create_cross_cluster_api_key.json @@ -8,7 +8,7 @@ ], "documentation": "https://www.elastic.co/guide/en/elasticsearch/reference/current/security-api-create-cross-cluster-api-key.html", "availability": { - "stack": false, + "stack": true, "serverless": false } } diff --git a/src/plugins/console/server/lib/spec_definitions/json/generated/security.get_api_key.json b/src/plugins/console/server/lib/spec_definitions/json/generated/security.get_api_key.json index 3c9d65672ca1e..eb5aaeccce007 100644 --- a/src/plugins/console/server/lib/spec_definitions/json/generated/security.get_api_key.json +++ b/src/plugins/console/server/lib/spec_definitions/json/generated/security.get_api_key.json @@ -10,7 +10,8 @@ "owner": "__flag__", "realm_name": "", "username": "", - "with_limited_by": "__flag__" + "with_limited_by": "__flag__", + "active_only": "__flag__" }, "methods": [ "GET" diff --git a/src/plugins/console/server/lib/spec_definitions/json/generated/security.get_settings.json b/src/plugins/console/server/lib/spec_definitions/json/generated/security.get_settings.json new file mode 100644 index 0000000000000..f91793b92cc8c --- /dev/null +++ b/src/plugins/console/server/lib/spec_definitions/json/generated/security.get_settings.json @@ -0,0 +1,15 @@ +{ + "security.get_settings": { + "methods": [ + "GET" + ], + "patterns": [ + "_security/settings" + ], + "documentation": "https://www.elastic.co/guide/en/elasticsearch/reference/current/security-api-get-settings.html", + "availability": { + "stack": true, + "serverless": false + } + } +} diff --git a/src/plugins/console/server/lib/spec_definitions/json/generated/security.update_cross_cluster_api_key.json b/src/plugins/console/server/lib/spec_definitions/json/generated/security.update_cross_cluster_api_key.json index f297f664e562a..e0897d85d46d4 100644 --- a/src/plugins/console/server/lib/spec_definitions/json/generated/security.update_cross_cluster_api_key.json +++ b/src/plugins/console/server/lib/spec_definitions/json/generated/security.update_cross_cluster_api_key.json @@ -8,7 +8,7 @@ ], "documentation": "https://www.elastic.co/guide/en/elasticsearch/reference/current/security-api-update-cross-cluster-api-key.html", "availability": { - "stack": false, + "stack": true, "serverless": false } } diff --git a/src/plugins/console/server/lib/spec_definitions/json/generated/security.update_settings.json b/src/plugins/console/server/lib/spec_definitions/json/generated/security.update_settings.json new file mode 100644 index 0000000000000..609adc164cc0f --- /dev/null +++ b/src/plugins/console/server/lib/spec_definitions/json/generated/security.update_settings.json @@ -0,0 +1,15 @@ +{ + "security.update_settings": { + "methods": [ + "PUT" + ], + "patterns": [ + "_security/settings" + ], + "documentation": "https://www.elastic.co/guide/en/elasticsearch/reference/current/security-api-update-settings.html", + "availability": { + "stack": true, + "serverless": false + } + } +} diff --git a/src/plugins/console/server/lib/spec_definitions/json/generated/text_structure.find_structure.json b/src/plugins/console/server/lib/spec_definitions/json/generated/text_structure.find_structure.json index 8c896988c3c31..7b0248d640819 100644 --- a/src/plugins/console/server/lib/spec_definitions/json/generated/text_structure.find_structure.json +++ b/src/plugins/console/server/lib/spec_definitions/json/generated/text_structure.find_structure.json @@ -4,6 +4,7 @@ "charset": "", "column_names": "", "delimiter": "", + "ecs_compatibility": "", "explain": "__flag__", "format": "", "grok_pattern": "", diff --git a/src/plugins/console/server/lib/spec_definitions/json/generated/text_structure.test_grok_pattern.json b/src/plugins/console/server/lib/spec_definitions/json/generated/text_structure.test_grok_pattern.json new file mode 100644 index 0000000000000..0c0c73c99bf51 --- /dev/null +++ b/src/plugins/console/server/lib/spec_definitions/json/generated/text_structure.test_grok_pattern.json @@ -0,0 +1,23 @@ +{ + "text_structure.test_grok_pattern": { + "url_params": { + "error_trace": "__flag__", + "filter_path": [], + "human": "__flag__", + "pretty": "__flag__", + "ecs_compatibility": "" + }, + "methods": [ + "GET", + "POST" + ], + "patterns": [ + "_text_structure/test_grok_pattern" + ], + "documentation": "https://www.elastic.co/guide/en/elasticsearch/reference/master/test-grok-pattern-api.html", + "availability": { + "stack": true, + "serverless": false + } + } +} diff --git a/src/plugins/console/server/lib/spec_definitions/json/generated/transform.delete_transform.json b/src/plugins/console/server/lib/spec_definitions/json/generated/transform.delete_transform.json index 4f03a50181c00..6d1631deb021f 100644 --- a/src/plugins/console/server/lib/spec_definitions/json/generated/transform.delete_transform.json +++ b/src/plugins/console/server/lib/spec_definitions/json/generated/transform.delete_transform.json @@ -6,6 +6,7 @@ "human": "__flag__", "pretty": "__flag__", "force": "__flag__", + "delete_dest_index": "__flag__", "timeout": [ "30s", "-1", From 385ddf8c47bd0b1b8fb24462c523fbfa739059f2 Mon Sep 17 00:00:00 2001 From: Ido Cohen <90558359+CohenIdo@users.noreply.github.com> Date: Mon, 12 Feb 2024 19:09:15 +0200 Subject: [PATCH 07/83] [Cloud Security][telemetry] posture score based on enabled rules --- .../cloud_accounts_stats_collector.ts | 85 +++++++++++++++++-- .../lib/telemetry/collectors/register.ts | 5 +- .../server/lib/telemetry/collectors/schema.ts | 8 ++ .../server/lib/telemetry/collectors/types.ts | 2 + .../schema/xpack_plugins.json | 22 +++++ 5 files changed, 115 insertions(+), 7 deletions(-) diff --git a/x-pack/plugins/cloud_security_posture/server/lib/telemetry/collectors/cloud_accounts_stats_collector.ts b/x-pack/plugins/cloud_security_posture/server/lib/telemetry/collectors/cloud_accounts_stats_collector.ts index fac84a5d8a7a2..5cd7c6dc03766 100644 --- a/x-pack/plugins/cloud_security_posture/server/lib/telemetry/collectors/cloud_accounts_stats_collector.ts +++ b/x-pack/plugins/cloud_security_posture/server/lib/telemetry/collectors/cloud_accounts_stats_collector.ts @@ -5,7 +5,7 @@ * 2.0. */ import type { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; -import type { Logger } from '@kbn/core/server'; +import type { ISavedObjectsRepository, Logger } from '@kbn/core/server'; import type { SearchRequest } from '@elastic/elasticsearch/lib/api/types'; import { getPackagePolicyIdRuntimeMapping } from '../../../../common/runtime_mappings/get_package_policy_id_mapping'; import { getIdentifierRuntimeMapping } from '../../../../common/runtime_mappings/get_identifier_runtime_mapping'; @@ -23,6 +23,10 @@ import { LATEST_VULNERABILITIES_INDEX_DEFAULT_NS, VULN_MGMT_POLICY_TEMPLATE, } from '../../../../common/constants'; +import { + getCspBenchmarkRulesStatesHandler, + getMutedRulesFilterQuery, +} from '../../../routes/benchmark_rules/get_states/v1'; export const getPostureAccountsStatsQuery = (index: string): SearchRequest => ({ index, @@ -348,23 +352,90 @@ export const getCloudAccountsStats = ( return cloudAccountsStats; }; +const getAccountStatsBasedOnEnablesRule = async ( + esClient: ElasticsearchClient, + encryptedSoClient: ISavedObjectsRepository, + accountQuery: SearchRequest, + logger: Logger +): Promise => { + const mutedRulesObject = await getCspBenchmarkRulesStatesHandler(encryptedSoClient); + const benchmarksWithMutedRules = [ + ...new Set( + Object.values(mutedRulesObject).map((item) => { + if (item.muted === true) return item.benchmark_id; + }) + ), + ].filter(Boolean); + + if (benchmarksWithMutedRules.length) { + const mutedRulesFilterQuery = await getMutedRulesFilterQuery(encryptedSoClient); + + const enabledRulesQuery = { + ...accountQuery, + query: { + bool: { + must_not: mutedRulesFilterQuery, + must: { + terms: { + 'rule.benchmark.id': benchmarksWithMutedRules, + }, + }, + }, + }, + }; + + const enabledRulesAccountsStatsResponse = await esClient.search( + enabledRulesQuery + ); + const cloudAccountsStatsForEnabledRules = enabledRulesAccountsStatsResponse.aggregations + ? getCloudAccountsStats(enabledRulesAccountsStatsResponse.aggregations, logger) + : []; + return cloudAccountsStatsForEnabledRules; + } + return []; +}; + export const getIndexAccountStats = async ( esClient: ElasticsearchClient, + encryptedSoClient: ISavedObjectsRepository, logger: Logger, index: string, getAccountQuery: (index: string) => SearchRequest -) => { - const accountsStatsResponse = await esClient.search( - getAccountQuery(index) - ); +): Promise => { + const accountQuery = getAccountQuery(index); + + const accountsStatsResponse = await esClient.search(accountQuery); - return accountsStatsResponse.aggregations + const cloudAccountsStats = accountsStatsResponse.aggregations ? getCloudAccountsStats(accountsStatsResponse.aggregations, logger) : []; + + if (index === LATEST_FINDINGS_INDEX_DEFAULT_NS) { + const cloudAccountsStatsForEnabledRules = await getAccountStatsBasedOnEnablesRule( + esClient, + encryptedSoClient, + accountQuery, + logger + ); + + cloudAccountsStatsForEnabledRules.forEach((statsEnabledRule) => { + const foundStatsIndex = cloudAccountsStats.findIndex( + (stats) => stats.account_id === statsEnabledRule.account_id + ); + if (foundStatsIndex !== -1) { + // Update the object in cloudAccountsStats based on the object in cloudAccountsStatsForEnabledRules + cloudAccountsStats[foundStatsIndex].posture_management_stats_enabled_rules = + statsEnabledRule.posture_management_stats; + cloudAccountsStats[foundStatsIndex].has_muted_rules = true; + } + }); + } + return cloudAccountsStats; }; export const getAllCloudAccountsStats = async ( esClient: ElasticsearchClient, + encryptedSoClient: ISavedObjectsRepository, logger: Logger ): Promise => { try { @@ -385,6 +456,7 @@ export const getAllCloudAccountsStats = async ( if (findingIndex.exists) { postureIndexAccountStats = await getIndexAccountStats( esClient, + encryptedSoClient, logger, findingIndex.name, getPostureAccountsStatsQuery @@ -394,6 +466,7 @@ export const getAllCloudAccountsStats = async ( if (vulnerabilitiesIndex.exists) { vulnerabilityIndexAccountStats = await getIndexAccountStats( esClient, + encryptedSoClient, logger, vulnerabilitiesIndex.name, getVulnMgmtAccountsStatsQuery diff --git a/x-pack/plugins/cloud_security_posture/server/lib/telemetry/collectors/register.ts b/x-pack/plugins/cloud_security_posture/server/lib/telemetry/collectors/register.ts index 3d283eed41834..259611e12032e 100644 --- a/x-pack/plugins/cloud_security_posture/server/lib/telemetry/collectors/register.ts +++ b/x-pack/plugins/cloud_security_posture/server/lib/telemetry/collectors/register.ts @@ -78,7 +78,10 @@ export function registerCspmUsageCollector( getInstallationStats(esClient, soClient, coreServices, logger) ), awaitPromiseSafe('Alerts', getAlertsStats(esClient, logger)), - awaitPromiseSafe('Cloud Accounts', getAllCloudAccountsStats(esClient, logger)), + awaitPromiseSafe( + 'Cloud Accounts', + getAllCloudAccountsStats(esClient, encryptedSoClient, logger) + ), awaitPromiseSafe('Muted Rules', getMutedRulesStats(soClient, encryptedSoClient, logger)), ]); return { diff --git a/x-pack/plugins/cloud_security_posture/server/lib/telemetry/collectors/schema.ts b/x-pack/plugins/cloud_security_posture/server/lib/telemetry/collectors/schema.ts index 0b0be13ba3a1f..22be67ca6a7b1 100644 --- a/x-pack/plugins/cloud_security_posture/server/lib/telemetry/collectors/schema.ts +++ b/x-pack/plugins/cloud_security_posture/server/lib/telemetry/collectors/schema.ts @@ -185,12 +185,20 @@ export const cspmUsageSchema: MakeSchemaFrom = { passed_findings_count: { type: 'long' }, failed_findings_count: { type: 'long' }, }, + posture_management_stats_enabled_rules: { + posture_score: { type: 'long' }, + benchmark_name: { type: 'keyword' }, + benchmark_version: { type: 'keyword' }, + passed_findings_count: { type: 'long' }, + failed_findings_count: { type: 'long' }, + }, kspm_stats: { kubernetes_version: { type: 'keyword' }, agents_count: { type: 'short' }, nodes_count: { type: 'short' }, pods_count: { type: 'short' }, }, + has_muted_rules: { type: 'boolean' }, }, }, muted_rules_stats: { diff --git a/x-pack/plugins/cloud_security_posture/server/lib/telemetry/collectors/types.ts b/x-pack/plugins/cloud_security_posture/server/lib/telemetry/collectors/types.ts index f078b35e25ee6..35308647df386 100644 --- a/x-pack/plugins/cloud_security_posture/server/lib/telemetry/collectors/types.ts +++ b/x-pack/plugins/cloud_security_posture/server/lib/telemetry/collectors/types.ts @@ -77,9 +77,11 @@ export interface CloudSecurityAccountsStats { cloud_provider: string | null; package_policy_id: string | null; posture_management_stats?: CloudPostureAccountsStats; + posture_management_stats_enabled_rules?: CloudPostureAccountsStats; kspm_stats?: KSPMAccountsStats; latest_doc_count: number; latest_doc_updated_timestamp: string; + has_muted_rules?: boolean; } export interface CloudPostureAccountsStats { posture_score: number; diff --git a/x-pack/plugins/telemetry_collection_xpack/schema/xpack_plugins.json b/x-pack/plugins/telemetry_collection_xpack/schema/xpack_plugins.json index d6840a2267d7b..26b8ab2f1c537 100644 --- a/x-pack/plugins/telemetry_collection_xpack/schema/xpack_plugins.json +++ b/x-pack/plugins/telemetry_collection_xpack/schema/xpack_plugins.json @@ -7778,6 +7778,25 @@ } } }, + "posture_management_stats_enabled_rules": { + "properties": { + "posture_score": { + "type": "long" + }, + "benchmark_name": { + "type": "keyword" + }, + "benchmark_version": { + "type": "keyword" + }, + "passed_findings_count": { + "type": "long" + }, + "failed_findings_count": { + "type": "long" + } + } + }, "kspm_stats": { "properties": { "kubernetes_version": { @@ -7793,6 +7812,9 @@ "type": "short" } } + }, + "has_muted_rules": { + "type": "boolean" } } } From 509248b0c6a4f4c7a7f658eab5e69254bc1bd9b5 Mon Sep 17 00:00:00 2001 From: Davis McPhee Date: Mon, 12 Feb 2024 13:18:17 -0400 Subject: [PATCH 08/83] [Saved Queries] Improve saved query management (#170599) ## Summary This PR introduces a number of changes and improvements to saved query management: - Add server side pagination (5 queries per page) and search functionality to the "Load query" list, which improves UX and performance by no longer requesting all queries at once. - Redesign the "Load query" list to improve the UX and a11y, making it possible for keyboard users to effectively navigate the list and load/delete queries. - Add an "Active" badge to the "Load query" list to indicate which list entry represents the currently loaded query, and hoist the entry to the top of the first page for better visibility when no search term exists. - Deprecate the saved query `/_all` endpoint and update it to return only the first 100 queries instead of loading them all into memory at once. - Add a new `titleKeyword` field to the saved query SO, which allows sorting queries alphabetically by title when displaying them in the "Load query" list. - Improve the performance of the "has saved queries" check when Unified Search is mounted to no longer request actual queries, and instead just request the count. - Update the saved query duplicate title check to no longer rely on fetching all queries at once, and instead asynchronously check for duplicates by title on save. - Add server side duplicate title validation to the create and update saved query endpoints. - Various small fixes and cleanups throughout saved query management. https://github.com/elastic/kibana/assets/25592674/43328aea-0f7b-4b7a-a5fb-e33ed822f317 Resolves #172044. Resolves #176427. ## Testing To generate saved queries for testing, run the script below and replace `{KIBANA_REQUEST_COOKIE}` with the cookie header value from an API request of a Kibana user with an active session: ```shell for i in {1..100}; do curl 'http://localhost:5601/internal/saved_query/_create' \ -H 'Accept: */*' \ -H 'Accept-Language: en-US,en;q=0.9,az;q=0.8,es;q=0.7' \ -H 'Cache-Control: no-cache' \ -H 'Connection: keep-alive' \ -H 'Content-Type: application/json' \ -H 'Cookie: {KIBANA_REQUEST_COOKIE}' \ -H 'elastic-api-version: 1' \ -H 'kbn-build-number: 9007199254740991' \ -H 'kbn-version: 8.13.0' \ -H 'x-elastic-internal-origin: Kibana' \ --data-raw '{"title":"query '"$(echo $(($i - 1)) | tr '[0-9]' '[a-j]')"'","description":"","query":{"query":"bytes > 500","language":"kuery"},"filters":[]}' \ --compressed; done ``` ### 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) - [ ] [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 - [x] Any UI touched in this PR is usable by keyboard only (learn more about [keyboard accessibility](https://webaim.org/techniques/keyboard/)) - [x] 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) - [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) ### 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) --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: Stratoula Kalafateli --- .../current_fields.json | 3 +- .../current_mappings.json | 3 + .../check_registered_types.test.ts | 2 +- src/plugins/data/public/query/mocks.ts | 2 +- .../saved_query/saved_query_service.test.ts | 27 +- .../query/saved_query/saved_query_service.ts | 22 +- .../data/public/query/saved_query/types.ts | 2 +- .../query/route_handler_context.test.ts | 116 ++- .../server/query/route_handler_context.ts | 178 +++-- src/plugins/data/server/query/route_types.ts | 2 +- src/plugins/data/server/query/routes.ts | 69 +- .../data/server/saved_objects/query.test.ts | 60 ++ .../data/server/saved_objects/query.ts | 38 +- .../server/saved_objects/schemas/query.ts | 40 +- src/plugins/data/tsconfig.json | 3 +- .../add_filter_popover.styles.ts | 20 - .../query_string_input/add_filter_popover.tsx | 5 +- .../public/query_string_input/panel_title.tsx | 97 +++ .../query_bar_menu.test.tsx | 1 + .../query_string_input/query_bar_menu.tsx | 112 +-- .../query_bar_menu_panels.tsx | 144 ++-- .../saved_query_form/save_query_form.tsx | 77 +- .../saved_query_management_list.scss | 4 - .../saved_query_management_list.test.tsx | 626 ++++++++++++++--- .../saved_query_management_list.tsx | 662 ++++++++++++------ .../public/search_bar/search_bar.test.tsx | 2 + .../public/search_bar/search_bar.tsx | 76 +- src/plugins/unified_search/public/types.ts | 3 + src/plugins/unified_search/tsconfig.json | 1 + test/accessibility/apps/discover.ts | 8 +- .../apis/saved_queries/{index.js => index.ts} | 4 +- .../apis/saved_queries/saved_queries.js | 154 ---- .../apis/saved_queries/saved_queries.ts | 426 +++++++++++ test/functional/page_objects/discover_page.ts | 10 +- .../saved_query_management_component.ts | 10 +- .../lens/public/app_plugin/lens_top_nav.tsx | 9 +- .../common/lib/kibana/kibana_react.mock.ts | 17 +- .../public/mocks/test_providers.tsx | 17 +- .../translations/translations/fr-FR.json | 3 - .../translations/translations/ja-JP.json | 3 - .../translations/translations/zh-CN.json | 3 - .../cypress/tasks/api_calls/saved_queries.ts | 2 +- 42 files changed, 2204 insertions(+), 859 deletions(-) create mode 100644 src/plugins/data/server/saved_objects/query.test.ts delete mode 100644 src/plugins/unified_search/public/query_string_input/add_filter_popover.styles.ts create mode 100644 src/plugins/unified_search/public/query_string_input/panel_title.tsx rename test/api_integration/apis/saved_queries/{index.js => index.ts} (77%) delete mode 100644 test/api_integration/apis/saved_queries/saved_queries.js create mode 100644 test/api_integration/apis/saved_queries/saved_queries.ts diff --git a/packages/kbn-check-mappings-update-cli/current_fields.json b/packages/kbn-check-mappings-update-cli/current_fields.json index 4d8c775710f09..01a7de459affb 100644 --- a/packages/kbn-check-mappings-update-cli/current_fields.json +++ b/packages/kbn-check-mappings-update-cli/current_fields.json @@ -716,7 +716,8 @@ ], "query": [ "description", - "title" + "title", + "titleKeyword" ], "risk-engine-configuration": [ "dataViewId", diff --git a/packages/kbn-check-mappings-update-cli/current_mappings.json b/packages/kbn-check-mappings-update-cli/current_mappings.json index 758fde639d00f..aaf612ed8ed56 100644 --- a/packages/kbn-check-mappings-update-cli/current_mappings.json +++ b/packages/kbn-check-mappings-update-cli/current_mappings.json @@ -2403,6 +2403,9 @@ }, "title": { "type": "text" + }, + "titleKeyword": { + "type": "keyword" } } }, diff --git a/src/core/server/integration_tests/ci_checks/saved_objects/check_registered_types.test.ts b/src/core/server/integration_tests/ci_checks/saved_objects/check_registered_types.test.ts index e99ca235bfad4..5f1c48c0391ff 100644 --- a/src/core/server/integration_tests/ci_checks/saved_objects/check_registered_types.test.ts +++ b/src/core/server/integration_tests/ci_checks/saved_objects/check_registered_types.test.ts @@ -130,7 +130,7 @@ describe('checking migration metadata changes on all registered SO types', () => "osquery-pack-asset": "cd140bc2e4b092e93692b587bf6e38051ef94c75", "osquery-saved-query": "6095e288750aa3164dfe186c74bc5195c2bf2bd4", "policy-settings-protection-updates-note": "33924bb246f9e5bcb876109cc83e3c7a28308352", - "query": "21cbbaa09abb679078145ce90087b1e88b7eae95", + "query": "501bece68f26fe561286a488eabb1a8ab12f1137", "risk-engine-configuration": "b105d4a3c6adce40708d729d12e5ef3c8fbd9508", "rules-settings": "892a2918ebaeba809a612b8d97cec0b07c800b5f", "sample-data-telemetry": "37441b12f5b0159c2d6d5138a494c9f440e950b5", diff --git a/src/plugins/data/public/query/mocks.ts b/src/plugins/data/public/query/mocks.ts index 14de815c0d793..74dd77c904165 100644 --- a/src/plugins/data/public/query/mocks.ts +++ b/src/plugins/data/public/query/mocks.ts @@ -38,7 +38,7 @@ const createStartContractMock = () => { addToQueryLog: jest.fn(), filterManager: createFilterManagerMock(), queryString: queryStringManagerMock.createStartContract(), - savedQueries: { getSavedQuery: jest.fn() } as any, + savedQueries: { getSavedQuery: jest.fn(), getSavedQueryCount: jest.fn() } as any, state$: new Observable(), getState: jest.fn(), timefilter: timefilterServiceMock.createStartContract(), diff --git a/src/plugins/data/public/query/saved_query/saved_query_service.test.ts b/src/plugins/data/public/query/saved_query/saved_query_service.test.ts index 3a223109dbd76..07b341fb3eaa5 100644 --- a/src/plugins/data/public/query/saved_query/saved_query_service.test.ts +++ b/src/plugins/data/public/query/saved_query/saved_query_service.test.ts @@ -14,12 +14,12 @@ import { SAVED_QUERY_BASE_URL } from '../../../common/constants'; const http = httpServiceMock.createStartContract(); const { + isDuplicateTitle, deleteSavedQuery, getSavedQuery, findSavedQueries, createQuery, updateQuery, - getAllSavedQueries, getSavedQueryCount, } = createSavedQueryService(http); @@ -42,6 +42,18 @@ describe('saved query service', () => { http.delete.mockReset(); }); + describe('isDuplicateTitle', function () { + it('should post the title and ID', async () => { + http.post.mockResolvedValue({ isDuplicate: true }); + await isDuplicateTitle('foo', 'bar'); + expect(http.post).toBeCalled(); + expect(http.post).toHaveBeenCalledWith(`${SAVED_QUERY_BASE_URL}/_is_duplicate_title`, { + body: '{"title":"foo","id":"bar"}', + version, + }); + }); + }); + describe('createQuery', function () { it('should post the stringified given attributes', async () => { await createQuery(savedQueryAttributes); @@ -64,19 +76,6 @@ describe('saved query service', () => { }); }); - describe('getAllSavedQueries', function () { - it('should post and extract the saved queries from the response', async () => { - http.post.mockResolvedValue({ - total: 0, - savedQueries: [{ attributes: savedQueryAttributes }], - }); - const result = await getAllSavedQueries(); - expect(http.post).toBeCalled(); - expect(http.post).toHaveBeenCalledWith(`${SAVED_QUERY_BASE_URL}/_all`, { version }); - expect(result).toEqual([{ attributes: savedQueryAttributes }]); - }); - }); - describe('findSavedQueries', function () { it('should post and return the total & saved queries', async () => { http.post.mockResolvedValue({ diff --git a/src/plugins/data/public/query/saved_query/saved_query_service.ts b/src/plugins/data/public/query/saved_query/saved_query_service.ts index 09afd75470dd0..e3847b357bdee 100644 --- a/src/plugins/data/public/query/saved_query/saved_query_service.ts +++ b/src/plugins/data/public/query/saved_query/saved_query_service.ts @@ -14,6 +14,17 @@ import { SAVED_QUERY_BASE_URL } from '../../../common/constants'; const version = '1'; export const createSavedQueryService = (http: HttpStart) => { + const isDuplicateTitle = async (title: string, id?: string) => { + const response = await http.post<{ isDuplicate: boolean }>( + `${SAVED_QUERY_BASE_URL}/_is_duplicate_title`, + { + body: JSON.stringify({ title, id }), + version, + } + ); + return response.isDuplicate; + }; + const createQuery = async (attributes: SavedQueryAttributes, { overwrite = false } = {}) => { const savedQuery = await http.post(`${SAVED_QUERY_BASE_URL}/_create`, { body: JSON.stringify(attributes), @@ -30,15 +41,6 @@ export const createSavedQueryService = (http: HttpStart) => { return savedQuery; }; - // we have to tell the saved objects client how many to fetch, otherwise it defaults to fetching 20 per page - const getAllSavedQueries = async (): Promise => { - const { savedQueries } = await http.post<{ savedQueries: SavedQuery[] }>( - `${SAVED_QUERY_BASE_URL}/_all`, - { version } - ); - return savedQueries; - }; - // findSavedQueries will do a 'match_all' if no search string is passed in const findSavedQueries = async ( search: string = '', @@ -69,9 +71,9 @@ export const createSavedQueryService = (http: HttpStart) => { }; return { + isDuplicateTitle, createQuery, updateQuery, - getAllSavedQueries, findSavedQueries, getSavedQuery, deleteSavedQuery, diff --git a/src/plugins/data/public/query/saved_query/types.ts b/src/plugins/data/public/query/saved_query/types.ts index 7b6b7408b4369..984ca9e804b01 100644 --- a/src/plugins/data/public/query/saved_query/types.ts +++ b/src/plugins/data/public/query/saved_query/types.ts @@ -17,9 +17,9 @@ export type SavedQueryTimeFilter = TimeRange & { export type { SavedQuery, SavedQueryAttributes }; export interface SavedQueryService { + isDuplicateTitle: (title: string, id?: string) => Promise; createQuery: (attributes: SavedQueryAttributes) => Promise; updateQuery: (id: string, attributes: SavedQueryAttributes) => Promise; - getAllSavedQueries: () => Promise; findSavedQueries: ( searchText?: string, perPage?: number, diff --git a/src/plugins/data/server/query/route_handler_context.test.ts b/src/plugins/data/server/query/route_handler_context.test.ts index 08052944cb283..5976db550f182 100644 --- a/src/plugins/data/server/query/route_handler_context.test.ts +++ b/src/plugins/data/server/query/route_handler_context.test.ts @@ -10,7 +10,10 @@ import { coreMock } from '@kbn/core/server/mocks'; import { FilterStateStore, Query } from '@kbn/es-query'; import { DATA_VIEW_SAVED_OBJECT_TYPE } from '../../common'; import type { SavedObject, SavedQueryAttributes } from '../../common'; -import { registerSavedQueryRouteHandlerContext } from './route_handler_context'; +import { + InternalSavedQueryAttributes, + registerSavedQueryRouteHandlerContext, +} from './route_handler_context'; import { SavedObjectsFindResponse, SavedObjectsUpdateResponse } from '@kbn/core/server'; const mockContext = { @@ -31,6 +34,10 @@ const savedQueryAttributes: SavedQueryAttributes = { }, filters: [], }; +const internalSavedQueryAttributes: InternalSavedQueryAttributes = { + ...savedQueryAttributes, + titleKeyword: 'foo', +}; const savedQueryAttributesBar: SavedQueryAttributes = { title: 'bar', description: 'baz', @@ -90,19 +97,29 @@ describe('saved query route handler context', () => { describe('create', function () { it('should create a saved object for the given attributes', async () => { - const mockResponse: SavedObject = { + const mockResponse: SavedObject = { id: 'foo', type: 'query', - attributes: savedQueryAttributes, + attributes: internalSavedQueryAttributes, references: [], }; + mockSavedObjectsClient.find.mockResolvedValue({ + total: 0, + page: 0, + per_page: 0, + saved_objects: [], + }); mockSavedObjectsClient.create.mockResolvedValue(mockResponse); const response = await context.create(savedQueryAttributes); - expect(mockSavedObjectsClient.create).toHaveBeenCalledWith('query', savedQueryAttributes, { - references: [], - }); + expect(mockSavedObjectsClient.create).toHaveBeenCalledWith( + 'query', + { ...internalSavedQueryAttributes, timefilter: null }, + { + references: [], + } + ); expect(response).toEqual({ id: 'foo', attributes: savedQueryAttributes, @@ -117,17 +134,29 @@ describe('saved query route handler context', () => { query: { match_all: {} }, }, }; - const mockResponse: SavedObject = { + const mockResponse: SavedObject = { id: 'foo', type: 'query', - attributes: savedQueryAttributesWithQueryObject, + attributes: { + ...savedQueryAttributesWithQueryObject, + titleKeyword: 'foo', + }, references: [], }; + mockSavedObjectsClient.find.mockResolvedValue({ + total: 0, + page: 0, + per_page: 0, + saved_objects: [], + }); mockSavedObjectsClient.create.mockResolvedValue(mockResponse); - const { attributes } = await context.create(savedQueryAttributesWithQueryObject); + const result = await context.create(savedQueryAttributesWithQueryObject); - expect(attributes).toEqual(savedQueryAttributesWithQueryObject); + expect(result).toEqual({ + id: 'foo', + attributes: savedQueryAttributesWithQueryObject, + }); }); it('should optionally accept filters and timefilters in object format', async () => { @@ -136,12 +165,21 @@ describe('saved query route handler context', () => { filters: savedQueryAttributesWithFilters.filters, timefilter: savedQueryAttributesWithFilters.timefilter, }; - const mockResponse: SavedObject = { + const mockResponse: SavedObject = { id: 'foo', type: 'query', - attributes: serializedSavedQueryAttributesWithFilters, + attributes: { + ...serializedSavedQueryAttributesWithFilters, + titleKeyword: 'foo', + }, references: [], }; + mockSavedObjectsClient.find.mockResolvedValue({ + total: 0, + page: 0, + per_page: 0, + saved_objects: [], + }); mockSavedObjectsClient.create.mockResolvedValue(mockResponse); await context.create(savedQueryAttributesWithFilters); @@ -154,6 +192,12 @@ describe('saved query route handler context', () => { }); it('should throw an error when saved objects client returns error', async () => { + mockSavedObjectsClient.find.mockResolvedValue({ + total: 0, + page: 0, + per_page: 0, + saved_objects: [], + }); mockSavedObjectsClient.create.mockResolvedValue({ error: { error: '123', @@ -169,19 +213,25 @@ describe('saved query route handler context', () => { it('should throw an error if the saved query does not have a title', async () => { const response = context.create({ ...savedQueryAttributes, title: '' }); expect(response).rejects.toMatchInlineSnapshot( - `[Error: Cannot create saved query without a title]` + `[Error: Cannot create query without a title]` ); }); }); describe('update', function () { it('should update a saved object for the given attributes', async () => { - const mockResponse: SavedObject = { + const mockResponse: SavedObject = { id: 'foo', type: 'query', - attributes: savedQueryAttributes, + attributes: internalSavedQueryAttributes, references: [], }; + mockSavedObjectsClient.find.mockResolvedValue({ + total: 0, + page: 0, + per_page: 0, + saved_objects: [], + }); mockSavedObjectsClient.update.mockResolvedValue(mockResponse); const response = await context.update('foo', savedQueryAttributes); @@ -189,7 +239,7 @@ describe('saved query route handler context', () => { expect(mockSavedObjectsClient.update).toHaveBeenCalledWith( 'query', 'foo', - savedQueryAttributes, + { ...internalSavedQueryAttributes, timefilter: null }, { references: [], } @@ -201,6 +251,12 @@ describe('saved query route handler context', () => { }); it('should throw an error when saved objects client returns error', async () => { + mockSavedObjectsClient.find.mockResolvedValue({ + total: 0, + page: 0, + per_page: 0, + saved_objects: [], + }); mockSavedObjectsClient.update.mockResolvedValue({ error: { error: '123', @@ -216,7 +272,7 @@ describe('saved query route handler context', () => { it('should throw an error if the saved query does not have a title', async () => { const response = context.create({ ...savedQueryAttributes, title: '' }); expect(response).rejects.toMatchInlineSnapshot( - `[Error: Cannot create saved query without a title]` + `[Error: Cannot create query without a title]` ); }); }); @@ -241,6 +297,13 @@ describe('saved query route handler context', () => { const response = await context.find(); + expect(mockSavedObjectsClient.find).toHaveBeenCalledWith({ + type: 'query', + page: 1, + perPage: 50, + sortField: 'titleKeyword', + sortOrder: 'asc', + }); expect(response.savedQueries).toEqual([{ id: 'foo', attributes: savedQueryAttributes }]); }); @@ -271,13 +334,15 @@ describe('saved query route handler context', () => { }; mockSavedObjectsClient.find.mockResolvedValue(mockResponse); - const response = await context.find({ search: 'foo' }); + const response = await context.find({ search: 'Foo < And > Bar' }); expect(mockSavedObjectsClient.find).toHaveBeenCalledWith({ + type: 'query', page: 1, perPage: 50, - search: 'foo', - type: 'query', + filter: 'query.attributes.title:(*Foo AND \\And AND Bar*)', + sortField: 'titleKeyword', + sortOrder: 'asc', }); expect(response.savedQueries).toEqual([{ id: 'foo', attributes: savedQueryAttributes }]); }); @@ -360,7 +425,8 @@ describe('saved query route handler context', () => { expect(mockSavedObjectsClient.find).toHaveBeenCalledWith({ page: 1, perPage: 2, - search: '', + sortField: 'titleKeyword', + sortOrder: 'asc', type: 'query', }); expect(response.savedQueries).toEqual( @@ -378,7 +444,6 @@ describe('saved query route handler context', () => { attributes: { description: 'baz', query: { language: 'kuery', query: 'response:200' }, - filters: [], title: 'bar', }, id: 'bar', @@ -529,7 +594,7 @@ describe('saved query route handler context', () => { }); const response = await context.get('food'); - expect(response.attributes.filters[0].meta.index).toBe('my-new-index'); + expect(response.attributes.filters?.[0].meta.index).toBe('my-new-index'); }); it('should throw if conflict', async () => { @@ -568,6 +633,11 @@ describe('saved query route handler context', () => { const response = await context.count(); + expect(mockSavedObjectsClient.find).toHaveBeenCalledWith({ + type: 'query', + page: 0, + perPage: 0, + }); expect(response).toEqual(1); }); }); diff --git a/src/plugins/data/server/query/route_handler_context.ts b/src/plugins/data/server/query/route_handler_context.ts index fcca59ece59fd..7971ae5104a41 100644 --- a/src/plugins/data/server/query/route_handler_context.ts +++ b/src/plugins/data/server/query/route_handler_context.ts @@ -6,17 +6,51 @@ * Side Public License, v 1. */ -import { CustomRequestHandlerContext, RequestHandlerContext, SavedObject } from '@kbn/core/server'; -import { isFilters, isOfQueryType } from '@kbn/es-query'; +import { badRequest, internal, conflict } from '@hapi/boom'; +import type { + CustomRequestHandlerContext, + RequestHandlerContext, + SavedObject, +} from '@kbn/core/server'; +import { escapeKuery, escapeQuotes, isFilters, isOfQueryType } from '@kbn/es-query'; +import { omit } from 'lodash'; import { isQuery, SavedQueryAttributes } from '../../common'; import { extract, inject } from '../../common/query/filters/persistable_state'; +import type { SavedQueryRestResponse } from './route_types'; + +export interface InternalSavedQueryAttributes + extends Omit { + titleKeyword: string; + filters?: SavedQueryAttributes['filters'] | null; + timefilter?: SavedQueryAttributes['timefilter'] | null; +} function injectReferences({ id, - attributes, + attributes: internalAttributes, namespaces, references, -}: Pick, 'id' | 'attributes' | 'namespaces' | 'references'>) { +}: Pick< + SavedObject, + 'id' | 'attributes' | 'namespaces' | 'references' +>) { + const attributes: SavedQueryAttributes = omit( + internalAttributes, + 'titleKeyword', + 'filters', + 'timefilter' + ); + + // filters or timefilter can be null if previously removed in an update, + // which isn't valid for the client model, so we conditionally add them + if (internalAttributes.filters) { + attributes.filters = inject(internalAttributes.filters, references); + } + + if (internalAttributes.timefilter) { + attributes.timefilter = internalAttributes.timefilter; + } + const { query } = attributes; if (isOfQueryType(query) && typeof query.query === 'string') { try { @@ -26,18 +60,21 @@ function injectReferences({ // Just keep it as a string } } - const filters = inject(attributes.filters ?? [], references); - return { id, attributes: { ...attributes, filters }, namespaces }; + + return { id, attributes, namespaces }; } function extractReferences({ title, description, query, - filters = [], + filters, timefilter, }: SavedQueryAttributes) { - const { state: extractedFilters, references } = extract(filters); + const { state: extractedFilters, references } = filters + ? extract(filters) + : { state: undefined, references: [] }; + const isOfQueryTypeQuery = isOfQueryType(query); let queryString = ''; if (isOfQueryTypeQuery) { @@ -48,15 +85,19 @@ function extractReferences({ } } - const attributes: SavedQueryAttributes = { + const attributes: InternalSavedQueryAttributes = { title: title.trim(), + titleKeyword: title.trim(), description: description.trim(), query: { ...query, ...(queryString && { query: queryString }), }, - filters: extractedFilters, - ...(timefilter && { timefilter }), + // Pass null instead of undefined for filters and timefilter + // to ensure they are removed from the saved object on update + // since the saved objects client ignores undefined values + filters: extractedFilters ?? null, + timefilter: timefilter ?? null, }; return { attributes, references }; @@ -64,109 +105,134 @@ function extractReferences({ function verifySavedQuery({ title, query, filters = [] }: SavedQueryAttributes) { if (!isQuery(query)) { - throw new Error(`Invalid query: ${query}`); + throw badRequest(`Invalid query: ${JSON.stringify(query, null, 2)}`); } if (!isFilters(filters)) { - throw new Error(`Invalid filters: ${filters}`); + throw badRequest(`Invalid filters: ${JSON.stringify(filters, null, 2)}`); } if (!title.trim().length) { - throw new Error('Cannot create saved query without a title'); + throw badRequest('Cannot create query without a title'); } } export async function registerSavedQueryRouteHandlerContext(context: RequestHandlerContext) { const soClient = (await context.core).savedObjects.client; - const createSavedQuery = async (attrs: SavedQueryAttributes) => { + const isDuplicateTitle = async ({ title, id }: { title: string; id?: string }) => { + const preparedTitle = title.trim(); + const { saved_objects: savedQueries } = await soClient.find({ + type: 'query', + page: 1, + perPage: 1, + filter: `query.attributes.titleKeyword:"${escapeQuotes(preparedTitle)}"`, + }); + const existingQuery = savedQueries[0]; + + return Boolean( + existingQuery && + existingQuery.attributes.titleKeyword === preparedTitle && + (!id || existingQuery.id !== id) + ); + }; + + const validateSavedQueryTitle = async (title: string, id?: string) => { + if (await isDuplicateTitle({ title, id })) { + throw badRequest(`Query with title "${title.trim()}" already exists`); + } + }; + + const createSavedQuery = async (attrs: SavedQueryAttributes): Promise => { verifySavedQuery(attrs); - const { attributes, references } = extractReferences(attrs); + await validateSavedQueryTitle(attrs.title); - const savedObject = await soClient.create('query', attributes, { + const { attributes, references } = extractReferences(attrs); + const savedObject = await soClient.create('query', attributes, { references, }); // TODO: Handle properly - if (savedObject.error) throw new Error(savedObject.error.message); + if (savedObject.error) throw internal(savedObject.error.message); return injectReferences(savedObject); }; - const updateSavedQuery = async (id: string, attrs: SavedQueryAttributes) => { + const updateSavedQuery = async ( + id: string, + attrs: SavedQueryAttributes + ): Promise => { verifySavedQuery(attrs); - const { attributes, references } = extractReferences(attrs); + await validateSavedQueryTitle(attrs.title, id); - const savedObject = await soClient.update('query', id, attributes, { - references, - }); + const { attributes, references } = extractReferences(attrs); + const savedObject = await soClient.update( + 'query', + id, + attributes, + { + references, + } + ); // TODO: Handle properly - if (savedObject.error) throw new Error(savedObject.error.message); + if (savedObject.error) throw internal(savedObject.error.message); return injectReferences({ id, attributes, references }); }; - const getSavedQuery = async (id: string) => { - const { saved_object: savedObject, outcome } = await soClient.resolve( - 'query', - id - ); + const getSavedQuery = async (id: string): Promise => { + const { saved_object: savedObject, outcome } = + await soClient.resolve('query', id); if (outcome === 'conflict') { - throw new Error(`Multiple saved queries found with ID: ${id} (legacy URL alias conflict)`); + throw conflict(`Multiple saved queries found with ID: ${id} (legacy URL alias conflict)`); } else if (savedObject.error) { - throw new Error(savedObject.error.message); + throw internal(savedObject.error.message); } return injectReferences(savedObject); }; const getSavedQueriesCount = async () => { - const { total } = await soClient.find({ + const { total } = await soClient.find({ type: 'query', + page: 0, + perPage: 0, }); return total; }; - const findSavedQueries = async ({ page = 1, perPage = 50, search = '' } = {}) => { - const { total, saved_objects: savedObjects } = await soClient.find({ - type: 'query', - page, - perPage, - search, - }); + const findSavedQueries = async ({ page = 1, perPage = 50, search = '' } = {}): Promise<{ + total: number; + savedQueries: SavedQueryRestResponse[]; + }> => { + const cleanedSearch = search.replace(/\W/g, ' ').trim(); + const preparedSearch = escapeKuery(cleanedSearch).split(/\s+/).join(' AND '); + const { total, saved_objects: savedObjects } = + await soClient.find({ + type: 'query', + page, + perPage, + filter: preparedSearch.length ? `query.attributes.title:(*${preparedSearch}*)` : undefined, + sortField: 'titleKeyword', + sortOrder: 'asc', + }); const savedQueries = savedObjects.map(injectReferences); return { total, savedQueries }; }; - const getAllSavedQueries = async () => { - const finder = soClient.createPointInTimeFinder({ - type: 'query', - perPage: 100, - }); - - const savedObjects: Array> = []; - for await (const response of finder.find()) { - savedObjects.push(...(response.saved_objects ?? [])); - } - await finder.close(); - - const savedQueries = savedObjects.map(injectReferences); - return { total: savedQueries.length, savedQueries }; - }; - const deleteSavedQuery = async (id: string) => { return await soClient.delete('query', id, { force: true }); }; return { + isDuplicateTitle, create: createSavedQuery, update: updateSavedQuery, get: getSavedQuery, count: getSavedQueriesCount, find: findSavedQueries, - getAll: getAllSavedQueries, delete: deleteSavedQuery, }; } diff --git a/src/plugins/data/server/query/route_types.ts b/src/plugins/data/server/query/route_types.ts index 656d52dad9fa7..535eaeecbeefe 100644 --- a/src/plugins/data/server/query/route_types.ts +++ b/src/plugins/data/server/query/route_types.ts @@ -120,7 +120,7 @@ type SavedQueryTimeFilterRestResponse = TimeRangeRestResponse & { export interface SavedQueryRestResponse { id: string; attributes: { - filters: FilterRestResponse[]; + filters?: FilterRestResponse[]; title: string; description: string; query: QueryRestResponse; diff --git a/src/plugins/data/server/query/routes.ts b/src/plugins/data/server/query/routes.ts index 5ac18df37d544..5bed196b6373b 100644 --- a/src/plugins/data/server/query/routes.ts +++ b/src/plugins/data/server/query/routes.ts @@ -10,7 +10,6 @@ import { schema } from '@kbn/config-schema'; import { CoreSetup } from '@kbn/core/server'; import { reportServerError } from '@kbn/kibana-utils-plugin/server'; import { SavedQueryRouteHandlerContext } from './route_handler_context'; -import { SavedQueryRestResponse } from './route_types'; import { SAVED_QUERY_BASE_URL } from '../../common/constants'; const SAVED_QUERY_ID_CONFIG = schema.object({ @@ -39,6 +38,37 @@ const version = '1'; export function registerSavedQueryRoutes({ http }: CoreSetup): void { const router = http.createRouter(); + router.versioned.post({ path: `${SAVED_QUERY_BASE_URL}/_is_duplicate_title`, access }).addVersion( + { + version, + validate: { + request: { + body: schema.object({ + title: schema.string(), + id: schema.maybe(schema.string()), + }), + }, + response: { + 200: { + body: schema.object({ + isDuplicate: schema.boolean(), + }), + }, + }, + }, + }, + async (context, request, response) => { + try { + const savedQuery = await context.savedQuery; + const isDuplicate = await savedQuery.isDuplicateTitle(request.body); + return response.ok({ body: { isDuplicate } }); + } catch (e) { + const err = e.output?.payload ?? e; + return reportServerError(response, err); + } + } + ); + router.versioned.post({ path: `${SAVED_QUERY_BASE_URL}/_create`, access }).addVersion( { version, @@ -56,7 +86,7 @@ export function registerSavedQueryRoutes({ http }: CoreSetup): void { async (context, request, response) => { try { const savedQuery = await context.savedQuery; - const body: SavedQueryRestResponse = await savedQuery.create(request.body); + const body = await savedQuery.create(request.body); return response.ok({ body }); } catch (e) { const err = e.output?.payload ?? e; @@ -84,7 +114,7 @@ export function registerSavedQueryRoutes({ http }: CoreSetup): void { const { id } = request.params; try { const savedQuery = await context.savedQuery; - const body: SavedQueryRestResponse = await savedQuery.update(id, request.body); + const body = await savedQuery.update(id, request.body); return response.ok({ body }); } catch (e) { const err = e.output?.payload ?? e; @@ -111,7 +141,7 @@ export function registerSavedQueryRoutes({ http }: CoreSetup): void { const { id } = request.params; try { const savedQuery = await context.savedQuery; - const body: SavedQueryRestResponse = await savedQuery.get(id); + const body = await savedQuery.get(id); return response.ok({ body }); } catch (e) { const err = e.output?.payload ?? e; @@ -168,36 +198,7 @@ export function registerSavedQueryRoutes({ http }: CoreSetup): void { async (context, request, response) => { try { const savedQuery = await context.savedQuery; - const body: { total: number; savedQueries: SavedQueryRestResponse[] } = - await savedQuery.find(request.body); - return response.ok({ body }); - } catch (e) { - const err = e.output?.payload ?? e; - return reportServerError(response, err); - } - } - ); - - router.versioned.post({ path: `${SAVED_QUERY_BASE_URL}/_all`, access }).addVersion( - { - version, - validate: { - request: {}, - response: { - 200: { - body: schema.object({ - total: schema.number(), - savedQueries: schema.arrayOf(savedQueryResponseSchema), - }), - }, - }, - }, - }, - async (context, request, response) => { - try { - const savedQuery = await context.savedQuery; - const body: { total: number; savedQueries: SavedQueryRestResponse[] } = - await savedQuery.getAll(); + const body = await savedQuery.find(request.body); return response.ok({ body }); } catch (e) { const err = e.output?.payload ?? e; diff --git a/src/plugins/data/server/saved_objects/query.test.ts b/src/plugins/data/server/saved_objects/query.test.ts new file mode 100644 index 0000000000000..0968413c27d83 --- /dev/null +++ b/src/plugins/data/server/saved_objects/query.test.ts @@ -0,0 +1,60 @@ +/* + * 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 { + createModelVersionTestMigrator, + type ModelVersionTestMigrator, +} from '@kbn/core-test-helpers-model-versions'; +import { querySavedObjectType } from './query'; + +describe('saved query model version transformations', () => { + let migrator: ModelVersionTestMigrator; + + beforeEach(() => { + migrator = createModelVersionTestMigrator({ type: querySavedObjectType }); + }); + + describe('model version 2', () => { + const query = { + id: 'some-id', + type: 'query', + attributes: { + title: 'Some Title', + description: 'some description', + query: { language: 'kuery', query: 'some query' }, + }, + references: [], + }; + + it('should properly backfill the titleKeyword field when converting from v1 to v2', () => { + const migrated = migrator.migrate({ + document: query, + fromVersion: 1, + toVersion: 2, + }); + + expect(migrated.attributes).toEqual({ + ...query.attributes, + titleKeyword: query.attributes.title, + }); + }); + + it('should properly remove the titleKeyword field when converting from v2 to v1', () => { + const migrated = migrator.migrate({ + document: { + ...query, + attributes: { ...query.attributes, titleKeyword: query.attributes.title }, + }, + fromVersion: 2, + toVersion: 1, + }); + + expect(migrated.attributes).toEqual(query.attributes); + }); + }); +}); diff --git a/src/plugins/data/server/saved_objects/query.ts b/src/plugins/data/server/saved_objects/query.ts index 59ff2483fbefb..4a96b4e2777aa 100644 --- a/src/plugins/data/server/saved_objects/query.ts +++ b/src/plugins/data/server/saved_objects/query.ts @@ -9,7 +9,11 @@ import { ANALYTICS_SAVED_OBJECT_INDEX } from '@kbn/core-saved-objects-server'; import { SavedObjectsType } from '@kbn/core/server'; import { savedQueryMigrations } from './migrations/query'; -import { SCHEMA_QUERY_V8_8_0 } from './schemas/query'; +import { + SCHEMA_QUERY_V8_8_0, + SCHEMA_QUERY_MODEL_VERSION_1, + SCHEMA_QUERY_MODEL_VERSION_2, +} from './schemas/query'; export const querySavedObjectType: SavedObjectsType = { name: 'query', @@ -31,10 +35,42 @@ export const querySavedObjectType: SavedObjectsType = { }; }, }, + modelVersions: { + 1: { + changes: [], + schemas: { + forwardCompatibility: SCHEMA_QUERY_MODEL_VERSION_1.extends({}, { unknowns: 'ignore' }), + create: SCHEMA_QUERY_MODEL_VERSION_1, + }, + }, + 2: { + changes: [ + { + type: 'mappings_addition', + addedMappings: { + titleKeyword: { type: 'keyword' }, + }, + }, + { + type: 'data_backfill', + backfillFn: (doc) => { + return { + attributes: { ...doc.attributes, titleKeyword: doc.attributes.title }, + }; + }, + }, + ], + schemas: { + forwardCompatibility: SCHEMA_QUERY_MODEL_VERSION_2.extends({}, { unknowns: 'ignore' }), + create: SCHEMA_QUERY_MODEL_VERSION_2, + }, + }, + }, mappings: { dynamic: false, properties: { title: { type: 'text' }, + titleKeyword: { type: 'keyword' }, description: { type: 'text' }, }, }, diff --git a/src/plugins/data/server/saved_objects/schemas/query.ts b/src/plugins/data/server/saved_objects/schemas/query.ts index c460a06b9727a..ae6e50340510f 100644 --- a/src/plugins/data/server/saved_objects/schemas/query.ts +++ b/src/plugins/data/server/saved_objects/schemas/query.ts @@ -8,25 +8,37 @@ import { schema } from '@kbn/config-schema'; +const FILTERS_SCHEMA = schema.arrayOf(schema.object({}, { unknowns: 'allow' })); + +const TIME_FILTER_SCHEMA = schema.object({ + from: schema.string(), + to: schema.string(), + refreshInterval: schema.maybe( + schema.object({ + value: schema.number(), + pause: schema.boolean(), + }) + ), +}); + // As per `SavedQueryAttributes` -export const SCHEMA_QUERY_V8_8_0 = schema.object({ +export const SCHEMA_QUERY_BASE = schema.object({ title: schema.string(), description: schema.string({ defaultValue: '' }), query: schema.object({ language: schema.string(), query: schema.oneOf([schema.string(), schema.object({}, { unknowns: 'allow' })]), }), - filters: schema.maybe(schema.arrayOf(schema.object({}, { unknowns: 'allow' }))), - timefilter: schema.maybe( - schema.object({ - from: schema.string(), - to: schema.string(), - refreshInterval: schema.maybe( - schema.object({ - value: schema.number(), - pause: schema.boolean(), - }) - ), - }) - ), + filters: schema.maybe(FILTERS_SCHEMA), + timefilter: schema.maybe(TIME_FILTER_SCHEMA), +}); + +export const SCHEMA_QUERY_V8_8_0 = SCHEMA_QUERY_BASE; + +export const SCHEMA_QUERY_MODEL_VERSION_1 = SCHEMA_QUERY_BASE; + +export const SCHEMA_QUERY_MODEL_VERSION_2 = SCHEMA_QUERY_BASE.extends({ + titleKeyword: schema.string(), + filters: schema.maybe(schema.nullable(FILTERS_SCHEMA)), + timefilter: schema.maybe(schema.nullable(TIME_FILTER_SCHEMA)), }); diff --git a/src/plugins/data/tsconfig.json b/src/plugins/data/tsconfig.json index 53cdd2e1f5d9f..74fc83691ec53 100644 --- a/src/plugins/data/tsconfig.json +++ b/src/plugins/data/tsconfig.json @@ -52,7 +52,8 @@ "@kbn/shared-ux-link-redirect-app", "@kbn/bfetch-error", "@kbn/es-types", - "@kbn/code-editor" + "@kbn/code-editor", + "@kbn/core-test-helpers-model-versions" ], "exclude": [ "target/**/*", diff --git a/src/plugins/unified_search/public/query_string_input/add_filter_popover.styles.ts b/src/plugins/unified_search/public/query_string_input/add_filter_popover.styles.ts deleted file mode 100644 index 21e3d6b649175..0000000000000 --- a/src/plugins/unified_search/public/query_string_input/add_filter_popover.styles.ts +++ /dev/null @@ -1,20 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { euiShadowMedium, UseEuiTheme } from '@elastic/eui'; -import { css } from '@emotion/react'; - -/** @todo important style should be remove after fixing elastic/eui/issues/6314. */ -export const popoverDragAndDropCss = (euiTheme: UseEuiTheme) => - css` - // Always needed for popover with drag & drop in them - transform: none !important; - transition: none !important; - filter: none !important; - ${euiShadowMedium(euiTheme)} - `; diff --git a/src/plugins/unified_search/public/query_string_input/add_filter_popover.tsx b/src/plugins/unified_search/public/query_string_input/add_filter_popover.tsx index 48cf1a0f481e1..7954abed26c85 100644 --- a/src/plugins/unified_search/public/query_string_input/add_filter_popover.tsx +++ b/src/plugins/unified_search/public/query_string_input/add_filter_popover.tsx @@ -13,13 +13,11 @@ import { EuiPopover, EuiButtonIconProps, EuiToolTip, - useEuiTheme, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { Filter } from '@kbn/es-query'; import type { DataView } from '@kbn/data-views-plugin/public'; import { FilterEditorWrapper } from './filter_editor_wrapper'; -import { popoverDragAndDropCss } from './add_filter_popover.styles'; import { withCloseFilterEditorConfirmModal, WithCloseFilterEditorConfirmModalProps, @@ -57,7 +55,6 @@ const AddFilterPopoverComponent = React.memo(function AddFilterPopover({ onLocalFilterCreate, suggestionsAbstraction, }: AddFilterPopoverProps) { - const euiTheme = useEuiTheme(); const [showAddFilterPopover, setShowAddFilterPopover] = useState(false); const button = ( @@ -91,11 +88,11 @@ const AddFilterPopoverComponent = React.memo(function AddFilterPopover({ panelPaddingSize="none" panelProps={{ 'data-test-subj': 'addFilterPopover', - css: popoverDragAndDropCss(euiTheme), }} initialFocus=".filterEditor__hiddenItem" ownFocus repositionOnScroll + hasDragDrop > ; + title: string; + append?: ReactNode; +}) => { + const { euiTheme } = useEuiTheme(); + const titleRef = useRef(null); + + const onTitleClick = useCallback( + () => queryBarMenuRef.current?.showPanel(QueryBarMenuPanel.main, 'previous'), + [queryBarMenuRef] + ); + + const onTitleKeyDown = useCallback( + (event: KeyboardEvent) => { + if (event.key !== keys.ARROW_LEFT) { + return; + } + + event.preventDefault(); + event.stopPropagation(); + queryBarMenuRef.current?.showPreviousPanel(); + queryBarMenuRef.current?.onUseKeyboardToNavigate(); + }, + [queryBarMenuRef] + ); + + useEffectOnce(() => { + const panel = titleRef.current?.closest('.euiContextMenuPanel'); + const focus = () => titleRef.current?.focus(); + + panel?.addEventListener('animationend', focus, { once: true }); + + return () => panel?.removeEventListener('animationend', focus); + }); + + return ( + + + + + {title} + + + + {append && ( + + {append} + + )} + + ); +}; diff --git a/src/plugins/unified_search/public/query_string_input/query_bar_menu.test.tsx b/src/plugins/unified_search/public/query_string_input/query_bar_menu.test.tsx index bdab607b2030c..85956745dff98 100644 --- a/src/plugins/unified_search/public/query_string_input/query_bar_menu.test.tsx +++ b/src/plugins/unified_search/public/query_string_input/query_bar_menu.test.tsx @@ -119,6 +119,7 @@ describe('Querybar Menu component', () => { ], }), }, + queryBarMenuRef: React.createRef(), }; }); it('should not render the popover if the openQueryBarMenu prop is false', async () => { diff --git a/src/plugins/unified_search/public/query_string_input/query_bar_menu.tsx b/src/plugins/unified_search/public/query_string_input/query_bar_menu.tsx index 14b11737e8844..ef16d9b3e48d8 100644 --- a/src/plugins/unified_search/public/query_string_input/query_bar_menu.tsx +++ b/src/plugins/unified_search/public/query_string_input/query_bar_menu.tsx @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import React, { useState, useEffect, useCallback } from 'react'; +import React, { useState, useEffect, useCallback, RefObject } from 'react'; import { EuiButtonIcon, EuiContextMenu, @@ -15,15 +15,22 @@ import { useGeneratedHtmlId, EuiButtonIconProps, EuiToolTip, - useEuiTheme, } from '@elastic/eui'; +import { + EuiContextMenuClass, + EuiContextMenuPanelId, +} from '@elastic/eui/src/components/context_menu/context_menu'; import { i18n } from '@kbn/i18n'; import type { Filter, Query, TimeRange } from '@kbn/es-query'; import type { DataView } from '@kbn/data-views-plugin/public'; -import type { SavedQueryService, SavedQuery } from '@kbn/data-plugin/public'; -import { QueryBarMenuPanels, QueryBarMenuPanelsProps } from './query_bar_menu_panels'; +import type { SavedQueryService, SavedQuery, SavedQueryTimeFilter } from '@kbn/data-plugin/public'; +import { euiThemeVars } from '@kbn/ui-theme'; +import { + QueryBarMenuPanel, + useQueryBarMenuPanels, + QueryBarMenuPanelsProps, +} from './query_bar_menu_panels'; import { FilterEditorWrapper } from './filter_editor_wrapper'; -import { popoverDragAndDropCss } from './add_filter_popover.styles'; import { withCloseFilterEditorConfirmModal, WithCloseFilterEditorConfirmModalProps, @@ -51,6 +58,7 @@ export interface QueryBarMenuProps extends WithCloseFilterEditorConfirmModalProp disableQueryLanguageSwitcher?: boolean; dateRangeFrom?: string; dateRangeTo?: string; + timeFilter?: SavedQueryTimeFilter; savedQueryService: SavedQueryService; saveAsNewQueryFormComponent?: JSX.Element; saveFormComponent?: JSX.Element; @@ -71,6 +79,7 @@ export interface QueryBarMenuProps extends WithCloseFilterEditorConfirmModalProp isDisabled?: boolean; suggestionsAbstraction?: SuggestionsAbstraction; renderQueryInputAppend?: () => React.ReactNode; + queryBarMenuRef: RefObject; } function QueryBarMenuComponent({ @@ -79,6 +88,7 @@ function QueryBarMenuComponent({ disableQueryLanguageSwitcher, dateRangeFrom, dateRangeTo, + timeFilter, onQueryChange, onQueryBarSubmit, savedQueryService, @@ -105,13 +115,16 @@ function QueryBarMenuComponent({ onLocalFilterCreate, onLocalFilterUpdate, suggestionsAbstraction, + queryBarMenuRef, }: QueryBarMenuProps) { const [renderedComponent, setRenderedComponent] = useState('menu'); - - const euiTheme = useEuiTheme(); + const [currentPanelId, setCurrentPanelId] = useState( + QueryBarMenuPanel.main + ); useEffect(() => { if (openQueryBarMenu) { + setCurrentPanelId(QueryBarMenuPanel.main); setRenderedComponent('menu'); } }, [openQueryBarMenu]); @@ -141,7 +154,7 @@ function QueryBarMenuComponent({ onClick={onButtonClick} isDisabled={isDisabled} {...buttonProps} - style={{ borderTopRightRadius: 0, borderBottomRightRadius: 0 }} + css={{ borderTopRightRadius: 0, borderBottomRightRadius: 0 }} iconType="filter" aria-label={strings.getFilterSetButtonLabel()} data-test-subj="showQueryBarMenu" @@ -149,22 +162,25 @@ function QueryBarMenuComponent({ ); - const panels = QueryBarMenuPanels({ + const panels = useQueryBarMenuPanels({ filters, savedQuery, language, dateRangeFrom, dateRangeTo, + timeFilter, query, showSaveQuery, showFilterBar, showQueryInput, savedQueryService, + saveFormComponent, saveAsNewQueryFormComponent, manageFilterSetComponent, hiddenPanelOptions, nonKqlMode, disableQueryLanguageSwitcher, + queryBarMenuRef, closePopover: plainClosePopover, onQueryBarSubmit, onFiltersUpdated, @@ -176,21 +192,41 @@ function QueryBarMenuComponent({ const renderComponent = () => { switch (renderedComponent) { case 'menu': - default: - return ( - - ); - case 'saveForm': return ( - {saveFormComponent}]} - /> - ); - case 'saveAsNewForm': - return ( - {saveAsNewQueryFormComponent}]} + setCurrentPanelId(panelId)} + data-test-subj="queryBarMenuPanel" + css={[ + { + // Add width to transition properties to smooth + // the animation when the panel width changes + transitionProperty: 'width, height !important', + // Add a white background to panels since panels + // of different widths can overlap each other + // when transitioning, but the background colour + // ensures the incoming panel always overlays + // the outgoing panel which improves the effect + '.euiContextMenuPanel': { + backgroundColor: euiThemeVars.euiColorEmptyShade, + }, + }, + // Fix the update button underline on hover, and + // the button focus outline being cut off + currentPanelId === QueryBarMenuPanel.main && { + '.euiContextMenuPanel__title': { + ':hover': { + textDecoration: 'none !important', + }, + '.euiContextMenuItem__text': { + overflow: 'visible', + }, + }, + }, + ]} /> ); case 'addFilter': @@ -217,23 +253,19 @@ function QueryBarMenuComponent({ }; return ( - <> - - {renderComponent()} - - + + {renderComponent()} + ); } diff --git a/src/plugins/unified_search/public/query_string_input/query_bar_menu_panels.tsx b/src/plugins/unified_search/public/query_string_input/query_bar_menu_panels.tsx index b55377d618b3d..6ca40656467e5 100644 --- a/src/plugins/unified_search/public/query_string_input/query_bar_menu_panels.tsx +++ b/src/plugins/unified_search/public/query_string_input/query_bar_menu_panels.tsx @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import React, { useState, useRef, useEffect, useCallback } from 'react'; +import React, { useState, useRef, useEffect, RefObject } from 'react'; import { isEqual } from 'lodash'; import { EuiContextMenuPanelDescriptor, @@ -14,6 +14,7 @@ import { EuiFlexGroup, EuiFlexItem, EuiButton, + EuiContextMenuPanelItemDescriptor, } from '@elastic/eui'; import { Filter, @@ -24,6 +25,8 @@ import { toggleFilterNegated, pinFilter, unpinFilter, + compareFilters, + COMPARE_ALL_OPTIONS, } from '@kbn/es-query'; import { i18n } from '@kbn/i18n'; import { METRIC_TYPE } from '@kbn/analytics'; @@ -33,11 +36,14 @@ import { KQL_TELEMETRY_ROUTE_LATEST_VERSION, UI_SETTINGS, } from '@kbn/data-plugin/common'; -import type { SavedQueryService, SavedQuery } from '@kbn/data-plugin/public'; +import type { SavedQueryService, SavedQuery, SavedQueryTimeFilter } from '@kbn/data-plugin/public'; +import { euiThemeVars } from '@kbn/ui-theme'; +import { EuiContextMenuClass } from '@elastic/eui/src/components/context_menu/context_menu'; import type { IUnifiedSearchPluginServices } from '../types'; import { fromUser } from './from_user'; import { QueryLanguageSwitcher } from './language_switcher'; import { FilterPanelOption } from '../types'; +import { PanelTitle } from './panel_title'; const MAP_ITEMS_TO_FILTER_OPTION: Record = { 'filter-sets-pinAllFilters': 'pinFilter', @@ -81,7 +87,7 @@ export const strings = { i18n.translate('unifiedSearch.filter.options.saveFilterSetLabel', { defaultMessage: 'Save query', }), - getClearllFiltersButtonLabel: () => + getClearAllFiltersButtonLabel: () => i18n.translate('unifiedSearch.filter.options.clearllFiltersButtonLabel', { defaultMessage: 'Clear all', }), @@ -136,22 +142,34 @@ export const strings = { }), }; +export enum QueryBarMenuPanel { + main = 'main', + applyToAllFilters = 'applyToAllFilters', + updateCurrentQuery = 'updateCurrentQuery', + saveAsNewQuery = 'saveAsNewQuery', + loadQuery = 'loadQuery', + selectLanguage = 'selectLanguage', +} + export interface QueryBarMenuPanelsProps { filters?: Filter[]; savedQuery?: SavedQuery; language: string; dateRangeFrom?: string; dateRangeTo?: string; + timeFilter?: SavedQueryTimeFilter; query?: Query; showSaveQuery?: boolean; showQueryInput?: boolean; showFilterBar?: boolean; savedQueryService: SavedQueryService; + saveFormComponent?: JSX.Element; saveAsNewQueryFormComponent?: JSX.Element; manageFilterSetComponent?: JSX.Element; hiddenPanelOptions?: FilterPanelOption[]; nonKqlMode?: 'lucene' | 'text'; disableQueryLanguageSwitcher?: boolean; + queryBarMenuRef: RefObject; closePopover: () => void; onQueryBarSubmit: (payload: { dateRange: TimeRange; query?: Query }) => void; onFiltersUpdated?: (filters: Filter[]) => void; @@ -160,22 +178,25 @@ export interface QueryBarMenuPanelsProps { setRenderedComponent: (component: string) => void; } -export function QueryBarMenuPanels({ +export function useQueryBarMenuPanels({ filters, savedQuery, language, dateRangeFrom, dateRangeTo, + timeFilter, query, showSaveQuery, showFilterBar, showQueryInput, savedQueryService, + saveFormComponent, saveAsNewQueryFormComponent, manageFilterSetComponent, hiddenPanelOptions, nonKqlMode, disableQueryLanguageSwitcher = false, + queryBarMenuRef, closePopover, onQueryBarSubmit, onFiltersUpdated, @@ -188,10 +209,17 @@ export function QueryBarMenuPanels({ const reportUiCounter = usageCollection?.reportUiCounter.bind(usageCollection, appName); const cancelPendingListingRequest = useRef<() => void>(() => {}); - const [savedQueries, setSavedQueries] = useState([] as SavedQuery[]); + const [hasSavedQueries, setHasSavedQueries] = useState(false); const [hasFiltersOrQuery, setHasFiltersOrQuery] = useState(false); const [savedQueryHasChanged, setSavedQueryHasChanged] = useState(false); + useEffect(() => { + if (savedQuery) { + cancelPendingListingRequest.current(); + setHasSavedQueries(true); + } + }, [savedQuery]); + useEffect(() => { const fetchSavedQueries = async () => { cancelPendingListingRequest.current(); @@ -200,35 +228,39 @@ export function QueryBarMenuPanels({ requestGotCancelled = true; }; - const { queries: savedQueryItems } = await savedQueryService.findSavedQueries(''); + const queryCount = await savedQueryService.getSavedQueryCount(); if (requestGotCancelled) return; - setSavedQueries(savedQueryItems.reverse().slice(0, 5)); + setHasSavedQueries(queryCount > 0); }; if (showQueryInput && showFilterBar) { fetchSavedQueries(); } - }, [savedQueryService, savedQuery, showQueryInput, showFilterBar]); + }, [savedQueryService, showQueryInput, showFilterBar]); useEffect(() => { if (savedQuery) { - let filtersHaveChanged = filters?.length !== savedQuery.attributes?.filters?.length; - if (filters?.length === savedQuery.attributes?.filters?.length) { - filtersHaveChanged = Boolean( - filters?.some( - (filter, index) => - !isEqual(filter.query, savedQuery.attributes?.filters?.[index]?.query) - ) - ); - } - if (filtersHaveChanged || !isEqual(query, savedQuery?.attributes.query)) { + const filtersHaveChanged = Boolean( + savedQuery?.attributes.filters && + !compareFilters(filters ?? [], savedQuery.attributes.filters, COMPARE_ALL_OPTIONS) + ); + + const timeFilterHasChanged = Boolean( + savedQuery?.attributes.timefilter && !isEqual(timeFilter, savedQuery?.attributes.timefilter) + ); + + if ( + filtersHaveChanged || + timeFilterHasChanged || + !isEqual(query, savedQuery?.attributes.query) + ) { setSavedQueryHasChanged(true); } else { setSavedQueryHasChanged(false); } } - }, [filters, query, savedQuery, savedQuery?.attributes.filters, savedQuery?.attributes.query]); + }, [filters, query, savedQuery, timeFilter]); useEffect(() => { const hasFilters = Boolean(filters && filters.length > 0); @@ -244,10 +276,6 @@ export function QueryBarMenuPanels({ }; }; - const handleSave = useCallback(() => { - setRenderedComponent('saveForm'); - }, [setRenderedComponent]); - const onEnableAll = () => { reportUiCounter?.(METRIC_TYPE.CLICK, `filter:enable_all`); const enabledFilters = filters?.map(enableFilter); @@ -320,7 +348,7 @@ export function QueryBarMenuPanels({ const luceneLabel = strings.getLuceneLanguageName(); const kqlLabel = strings.getKqlLanguageName(); - const filtersRelatedPanels = [ + const filtersRelatedPanels: EuiContextMenuPanelItemDescriptor[] = [ { name: strings.getOptionsAddFilterButtonLabel(), icon: 'plus', @@ -331,35 +359,34 @@ export function QueryBarMenuPanels({ { name: strings.getOptionsApplyAllFiltersButtonLabel(), icon: 'filter', - panel: 2, + panel: QueryBarMenuPanel.applyToAllFilters, disabled: !Boolean(filters && filters.length > 0), 'data-test-subj': 'filter-sets-applyToAllFilters', }, ]; - const queryAndFiltersRelatedPanels = [ + const queryAndFiltersRelatedPanels: EuiContextMenuPanelItemDescriptor[] = [ { name: savedQuery ? strings.getLoadOtherFilterSetLabel() : strings.getLoadCurrentFilterSetLabel(), - panel: 4, - width: 350, + panel: QueryBarMenuPanel.loadQuery, icon: 'filter', 'data-test-subj': 'saved-query-management-load-button', - disabled: !savedQueries.length, + disabled: !hasSavedQueries, }, { name: savedQuery ? strings.getSaveAsNewFilterSetLabel() : strings.getSaveFilterSetLabel(), icon: 'save', disabled: !Boolean(showSaveQuery) || !hasFiltersOrQuery || (savedQuery && !savedQueryHasChanged), - panel: 1, + panel: QueryBarMenuPanel.saveAsNewQuery, 'data-test-subj': 'saved-query-management-save-button', }, { isSeparator: true }, ]; - const items = []; + const items: EuiContextMenuPanelItemDescriptor[] = []; // apply to all actions are only shown when there are filters if (showFilterBar) { items.push(...filtersRelatedPanels); @@ -368,7 +395,7 @@ export function QueryBarMenuPanels({ if (showFilterBar || showQueryInput) { items.push( { - name: strings.getClearllFiltersButtonLabel(), + name: strings.getClearAllFiltersButtonLabel(), disabled: !hasFiltersOrQuery && !Boolean(savedQuery), icon: 'cross', 'data-test-subj': 'filter-sets-removeAllFilters', @@ -394,14 +421,14 @@ export function QueryBarMenuPanels({ if (showQueryInput && !disableQueryLanguageSwitcher) { items.push({ name: `Language: ${language === 'kuery' ? kqlLabel : luceneLabel}`, - panel: 3, + panel: QueryBarMenuPanel.selectLanguage, 'data-test-subj': 'switchQueryLanguageButton', }); } - let panels = [ + let panels: EuiContextMenuPanelDescriptor[] = [ { - id: 0, + id: QueryBarMenuPanel.main, title: savedQuery?.attributes.title ? ( <> @@ -419,7 +446,12 @@ export function QueryBarMenuPanels({ { + queryBarMenuRef.current?.showPanel( + QueryBarMenuPanel.updateCurrentQuery, + 'next' + ); + }} aria-label={strings.getSavedQueryPopoverSaveChangesButtonAriaLabel( savedQuery?.attributes.title )} @@ -435,13 +467,7 @@ export function QueryBarMenuPanels({ items, }, { - id: 1, - title: strings.getSaveCurrentFilterSetLabel(), - disabled: !Boolean(showSaveQuery), - content:
{saveAsNewQueryFormComponent}
, - }, - { - id: 2, + id: QueryBarMenuPanel.applyToAllFilters, initialFocusedItemIndex: 1, title: strings.getApplyAllFiltersButtonLabel(), items: [ @@ -493,7 +519,29 @@ export function QueryBarMenuPanels({ ], }, { - id: 3, + id: QueryBarMenuPanel.updateCurrentQuery, + content: ( + <> + +
{saveFormComponent}
+ + ), + }, + { + id: QueryBarMenuPanel.saveAsNewQuery, + title: strings.getSaveCurrentFilterSetLabel(), + content:
{saveAsNewQueryFormComponent}
, + }, + { + id: QueryBarMenuPanel.loadQuery, + width: 400, + content:
{manageFilterSetComponent}
, + }, + { + id: QueryBarMenuPanel.selectLanguage, title: strings.getFilterLanguageLabel(), content: ( ), }, - { - id: 4, - title: strings.getLoadCurrentFilterSetLabel(), - width: 400, - content:
{manageFilterSetComponent}
, - }, - ] as EuiContextMenuPanelDescriptor[]; + ]; if (hiddenPanelOptions && hiddenPanelOptions.length > 0) { panels = panels.map((panel) => ({ diff --git a/src/plugins/unified_search/public/saved_query_form/save_query_form.tsx b/src/plugins/unified_search/public/saved_query_form/save_query_form.tsx index 6f70944bae972..e9d7a548fadaa 100644 --- a/src/plugins/unified_search/public/saved_query_form/save_query_form.tsx +++ b/src/plugins/unified_search/public/saved_query_form/save_query_form.tsx @@ -6,10 +6,10 @@ * Side Public License, v 1. */ -import React, { useEffect, useState, useCallback } from 'react'; +import React, { useState, useCallback } from 'react'; import { EuiButton, EuiForm, EuiFormRow, EuiFieldText, EuiSwitch } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import { sortBy, isEqual } from 'lodash'; +import { isEqual } from 'lodash'; import { SavedQuery, SavedQueryService } from '@kbn/data-plugin/public'; interface Props { @@ -38,22 +38,22 @@ export function SaveQueryForm({ showTimeFilterOption = true, }: Props) { const [title, setTitle] = useState(savedQuery?.attributes.title ?? ''); - const [savedQueries, setSavedQueries] = useState([]); const [shouldIncludeFilters, setShouldIncludeFilters] = useState( - Boolean(savedQuery?.attributes.filters ?? true) + Boolean(savedQuery ? savedQuery.attributes.filters : true) ); // Defaults to false because saved queries are meant to be as portable as possible and loading // a saved query with a time filter will override whatever the current value of the global timepicker // is. We expect this option to be used rarely and only when the user knows they want this behavior. const [shouldIncludeTimefilter, setIncludeTimefilter] = useState( - Boolean(savedQuery?.attributes.timefilter ?? false) + Boolean(savedQuery ? savedQuery.attributes.timefilter : false) ); const [formErrors, setFormErrors] = useState([]); + const [saveIsDisabled, setSaveIsDisabled] = useState(false); const titleConflictErrorText = i18n.translate( 'unifiedSearch.search.searchBar.savedQueryForm.titleConflictText', { - defaultMessage: 'Name conflicts with an existing query', + defaultMessage: 'Name conflicts with an existing query.', } ); @@ -64,47 +64,48 @@ export function SaveQueryForm({ } ); - useEffect(() => { - const fetchQueries = async () => { - const allSavedQueries = await savedQueryService.getAllSavedQueries(); - const sortedAllSavedQueries = sortBy(allSavedQueries, 'attributes.title'); - setSavedQueries(sortedAllSavedQueries); - }; - fetchQueries(); - }, [savedQueryService]); - - const validate = useCallback(() => { + const validate = useCallback(async () => { const errors = []; - if ( - !!savedQueries.find( - (existingSavedQuery) => !savedQuery && existingSavedQuery.attributes.title === title - ) - ) { - errors.push(titleConflictErrorText); - } if (!title) { errors.push(titleExistsErrorText); } + if (await savedQueryService.isDuplicateTitle(title, savedQuery?.id)) { + errors.push(titleConflictErrorText); + } + if (!isEqual(errors, formErrors)) { setFormErrors(errors); return false; } return !formErrors.length; - }, [savedQueries, formErrors, title, savedQuery, titleConflictErrorText, titleExistsErrorText]); - - const onClickSave = useCallback(() => { - if (validate()) { - onSave({ - id: savedQuery?.id, - title, - description: '', - shouldIncludeFilters, - shouldIncludeTimefilter, - }); - onClose(); + }, [ + formErrors, + savedQuery, + savedQueryService, + title, + titleConflictErrorText, + titleExistsErrorText, + ]); + + const onClickSave = useCallback(async () => { + try { + setSaveIsDisabled(true); + + if (await validate()) { + onSave({ + id: savedQuery?.id, + title, + description: '', + shouldIncludeFilters, + shouldIncludeTimefilter, + }); + onClose(); + } + } finally { + setSaveIsDisabled(false); } }, [ validate, @@ -136,10 +137,6 @@ export function SaveQueryForm({ label={i18n.translate('unifiedSearch.search.searchBar.savedQueryNameLabelText', { defaultMessage: 'Name', })} - helpText={i18n.translate('unifiedSearch.search.searchBar.savedQueryNameHelpText', { - defaultMessage: - 'Name cannot contain a leading or trailing whitespace and must be unique.', - })} isInvalid={hasErrors} display="rowCompressed" > @@ -200,7 +197,7 @@ export function SaveQueryForm({ onClick={onClickSave} fill data-test-subj="savedQueryFormSaveButton" - disabled={hasErrors} + disabled={hasErrors || saveIsDisabled} > {i18n.translate('unifiedSearch.search.searchBar.savedQueryFormSaveButtonText', { defaultMessage: 'Save query', diff --git a/src/plugins/unified_search/public/saved_query_management/saved_query_management_list.scss b/src/plugins/unified_search/public/saved_query_management/saved_query_management_list.scss index 2e6f639ea792d..ad78b43fb1963 100644 --- a/src/plugins/unified_search/public/saved_query_management/saved_query_management_list.scss +++ b/src/plugins/unified_search/public/saved_query_management/saved_query_management_list.scss @@ -4,10 +4,6 @@ overflow-y: hidden; } -.kbnSavedQueryManagement__text { - padding: $euiSizeM $euiSizeM calc($euiSizeM / 2) $euiSizeM; -} - .kbnSavedQueryManagement__list { @include euiYScrollWithShadows; max-height: inherit; // Fixes overflow for applied max-height diff --git a/src/plugins/unified_search/public/saved_query_management/saved_query_management_list.test.tsx b/src/plugins/unified_search/public/saved_query_management/saved_query_management_list.test.tsx index 3dbdfaf7588fc..c5103c49b93fe 100644 --- a/src/plugins/unified_search/public/saved_query_management/saved_query_management_list.test.tsx +++ b/src/plugins/unified_search/public/saved_query_management/saved_query_management_list.test.tsx @@ -7,12 +7,7 @@ */ import React from 'react'; -import { EuiSelectable } from '@elastic/eui'; import { I18nProvider } from '@kbn/i18n-react'; -import { act } from 'react-dom/test-utils'; -import { findTestSubject } from '@elastic/eui/lib/test'; -import { mountWithIntl as mount } from '@kbn/test-jest-helpers'; -import { ReactWrapper } from 'enzyme'; import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public'; import { coreMock, applicationServiceMock } from '@kbn/core/public/mocks'; import { dataPluginMock } from '@kbn/data-plugin/public/mocks'; @@ -20,6 +15,8 @@ import { SavedQueryManagementListProps, SavedQueryManagementList, } from './saved_query_management_list'; +import { render, screen, waitFor } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; describe('Saved query management list component', () => { const startMock = coreMock.createStart(); @@ -32,11 +29,16 @@ describe('Saved query management list component', () => { savedObjectsManagement: { edit: true }, }, }; - function wrapSavedQueriesListComponentInContext(testProps: SavedQueryManagementListProps) { + + const wrapSavedQueriesListComponentInContext = ( + testProps: SavedQueryManagementListProps, + applicationService = application + ) => { const services = { uiSettings: startMock.uiSettings, http: startMock.http, - application, + application: applicationService, + notifications: startMock.notifications, }; return ( @@ -46,119 +48,203 @@ describe('Saved query management list component', () => { ); - } + }; + + const generateSavedQueries = (total: number) => { + const queries = []; + for (let i = 0; i < total; i++) { + queries.push({ + id: `8a0b7cd0-b0c4-11ec-92b2-73d62e0d28a${i}`, + attributes: { + title: `Test ${i}`, + description: '', + query: { + query: 'category.keyword : "Men\'s Shoes" ', + language: 'kuery', + }, + filters: [], + }, + namespaces: ['default'], + }); + } + return queries; + }; + + const fooQuery = { + id: '8a0b7cd0-b0c4-11ec-92b2-73d62e0d28a9', + attributes: { + title: 'Foo', + description: '', + query: { + query: 'category.keyword : "Men\'s Shoes" ', + language: 'kuery', + }, + filters: [], + }, + namespaces: ['default'], + }; + + const barQuery = { + id: '8a0b7cd0-b0c4-11ec-92b2-73d62e0d28a8', + attributes: { + title: 'Bar', + description: '', + query: { + query: 'category.keyword : "Men\'s Shoes" ', + language: 'kuery', + }, + filters: [], + }, + namespaces: ['default'], + }; + + const testQuery = { + id: '8a0b7cd0-b0c4-11ec-92b2-73d62e0d28a9', + attributes: { + title: 'Test', + description: '', + query: { + query: 'category.keyword : "Men\'s Shoes" ', + language: 'kuery', + }, + filters: [], + }, + namespaces: ['default'], + }; - function flushEffect(component: ReactWrapper) { - return act(async () => { - await component; - await new Promise((r) => setImmediate(r)); - component.update(); - }); - } let props: SavedQueryManagementListProps; + beforeEach(() => { props = { onLoad: jest.fn(), onClearSavedQuery: jest.fn(), onClose: jest.fn(), showSaveQuery: true, - hasFiltersOrQuery: false, savedQueryService: { ...dataMock.query.savedQueries, - getAllSavedQueries: jest.fn().mockResolvedValue([ - { - id: '8a0b7cd0-b0c4-11ec-92b2-73d62e0d28a9', - attributes: { - title: 'Test', - description: '', - query: { - query: 'category.keyword : "Men\'s Shoes" ', - language: 'kuery', - }, - filters: [], - }, - namespaces: ['default'], - }, - ]), + findSavedQueries: jest.fn().mockResolvedValue({ + total: 1, + queries: [testQuery], + }), deleteSavedQuery: jest.fn(), }, + queryBarMenuRef: React.createRef(), }; }); + it('should render the list component if saved queries exist', async () => { - const component = mount(wrapSavedQueriesListComponentInContext(props)); - await flushEffect(component); - expect(component.find('[data-test-subj="saved-query-management-list"]').length).toBe(1); + render(wrapSavedQueriesListComponentInContext(props)); + expect(await screen.findByRole('listbox', { name: 'Query list' })).toBeInTheDocument(); }); - it('should not rendet the list component if not saved queries exist', async () => { + it('should not render the list component if saved queries do not exist', async () => { const newProps = { ...props, savedQueryService: { ...dataMock.query.savedQueries, - getAllSavedQueries: jest.fn().mockResolvedValue([]), + findSavedQueries: jest.fn().mockResolvedValue({ total: 0, queries: [] }), }, }; - const component = mount(wrapSavedQueriesListComponentInContext(newProps)); - await flushEffect(component); - expect(component.find('[data-test-subj="saved-query-management-empty"]').length).toBeTruthy(); + render(wrapSavedQueriesListComponentInContext(newProps)); + await waitFor(() => { + expect(screen.queryByRole('listbox', { name: 'Query list' })).not.toBeInTheDocument(); + }); + expect(screen.queryAllByText(/No saved queries/)[0]).toBeInTheDocument(); }); it('should render the saved queries on the selectable component', async () => { - const component = mount(wrapSavedQueriesListComponentInContext(props)); - await flushEffect(component); - expect(component.find(EuiSelectable).prop('options').length).toBe(1); - expect(component.find(EuiSelectable).prop('options')[0].label).toBe('Test'); + render(wrapSavedQueriesListComponentInContext(props)); + expect(await screen.findAllByRole('option')).toHaveLength(1); + expect(screen.getByRole('option', { name: 'Test' })).toBeInTheDocument(); + }); + + it('should display the total and selected count', async () => { + const newProps = { + ...props, + savedQueryService: { + ...props.savedQueryService, + findSavedQueries: jest.fn().mockResolvedValue({ + total: 6, + queries: generateSavedQueries(5), + }), + }, + }; + render(wrapSavedQueriesListComponentInContext(newProps)); + expect(await screen.findByText('6 queries')).toBeInTheDocument(); + expect(screen.queryByText('6 queries | 1 selected')).not.toBeInTheDocument(); + userEvent.click(screen.getByRole('option', { name: 'Test 0' })); + expect(screen.queryByText('6 queries')).not.toBeInTheDocument(); + expect(screen.getByText('6 queries | 1 selected')).toBeInTheDocument(); + }); + + it('should not display the "Manage queries" link if application.capabilities.savedObjectsManagement.edit is false', async () => { + render( + wrapSavedQueriesListComponentInContext(props, { + ...application, + capabilities: { + ...application.capabilities, + savedObjectsManagement: { edit: false }, + }, + }) + ); + await waitFor(() => { + expect(screen.queryByRole('link', { name: 'Manage queries' })).not.toBeInTheDocument(); + }); + }); + + it('should display the "Manage queries" link if application.capabilities.savedObjectsManagement.edit is true', async () => { + render(wrapSavedQueriesListComponentInContext(props)); + expect(await screen.findByRole('link', { name: 'Manage queries' })).toBeInTheDocument(); + expect(screen.getByRole('link', { name: 'Manage queries' })).toHaveAttribute( + 'href', + '/app/management/kibana/objects?initialQuery=type:("query")' + ); }); - it('should call the onLoad function', async () => { + it('should call the onLoad and onClose function', async () => { const onLoadSpy = jest.fn(); + const onCloseSpy = jest.fn(); const newProps = { ...props, onLoad: onLoadSpy, + onClose: onCloseSpy, }; - const component = mount(wrapSavedQueriesListComponentInContext(newProps)); - await flushEffect(component); - component.find('[data-test-subj="load-saved-query-Test-button"]').first().simulate('click'); - expect( - component.find('[data-test-subj="saved-query-management-apply-changes-button"]').length - ).toBeTruthy(); - component - .find('button[data-test-subj="saved-query-management-apply-changes-button"]') - .first() - .simulate('click'); + render(wrapSavedQueriesListComponentInContext(newProps)); + expect(await screen.findByLabelText('Load query')).toBeDisabled(); + expect(screen.getByRole('button', { name: 'Delete query' })).toBeDisabled(); + userEvent.click(screen.getByRole('option', { name: 'Test' })); + expect(screen.getByLabelText('Load query')).toBeEnabled(); + expect(screen.getByRole('button', { name: 'Delete query' })).toBeEnabled(); + userEvent.click(screen.getByLabelText('Load query')); expect(onLoadSpy).toBeCalled(); + expect(onCloseSpy).toBeCalled(); }); it('should render the button with the correct text', async () => { - const component = mount(wrapSavedQueriesListComponentInContext(props)); - await flushEffect(component); + render(wrapSavedQueriesListComponentInContext(props)); expect( - component - .find('[data-test-subj="saved-query-management-apply-changes-button"]') - .first() - .text() - ).toBe('Load query'); + await screen.findByTestId('saved-query-management-apply-changes-button') + ).toHaveTextContent('Load query'); + }); + it('should not render the delete button if showSaveQuery is false', async () => { const newProps = { ...props, - hasFiltersOrQuery: true, + showSaveQuery: false, }; - const updatedComponent = mount(wrapSavedQueriesListComponentInContext(newProps)); - await flushEffect(component); - expect( - updatedComponent - .find('[data-test-subj="saved-query-management-apply-changes-button"]') - .first() - .text() - ).toBe('Load query'); + render(wrapSavedQueriesListComponentInContext(newProps)); + expect(await screen.findByLabelText('Load query')).toBeInTheDocument(); + expect(screen.queryByRole('button', { name: 'Delete query' })).not.toBeInTheDocument(); }); it('should render the modal on delete', async () => { - const component = mount(wrapSavedQueriesListComponentInContext(props)); - await flushEffect(component); - findTestSubject(component, 'delete-saved-query-Test-button').simulate('click'); - expect(component.find('[data-test-subj="confirmModalConfirmButton"]').length).toBeTruthy(); - expect(component.text()).not.toContain('you remove it from every space'); + render(wrapSavedQueriesListComponentInContext(props)); + userEvent.click(await screen.findByRole('option', { name: 'Test' })); + userEvent.click(screen.getByRole('button', { name: 'Delete query' })); + expect(screen.getByRole('heading', { name: 'Delete "Test"?' })).toBeInTheDocument(); + expect(screen.getByRole('button', { name: 'Cancel' })).toBeInTheDocument(); + expect(screen.getByRole('button', { name: 'Delete' })).toBeInTheDocument(); + expect(screen.queryByText(/you remove it from every space/)).not.toBeInTheDocument(); }); it('should render the modal with warning for multiple namespaces on delete', async () => { @@ -166,55 +252,369 @@ describe('Saved query management list component', () => { ...props, savedQueryService: { ...props.savedQueryService, - getAllSavedQueries: jest.fn().mockResolvedValue([ - { - id: '8a0b7cd0-b0c4-11ec-92b2-73d62e0d28a9', - attributes: { - title: 'Test', - description: '', - query: { - query: 'category.keyword : "Men\'s Shoes" ', - language: 'kuery', - }, - filters: [], - }, - namespaces: ['one', 'two'], - }, - ]), + findSavedQueries: jest.fn().mockResolvedValue({ + total: 1, + queries: [{ ...testQuery, namespaces: ['one', 'two'] }], + }), deleteSavedQuery: jest.fn(), }, }; - const component = mount(wrapSavedQueriesListComponentInContext(newProps)); - await flushEffect(component); - findTestSubject(component, 'delete-saved-query-Test-button').simulate('click'); - - expect(component.find('[data-test-subj="confirmModalConfirmButton"]').length).toBeTruthy(); - expect(component.text()).toContain('you remove it from every space'); + render(wrapSavedQueriesListComponentInContext(newProps)); + userEvent.click(await screen.findByRole('option', { name: 'Test' })); + userEvent.click(screen.getByRole('button', { name: 'Delete query' })); + expect(screen.getByRole('heading', { name: 'Delete "Test"?' })).toBeInTheDocument(); + expect(screen.getByRole('button', { name: 'Cancel' })).toBeInTheDocument(); + expect(screen.getByRole('button', { name: 'Delete' })).toBeInTheDocument(); + expect(screen.queryByText(/you remove it from every space/)).toBeInTheDocument(); }); - it('should render the onClearSavedQuery on delete of the current selected query', async () => { + it('should call deleteSavedQuery and onClearSavedQuery on delete of the current selected query', async () => { + const deleteSavedQuerySpy = jest.fn(); const onClearSavedQuerySpy = jest.fn(); const newProps = { ...props, - loadedSavedQuery: { - id: '8a0b7cd0-b0c4-11ec-92b2-73d62e0d28a9', - attributes: { - title: 'Test', - description: '', - query: { - query: 'category.keyword : "Men\'s Shoes" ', - language: 'kuery', - }, - filters: [], - }, - namespaces: ['default'], + loadedSavedQuery: testQuery, + savedQueryService: { + ...props.savedQueryService, + findSavedQueries: jest.fn().mockResolvedValue({ + total: 2, + queries: generateSavedQueries(1), + }), + deleteSavedQuery: deleteSavedQuerySpy, }, onClearSavedQuery: onClearSavedQuerySpy, }; - const component = mount(wrapSavedQueriesListComponentInContext(newProps)); - await flushEffect(component); - findTestSubject(component, 'delete-saved-query-Test-button').simulate('click'); - findTestSubject(component, 'confirmModalConfirmButton').simulate('click'); + render(wrapSavedQueriesListComponentInContext(newProps)); + expect(await screen.findByText('2 queries | 1 selected')).toBeInTheDocument(); + expect(screen.getAllByRole('option')).toHaveLength(2); + expect(screen.getByLabelText('Load query')).toBeEnabled(); + expect(screen.getByRole('button', { name: 'Delete query' })).toBeEnabled(); + userEvent.click(screen.getByRole('button', { name: 'Delete query' })); + userEvent.click(screen.getByRole('button', { name: 'Delete' })); + expect(screen.getByText('1 query')).toBeInTheDocument(); + expect(screen.getAllByRole('option')).toHaveLength(1); + expect(screen.getByLabelText('Load query')).toBeDisabled(); + expect(screen.getByRole('button', { name: 'Delete query' })).toBeDisabled(); + expect(deleteSavedQuerySpy).toHaveBeenLastCalledWith('8a0b7cd0-b0c4-11ec-92b2-73d62e0d28a9'); expect(onClearSavedQuerySpy).toBeCalled(); }); + + it('should not render pagination if there are less than 5 saved queries', async () => { + render(wrapSavedQueriesListComponentInContext(props)); + await waitFor(() => { + expect(screen.queryByText(/1 of/)).not.toBeInTheDocument(); + }); + }); + + it('should render pagination if there are more than 5 saved queries', async () => { + const newProps = { + ...props, + savedQueryService: { + ...props.savedQueryService, + findSavedQueries: jest.fn().mockResolvedValue({ + total: 6, + queries: generateSavedQueries(5), + }), + }, + }; + render(wrapSavedQueriesListComponentInContext(newProps)); + expect(await screen.findByText(/1 of 2/)).toBeInTheDocument(); + }); + + it('should allow navigating between saved query pages', async () => { + const findSavedQueriesSpy = jest.fn().mockResolvedValue({ + total: 6, + queries: generateSavedQueries(5), + }); + const newProps = { + ...props, + savedQueryService: { + ...props.savedQueryService, + findSavedQueries: findSavedQueriesSpy, + }, + }; + render(wrapSavedQueriesListComponentInContext(newProps)); + await waitFor(() => { + expect(findSavedQueriesSpy).toHaveBeenLastCalledWith(undefined, 5, 1); + }); + expect(screen.getByText(/1 of 2/)).toBeInTheDocument(); + expect(screen.getAllByRole('option')).toHaveLength(5); + findSavedQueriesSpy.mockResolvedValue({ + total: 6, + queries: generateSavedQueries(1), + }); + userEvent.click(screen.getByRole('button', { name: 'Next page' })); + await waitFor(() => { + expect(findSavedQueriesSpy).toHaveBeenLastCalledWith(undefined, 5, 2); + }); + expect(screen.getByText(/2 of 2/)).toBeInTheDocument(); + expect(screen.getAllByRole('option')).toHaveLength(1); + findSavedQueriesSpy.mockResolvedValue({ + total: 6, + queries: generateSavedQueries(5), + }); + userEvent.click(screen.getByRole('button', { name: 'Previous page' })); + await waitFor(() => { + expect(findSavedQueriesSpy).toHaveBeenLastCalledWith(undefined, 5, 1); + }); + expect(screen.getByText(/1 of 2/)).toBeInTheDocument(); + expect(screen.getAllByRole('option')).toHaveLength(5); + }); + + it('should not clear the currently selected saved query when navigating between pages', async () => { + const findSavedQueriesSpy = jest.fn().mockResolvedValue({ + total: 6, + queries: generateSavedQueries(5), + }); + const newProps = { + ...props, + savedQueryService: { + ...props.savedQueryService, + findSavedQueries: findSavedQueriesSpy, + }, + }; + render(wrapSavedQueriesListComponentInContext(newProps)); + await waitFor(() => { + expect(findSavedQueriesSpy).toHaveBeenLastCalledWith(undefined, 5, 1); + }); + expect(screen.getByRole('option', { name: 'Test 0', checked: false })).toBeInTheDocument(); + userEvent.click(screen.getByRole('option', { name: 'Test 0' })); + expect(screen.getByRole('option', { name: 'Test 0', checked: true })).toBeInTheDocument(); + findSavedQueriesSpy.mockResolvedValue({ + total: 6, + queries: generateSavedQueries(1), + }); + userEvent.click(screen.getByRole('button', { name: 'Next page' })); + await waitFor(() => { + expect(findSavedQueriesSpy).toHaveBeenLastCalledWith(undefined, 5, 2); + }); + findSavedQueriesSpy.mockResolvedValue({ + total: 6, + queries: generateSavedQueries(5), + }); + userEvent.click(screen.getByRole('button', { name: 'Previous page' })); + await waitFor(() => { + expect(findSavedQueriesSpy).toHaveBeenLastCalledWith(undefined, 5, 1); + }); + expect(screen.getByRole('option', { name: 'Test 0', checked: true })).toBeInTheDocument(); + }); + + it('should allow providing a search term', async () => { + const findSavedQueriesSpy = jest.fn().mockResolvedValue({ + total: 6, + queries: generateSavedQueries(5), + }); + const newProps = { + ...props, + savedQueryService: { + ...props.savedQueryService, + findSavedQueries: findSavedQueriesSpy, + }, + }; + render(wrapSavedQueriesListComponentInContext(newProps)); + await waitFor(() => { + expect(findSavedQueriesSpy).toHaveBeenLastCalledWith(undefined, 5, 1); + }); + expect(screen.getByText(/1 of 2/)).toBeInTheDocument(); + expect(screen.getAllByRole('option')).toHaveLength(5); + findSavedQueriesSpy.mockResolvedValue({ + total: 6, + queries: generateSavedQueries(1), + }); + userEvent.click(screen.getByRole('button', { name: 'Next page' })); + await waitFor(() => { + expect(findSavedQueriesSpy).toHaveBeenLastCalledWith(undefined, 5, 2); + }); + expect(screen.getByText(/2 of 2/)).toBeInTheDocument(); + expect(screen.getAllByRole('option')).toHaveLength(1); + findSavedQueriesSpy.mockResolvedValue({ + total: 1, + queries: generateSavedQueries(1), + }); + userEvent.type(screen.getByRole('combobox', { name: 'Query list' }), ' Test And Search '); + await waitFor(() => { + expect(findSavedQueriesSpy).toHaveBeenLastCalledWith('Test And Search', 5, 1); + }); + expect(screen.queryByText(/1 of/)).not.toBeInTheDocument(); + expect(screen.getAllByRole('option')).toHaveLength(1); + }); + + it('should correctly handle out of order responses', async () => { + const completionOrder: number[] = []; + let triggerResolve = () => {}; + const findSavedQueriesSpy = jest.fn().mockImplementation(async (_, __, page) => { + let queries: ReturnType = []; + if (page === 1) { + queries = generateSavedQueries(5); + completionOrder.push(1); + } else if (page === 2) { + queries = await new Promise((resolve) => { + triggerResolve = () => resolve(generateSavedQueries(5)); + }); + completionOrder.push(2); + } else if (page === 3) { + queries = generateSavedQueries(1); + completionOrder.push(3); + } + return { + total: 11, + queries, + }; + }); + const newProps = { + ...props, + savedQueryService: { + ...props.savedQueryService, + findSavedQueries: findSavedQueriesSpy, + }, + }; + render(wrapSavedQueriesListComponentInContext(newProps)); + await waitFor(() => { + expect(completionOrder).toEqual([1]); + }); + expect(screen.getByText(/1 of 3/)).toBeInTheDocument(); + expect(screen.getAllByRole('option')).toHaveLength(5); + userEvent.click(screen.getByRole('button', { name: 'Next page' })); + await waitFor(() => { + expect(findSavedQueriesSpy).toHaveBeenLastCalledWith(undefined, 5, 2); + }); + expect(completionOrder).toEqual([1]); + expect(screen.getByText(/2 of 3/)).toBeInTheDocument(); + expect(screen.getAllByRole('option')).toHaveLength(5); + userEvent.click(screen.getByRole('button', { name: 'Next page' })); + await waitFor(() => { + expect(findSavedQueriesSpy).toHaveBeenLastCalledWith(undefined, 5, 3); + }); + expect(completionOrder).toEqual([1, 3]); + triggerResolve(); + await waitFor(() => { + expect(completionOrder).toEqual([1, 3, 2]); + }); + expect(screen.getByText(/3 of 3/)).toBeInTheDocument(); + expect(screen.getAllByRole('option')).toHaveLength(1); + userEvent.click(screen.getByRole('button', { name: 'Previous page' })); + await waitFor(() => { + expect(findSavedQueriesSpy).toHaveBeenLastCalledWith(undefined, 5, 2); + }); + expect(completionOrder).toEqual([1, 3, 2]); + expect(screen.getByText(/2 of 3/)).toBeInTheDocument(); + expect(screen.getAllByRole('option')).toHaveLength(1); + userEvent.click(screen.getByRole('button', { name: 'Previous page' })); + await waitFor(() => { + expect(findSavedQueriesSpy).toHaveBeenLastCalledWith(undefined, 5, 1); + }); + expect(completionOrder).toEqual([1, 3, 2, 1]); + triggerResolve(); + await waitFor(() => { + expect(completionOrder).toEqual([1, 3, 2, 1, 2]); + }); + }); + + it('should not display an "Active" badge if there is no currently loaded saved query', async () => { + render(wrapSavedQueriesListComponentInContext(props)); + await waitFor(() => { + expect(screen.queryByText(/Active/)).not.toBeInTheDocument(); + }); + }); + + it('should display an "Active" badge for the currently loaded saved query', async () => { + const newProps = { + ...props, + loadedSavedQuery: testQuery, + }; + render(wrapSavedQueriesListComponentInContext(newProps)); + expect(await screen.findByText(/Active/)).toBeInTheDocument(); + }); + + it('should hoist the currently loaded saved query to the top of the list', async () => { + const newProps = { + ...props, + loadedSavedQuery: fooQuery, + savedQueryService: { + ...props.savedQueryService, + findSavedQueries: jest.fn().mockResolvedValue({ + total: 2, + queries: [barQuery, fooQuery], + }), + }, + }; + render(wrapSavedQueriesListComponentInContext(newProps)); + expect(await screen.findAllByRole('option')).toHaveLength(2); + expect(screen.getAllByRole('option')[0]).toHaveTextContent('Foo'); + expect(screen.getAllByRole('option')[0]).toHaveTextContent('Active'); + expect(screen.getAllByRole('option')[1]).toHaveTextContent('Bar'); + expect(screen.getAllByRole('option')[1]).not.toHaveTextContent('Active'); + }); + + it('should hoist the currently loaded saved query to the top of the list even if it is not in the first page of results', async () => { + const newProps = { + ...props, + loadedSavedQuery: fooQuery, + savedQueryService: { + ...props.savedQueryService, + findSavedQueries: jest.fn().mockResolvedValue({ + total: 6, + queries: generateSavedQueries(5), + }), + }, + }; + render(wrapSavedQueriesListComponentInContext(newProps)); + expect(await screen.findAllByRole('option')).toHaveLength(6); + expect(screen.getAllByRole('option')[0]).toHaveTextContent('Foo'); + expect(screen.getAllByRole('option')[0]).toHaveTextContent('Active'); + expect(screen.getAllByRole('option')[1]).toHaveTextContent('Test 0'); + expect(screen.getAllByRole('option')[1]).not.toHaveTextContent('Active'); + }); + + it('should not hoist the currently loaded saved query to the top of the list if there is a search term', async () => { + const findSavedQueriesSpy = jest.fn().mockResolvedValue({ + total: 2, + queries: [barQuery, fooQuery], + }); + const newProps = { + ...props, + loadedSavedQuery: fooQuery, + savedQueryService: { + ...props.savedQueryService, + findSavedQueries: findSavedQueriesSpy, + }, + }; + render(wrapSavedQueriesListComponentInContext(newProps)); + expect(await screen.findAllByRole('option')).toHaveLength(2); + expect(screen.getAllByRole('option')[0]).toHaveTextContent('Foo'); + expect(screen.getAllByRole('option')[0]).toHaveTextContent('Active'); + userEvent.type(screen.getByRole('searchbox', { name: 'Query list' }), ' Test And Search '); + await waitFor(() => { + expect(findSavedQueriesSpy).toHaveBeenLastCalledWith('Test And Search', 5, 1); + }); + expect(screen.getAllByRole('option')).toHaveLength(2); + expect(screen.getAllByRole('option')[0]).toHaveTextContent('Bar'); + expect(screen.getAllByRole('option')[0]).not.toHaveTextContent('Active'); + }); + + it('should not hoist the currently loaded saved query to the top of the list if not on the first page', async () => { + const findSavedQueriesSpy = jest.fn().mockResolvedValue({ + total: 6, + queries: generateSavedQueries(5), + }); + const newProps = { + ...props, + loadedSavedQuery: fooQuery, + savedQueryService: { + ...props.savedQueryService, + findSavedQueries: findSavedQueriesSpy, + }, + }; + render(wrapSavedQueriesListComponentInContext(newProps)); + expect(await screen.findAllByRole('option')).toHaveLength(6); + expect(screen.getAllByRole('option')[0]).toHaveTextContent('Foo'); + expect(screen.getAllByRole('option')[0]).toHaveTextContent('Active'); + userEvent.click(screen.getByRole('button', { name: 'Next page' })); + await waitFor(() => { + expect(findSavedQueriesSpy).toHaveBeenLastCalledWith(undefined, 5, 2); + }); + expect(screen.getAllByRole('option')).toHaveLength(5); + expect(screen.getAllByRole('option')[0]).toHaveTextContent('Test 0'); + expect(screen.getAllByRole('option')[0]).not.toHaveTextContent('Active'); + }); }); diff --git a/src/plugins/unified_search/public/saved_query_management/saved_query_management_list.tsx b/src/plugins/unified_search/public/saved_query_management/saved_query_management_list.tsx index 0cff3baa90883..d62061d7d6cf6 100644 --- a/src/plugins/unified_search/public/saved_query_management/saved_query_management_list.tsx +++ b/src/plugins/unified_search/public/saved_query_management/saved_query_management_list.tsx @@ -13,33 +13,42 @@ import { EuiIcon, EuiPanel, EuiSelectable, - EuiText, EuiPopoverFooter, EuiButtonIcon, - EuiButtonEmpty, EuiConfirmModal, - usePrettyDuration, ShortDate, + EuiPagination, + EuiBadge, + EuiToolTip, + EuiText, + EuiHorizontalRule, + EuiProgress, + PrettyDuration, } from '@elastic/eui'; - +import { EuiContextMenuClass } from '@elastic/eui/src/components/context_menu/context_menu'; import { i18n } from '@kbn/i18n'; -import React, { useCallback, useEffect, useState, useRef } from 'react'; -import { css } from '@emotion/react'; -import { sortBy } from 'lodash'; +import React, { useCallback, useState, useRef, useEffect, useMemo, RefObject } from 'react'; +import { renderToStaticMarkup } from 'react-dom/server'; import { useKibana } from '@kbn/kibana-react-plugin/public'; -import { SavedQuery, SavedQueryService } from '@kbn/data-plugin/public'; +import type { SavedQuery, SavedQueryService } from '@kbn/data-plugin/public'; import type { SavedQueryAttributes } from '@kbn/data-plugin/common'; import './saved_query_management_list.scss'; +import { euiThemeVars } from '@kbn/ui-theme'; +import { debounce } from 'lodash'; +import useLatest from 'react-use/lib/useLatest'; +import { KibanaRenderContextProvider } from '@kbn/react-kibana-context-render'; import type { IUnifiedSearchPluginServices } from '../types'; +import { strings as queryBarMenuPanelsStrings } from '../query_string_input/query_bar_menu_panels'; +import { PanelTitle } from '../query_string_input/panel_title'; export interface SavedQueryManagementListProps { showSaveQuery?: boolean; loadedSavedQuery?: SavedQuery; savedQueryService: SavedQueryService; + queryBarMenuRef: RefObject; onLoad: (savedQuery: SavedQuery) => void; onClearSavedQuery: () => void; onClose: () => void; - hasFiltersOrQuery: boolean; } interface SelectableProps { @@ -60,34 +69,90 @@ interface DurationRange { } const commonDurationRanges: DurationRange[] = [ - { start: 'now/d', end: 'now/d', label: 'Today' }, - { start: 'now/w', end: 'now/w', label: 'This week' }, - { start: 'now/M', end: 'now/M', label: 'This month' }, - { start: 'now/y', end: 'now/y', label: 'This year' }, - { start: 'now-1d/d', end: 'now-1d/d', label: 'Yesterday' }, - { start: 'now/w', end: 'now', label: 'Week to date' }, - { start: 'now/M', end: 'now', label: 'Month to date' }, - { start: 'now/y', end: 'now', label: 'Year to date' }, + { + start: 'now/d', + end: 'now/d', + label: i18n.translate('unifiedSearch.search.searchBar.savedQueryTodayLabel', { + defaultMessage: 'Today', + }), + }, + { + start: 'now/w', + end: 'now/w', + label: i18n.translate('unifiedSearch.search.searchBar.savedQueryWeekLabel', { + defaultMessage: 'This week', + }), + }, + { + start: 'now/M', + end: 'now/M', + label: i18n.translate('unifiedSearch.search.searchBar.savedQueryMonthLabel', { + defaultMessage: 'This month', + }), + }, + { + start: 'now/y', + end: 'now/y', + label: i18n.translate('unifiedSearch.search.searchBar.savedQueryYearLabel', { + defaultMessage: 'This year', + }), + }, + { + start: 'now-1d/d', + end: 'now-1d/d', + label: i18n.translate('unifiedSearch.searchBar.savedQueryYesterdayLabel', { + defaultMessage: 'Yesterday', + }), + }, + { + start: 'now/w', + end: 'now', + label: i18n.translate('unifiedSearch.searchBar.savedQueryWeekToDateLabel', { + defaultMessage: 'Week to date', + }), + }, + { + start: 'now/M', + end: 'now', + label: i18n.translate('unifiedSearch.searchBar.savedQueryMonthToDateLabel', { + defaultMessage: 'Month to date', + }), + }, + { + start: 'now/y', + end: 'now', + label: i18n.translate('unifiedSearch.searchBar.savedQueryYearToDateLabel', { + defaultMessage: 'Year to date', + }), + }, ]; -const itemTitle = (attributes: SavedQueryAttributes, format: string) => { - let label = attributes.title; - const prettifier = usePrettyDuration; +const itemTitle = (attributes: SavedQueryAttributes, services: IUnifiedSearchPluginServices) => { + const label = [attributes.title]; if (attributes.description) { - label += `; ${attributes.description}`; + label.push(attributes.description); } if (attributes.timefilter) { - label += `; ${prettifier({ - timeFrom: attributes.timefilter?.from, - timeTo: attributes.timefilter?.to, - quickRanges: commonDurationRanges, - dateFormat: format, - })}`; + label.push( + // This is a hack to render the PrettyDuration component to a string since itemTitle + // is called in a loop, so the usePrettyDuration hook is not an option, and it must + // return a string, but there is no non-hook alternative that returns a string + renderToStaticMarkup( + + + + ) + ); } - return label; + return label.join('; '); }; const itemLabel = (attributes: SavedQueryAttributes) => { @@ -112,41 +177,103 @@ const itemLabel = (attributes: SavedQueryAttributes) => { return label; }; -export function SavedQueryManagementList({ +const noSavedQueriesDescriptionText = [ + i18n.translate('unifiedSearch.search.searchBar.savedQueryNoSavedQueriesText', { + defaultMessage: 'No saved queries.', + }), + i18n.translate('unifiedSearch.search.searchBar.savedQueryDescriptionText', { + defaultMessage: 'Save query text and filters that you want to use again.', + }), +].join(' '); + +const savedQueryMultipleNamespacesDeleteWarning = i18n.translate( + 'unifiedSearch.search.searchBar.savedQueryMultipleNamespacesDeleteWarning', + { + defaultMessage: `This saved query is shared in multiple spaces. When you delete it, you remove it from every space it is shared in. You can't undo this action.`, + } +); + +const SAVED_QUERY_PAGE_SIZE = 5; +const SAVED_QUERY_SEARCH_DEBOUNCE = 500; +const LOADING_INDICATOR_DELAY = 250; + +export const SavedQueryManagementList = ({ showSaveQuery, loadedSavedQuery, + savedQueryService, + queryBarMenuRef, onLoad, onClearSavedQuery, - savedQueryService, onClose, - hasFiltersOrQuery, -}: SavedQueryManagementListProps) { - const kibana = useKibana(); - const [savedQueries, setSavedQueries] = useState([] as SavedQuery[]); +}: SavedQueryManagementListProps) => { + const services = useKibana().services; + const [searchTerm, setSearchTerm] = useState(''); + const [currentPageNumber, setCurrentPageNumber] = useState(0); + const [totalQueryCount, setTotalQueryCount] = useState(0); + const [currentPageQueries, setCurrentPageQueries] = useState([]); + const [isLoading, setIsLoading] = useState(false); + const [isInitializing, setIsInitializing] = useState(true); + const currentPageFetchId = useRef(0); + const selectableRef = useRef(null); const [selectedSavedQuery, setSelectedSavedQuery] = useState(loadedSavedQuery); - const [toBeDeletedSavedQuery, setToBeDeletedSavedQuery] = useState(null as SavedQuery | null); + const [toBeDeletedSavedQuery, setToBeDeletedSavedQuery] = useState(null); const [showDeletionConfirmationModal, setShowDeletionConfirmationModal] = useState(false); - const cancelPendingListingRequest = useRef<() => void>(() => {}); - const { uiSettings, http, application } = kibana.services; - const format = uiSettings.get('dateFormat'); - useEffect(() => { - const fetchCountAndSavedQueries = async () => { - cancelPendingListingRequest.current(); - let requestGotCancelled = false; - cancelPendingListingRequest.current = () => { - requestGotCancelled = true; - }; + const debouncedSetSearchTerm = useMemo(() => { + return debounce((newSearchTerm: string) => { + setSearchTerm((currentSearchTerm) => { + if (currentSearchTerm !== newSearchTerm) { + setCurrentPageNumber(0); + } - const savedQueryItems = await savedQueryService.getAllSavedQueries(); + return newSearchTerm; + }); + }, SAVED_QUERY_SEARCH_DEBOUNCE); + }, []); - if (requestGotCancelled) return; + const fetchPage = useLatest(async () => { + const fetchIdValue = ++currentPageFetchId.current; + const loadingTimeout = setTimeout(() => { + setIsLoading(true); + }, LOADING_INDICATOR_DELAY); + + try { + const preparedSearch = searchTerm.trim(); + const { total, queries } = await savedQueryService.findSavedQueries( + preparedSearch || undefined, + SAVED_QUERY_PAGE_SIZE, + currentPageNumber + 1 + ); + + if (fetchIdValue !== currentPageFetchId.current) { + return; + } + + let filteredQueries = queries; + + if (loadedSavedQuery && !preparedSearch && currentPageNumber === 0) { + filteredQueries = [ + loadedSavedQuery, + ...queries.filter((savedQuery) => savedQuery.id !== loadedSavedQuery.id), + ]; + } + + setTotalQueryCount(total); + setCurrentPageQueries(filteredQueries); + selectableRef.current?.scrollToItem(0); + } finally { + clearTimeout(loadingTimeout); + + if (fetchIdValue === currentPageFetchId.current) { + setIsLoading(false); + setIsInitializing(false); + } + } + }); - const sortedSavedQueryItems = sortBy(savedQueryItems, 'attributes.title'); - setSavedQueries(sortedSavedQueryItems); - }; - fetchCountAndSavedQueries(); - }, [savedQueryService]); + useEffect(() => { + fetchPage.current(); + }, [currentPageNumber, fetchPage, searchTerm]); const handleLoad = useCallback(() => { if (selectedSavedQuery) { @@ -165,194 +292,275 @@ export function SavedQueryManagementList({ }, []); const onDelete = useCallback( - (savedQueryToDelete: string) => { + (savedQueryToDelete: SavedQuery) => { const onDeleteSavedQuery = async (savedQueryId: string) => { - cancelPendingListingRequest.current(); - setSavedQueries( - savedQueries.filter((currentSavedQuery) => currentSavedQuery.id !== savedQueryId) + setTotalQueryCount((currentTotalQueryCount) => Math.max(0, currentTotalQueryCount - 1)); + setCurrentPageQueries( + currentPageQueries.filter((currentSavedQuery) => currentSavedQuery.id !== savedQueryId) ); + setSelectedSavedQuery(undefined); if (loadedSavedQuery && loadedSavedQuery.id === savedQueryId) { onClearSavedQuery(); - setSelectedSavedQuery(undefined); } - await savedQueryService.deleteSavedQuery(savedQueryId); + try { + await savedQueryService.deleteSavedQuery(savedQueryId); + + services.notifications.toasts.addSuccess( + i18n.translate('unifiedSearch.search.searchBar.deleteQuerySuccessMessage', { + defaultMessage: 'Query "{queryTitle}" was deleted', + values: { + queryTitle: savedQueryToDelete.attributes.title, + }, + }) + ); + } catch (error) { + services.notifications.toasts.addDanger( + i18n.translate('unifiedSearch.search.searchBar.deleteQueryErrorMessage', { + defaultMessage: + 'An error occured while deleting query "{queryTitle}": {errorMessage}', + values: { + queryTitle: savedQueryToDelete.attributes.title, + errorMessage: error.message, + }, + }) + ); + throw error; + } }; - onDeleteSavedQuery(savedQueryToDelete); + onDeleteSavedQuery(savedQueryToDelete.id); }, - [loadedSavedQuery, onClearSavedQuery, savedQueries, savedQueryService] + [ + currentPageQueries, + loadedSavedQuery, + onClearSavedQuery, + savedQueryService, + services.notifications.toasts, + ] ); - const savedQueryDescriptionText = i18n.translate( - 'unifiedSearch.search.searchBar.savedQueryDescriptionText', - { - defaultMessage: 'Save query text and filters that you want to use again.', - } - ); - - const noSavedQueriesDescriptionText = - i18n.translate('unifiedSearch.search.searchBar.savedQueryNoSavedQueriesText', { - defaultMessage: 'No saved queries.', - }) + - ' ' + - savedQueryDescriptionText; - - const savedQueryMultipleNamespacesDeleteWarning = i18n.translate( - 'unifiedSearch.search.searchBar.savedQueryMultipleNamespacesDeleteWarning', - { - defaultMessage: `This saved query is shared in multiple spaces. When you delete it, you remove it from every space it is shared in. You can't undo this action.`, - } - ); - - const savedQueriesOptions = () => { - const savedQueriesWithoutCurrent = savedQueries.filter((savedQuery) => { - if (!loadedSavedQuery) return true; - return savedQuery.id !== loadedSavedQuery.id; - }); - const savedQueriesReordered = - loadedSavedQuery && savedQueriesWithoutCurrent.length !== savedQueries.length - ? [loadedSavedQuery, ...savedQueriesWithoutCurrent] - : [...savedQueriesWithoutCurrent]; - - return savedQueriesReordered.map((savedQuery) => { + const savedQueriesOptions = useMemo(() => { + return currentPageQueries.map((savedQuery) => { return { key: savedQuery.id, label: savedQuery.attributes.title, - title: itemTitle(savedQuery.attributes, format), + title: itemTitle(savedQuery.attributes, services), 'data-test-subj': `load-saved-query-${savedQuery.attributes.title}-button`, value: savedQuery.id, checked: selectedSavedQuery && savedQuery.id === selectedSavedQuery.id ? 'on' : undefined, data: { attributes: savedQuery.attributes, }, - append: !!showSaveQuery && ( - handleDelete(savedQuery)} - color="danger" - /> - ), }; - }) as unknown as SelectableProps[]; - }; + }); + }, [currentPageQueries, selectedSavedQuery, services]); - const renderOption = (option: RenderOptionProps) => { - return <>{option.attributes ? itemLabel(option.attributes) : option.label}; - }; + const renderOption = useCallback( + (option: RenderOptionProps) => { + return ( + <> + {option.attributes ? itemLabel(option.attributes) : option.label} + {option.value === loadedSavedQuery?.id && ( + + {i18n.translate('unifiedSearch.search.searchBar.savedQueryActiveBadgeText', { + defaultMessage: 'Active', + })} + + )} + + ); + }, + [loadedSavedQuery?.id] + ); - const canEditSavedObjects = application.capabilities.savedObjectsManagement.edit; + const countDisplay = useMemo(() => { + const parts = [ + i18n.translate('unifiedSearch.search.searchBar.savedQueryTotalQueryCount', { + defaultMessage: '{totalQueryCount, plural, one {# query} other {# queries}}', + values: { totalQueryCount }, + }), + ]; + + if (Boolean(selectedSavedQuery)) { + parts.push( + i18n.translate('unifiedSearch.search.searchBar.savedQuerySelectedQueryCount', { + defaultMessage: '1 selected', + }) + ); + } + + return parts.join(' | '); + }, [selectedSavedQuery, totalQueryCount]); - const listComponent = ( + return ( <> - {savedQueries.length > 0 ? ( - <> -
- - aria-label="Basic example" - options={savedQueriesOptions()} - searchable - singleSelection="always" - onChange={(choices) => { - const choice = choices.find(({ checked }) => checked) as unknown as { - value: string; - }; - if (choice) { - handleSelect(savedQueries.find((savedQuery) => savedQuery.id === choice.value)); - } - }} - searchProps={{ - compressed: true, - placeholder: i18n.translate( - 'unifiedSearch.query.queryBar.indexPattern.findFilterSet', - { - defaultMessage: 'Find a query', - } - ), - }} - listProps={{ - isVirtualized: true, - }} - renderOption={renderOption} - > - {(list, search) => ( - <> - - {search} - - {list} - - )} - -
- - ) : ( - <> - -

{noSavedQueriesDescriptionText}

-
- - )} - - - - - {i18n.translate( - 'unifiedSearch.search.searchBar.savedQueryPopoverApplyFilterSetLabel', + + + + {isLoading && } + + ref={selectableRef} + aria-label={i18n.translate('unifiedSearch.search.searchBar.savedQueryListAriaLabel', { + defaultMessage: 'Query list', + })} + isLoading={isInitializing} + singleSelection="always" + options={savedQueriesOptions} + listProps={{ onFocusBadge: false }} + isPreFiltered + searchable + searchProps={{ + compressed: true, + placeholder: i18n.translate( + 'unifiedSearch.query.queryBar.indexPattern.findFilterSet', { - defaultMessage: 'Load query', + defaultMessage: 'Find a query', } - )} - + ), + onChange: debouncedSetSearchTerm, + 'data-test-subj': 'saved-query-management-search-input', + }} + loadingMessage={i18n.translate( + 'unifiedSearch.search.searchBar.savedQueryLoadingQueriesText', + { + defaultMessage: 'Loading queries', + } + )} + emptyMessage={ + + {noSavedQueriesDescriptionText} + + } + onChange={(choices) => { + const choice = choices.find(({ checked }) => checked); + if (choice) { + handleSelect( + currentPageQueries.find((savedQuery) => savedQuery.id === choice.value) + ); + } + }} + renderOption={renderOption} + css={{ + '.euiSelectableList__list': { + WebkitMaskImage: 'unset', + maskImage: 'unset', + }, + }} + > + {(list, search) => ( + <> + + {search} + + + + {countDisplay} + + + + {list} + + )} +
+
+ {totalQueryCount > SAVED_QUERY_PAGE_SIZE && ( + + + + setCurrentPageNumber(activePage)} + compressed + /> + + - {canEditSavedObjects && ( + )} +
+ + + {Boolean(showSaveQuery) && ( - - {i18n.translate('unifiedSearch.search.searchBar.savedQueryPopoverManageLabel', { - defaultMessage: 'Manage saved objects', - })} - + { + if (selectedSavedQuery) { + handleDelete(selectedSavedQuery); + } + }} + /> + )} + + + + {i18n.translate( + 'unifiedSearch.search.searchBar.savedQueryPopoverApplyFilterSetLabel', + { + defaultMessage: 'Load query', + } + )} + + + {showDeletionConfirmationModal && toBeDeletedSavedQuery && ( @@ -379,7 +587,7 @@ export function SavedQueryManagementList({ } )} onConfirm={() => { - onDelete(toBeDeletedSavedQuery.id); + onDelete(toBeDeletedSavedQuery); setShowDeletionConfirmationModal(false); }} buttonColor="danger" @@ -395,6 +603,40 @@ export function SavedQueryManagementList({ )} ); +}; - return listComponent; -} +const ListTitle = ({ queryBarMenuRef }: { queryBarMenuRef: RefObject }) => { + const { application, http } = useKibana().services; + const canEditSavedObjects = application.capabilities.savedObjectsManagement.edit; + + return ( + + + + ) + } + /> + ); +}; diff --git a/src/plugins/unified_search/public/search_bar/search_bar.test.tsx b/src/plugins/unified_search/public/search_bar/search_bar.test.tsx index b7c7e83b7c7f5..e9fce0f749928 100644 --- a/src/plugins/unified_search/public/search_bar/search_bar.test.tsx +++ b/src/plugins/unified_search/public/search_bar/search_bar.test.tsx @@ -100,6 +100,7 @@ function wrapSearchBarInContext(testProps: any) { savedQueries: { findSavedQueries: () => Promise.resolve({ + total: 1, queries: [ { id: 'testwewe', @@ -115,6 +116,7 @@ function wrapSearchBarInContext(testProps: any) { }, ], }), + getSavedQueryCount: jest.fn(), }, }, dataViewEditor: dataViewEditorMock, diff --git a/src/plugins/unified_search/public/search_bar/search_bar.tsx b/src/plugins/unified_search/public/search_bar/search_bar.tsx index a8a81224df534..77755ccd6a990 100644 --- a/src/plugins/unified_search/public/search_bar/search_bar.tsx +++ b/src/plugins/unified_search/public/search_bar/search_bar.tsx @@ -9,18 +9,24 @@ import { compact } from 'lodash'; import { InjectedIntl, injectI18n } from '@kbn/i18n-react'; import classNames from 'classnames'; -import React, { Component } from 'react'; +import React, { Component, createRef } from 'react'; import { EuiIconProps, withEuiTheme, WithEuiThemeProps } from '@elastic/eui'; +import { EuiContextMenuClass } from '@elastic/eui/src/components/context_menu/context_menu'; import { get, isEqual } from 'lodash'; import memoizeOne from 'memoize-one'; import { METRIC_TYPE } from '@kbn/analytics'; import { Query, Filter, TimeRange, AggregateQuery, isOfQueryType } from '@kbn/es-query'; import { withKibana, KibanaReactContextValue } from '@kbn/kibana-react-plugin/public'; -import type { TimeHistoryContract, SavedQuery } from '@kbn/data-plugin/public'; +import type { + TimeHistoryContract, + SavedQuery, + SavedQueryTimeFilter, +} from '@kbn/data-plugin/public'; import type { SavedQueryAttributes } from '@kbn/data-plugin/common'; import { DataView } from '@kbn/data-views-plugin/public'; +import { i18n } from '@kbn/i18n'; import type { IUnifiedSearchPluginServices } from '../types'; import { SavedQueryMeta, SaveQueryForm } from '../saved_query_form'; import { SavedQueryManagementList } from '../saved_query_management'; @@ -153,6 +159,7 @@ class SearchBarUI extends C private services = this.props.kibana.services; private savedQueryService = this.services.data.query.savedQueries; + private queryBarMenuRef = createRef(); public static getDerivedStateFromProps( nextProps: SearchBarProps, @@ -290,27 +297,14 @@ class SearchBarUI extends C return true; } - public onSave = async (savedQueryMeta: SavedQueryMeta, saveAsNew = false) => { - if (!this.state.query) return; - - const savedQueryAttributes: SavedQueryAttributes = { - title: savedQueryMeta.title, - description: savedQueryMeta.description, - query: this.state.query as Query, - }; - - if (savedQueryMeta.shouldIncludeFilters) { - savedQueryAttributes.filters = this.props.filters; - } - + private getTimeFilter(): SavedQueryTimeFilter | undefined { if ( - savedQueryMeta.shouldIncludeTimefilter && this.state.dateRangeTo !== undefined && this.state.dateRangeFrom !== undefined && this.props.refreshInterval !== undefined && this.props.isRefreshPaused !== undefined ) { - savedQueryAttributes.timefilter = { + return { from: this.state.dateRangeFrom, to: this.state.dateRangeTo, refreshInterval: { @@ -319,6 +313,26 @@ class SearchBarUI extends C }, }; } + } + + public onSave = async (savedQueryMeta: SavedQueryMeta, saveAsNew = false) => { + if (!this.state.query) return; + + const savedQueryAttributes: SavedQueryAttributes = { + title: savedQueryMeta.title, + description: savedQueryMeta.description, + query: this.state.query as Query, + }; + + if (savedQueryMeta.shouldIncludeFilters) { + savedQueryAttributes.filters = this.props.filters; + } + + const timeFilter = this.getTimeFilter(); + + if (savedQueryMeta.shouldIncludeTimefilter && timeFilter) { + savedQueryAttributes.timefilter = timeFilter; + } try { let response; @@ -332,7 +346,12 @@ class SearchBarUI extends C } this.services.notifications.toasts.addSuccess( - `Your query "${response.attributes.title}" was saved` + i18n.translate('unifiedSearch.search.searchBar.saveQuerySuccessMessage', { + defaultMessage: 'Your query "{queryTitle}" was saved', + values: { + queryTitle: response.attributes.title, + }, + }) ); if (this.props.onSaved) { @@ -340,7 +359,12 @@ class SearchBarUI extends C } } catch (error) { this.services.notifications.toasts.addDanger( - `An error occured while saving your query: ${error.message}` + i18n.translate('unifiedSearch.search.searchBar.saveQueryErrorMessage', { + defaultMessage: 'An error occured while saving your query: {errorMessage}', + values: { + errorMessage: error.message, + }, + }) ); throw error; } @@ -498,6 +522,7 @@ class SearchBarUI extends C onQueryBarSubmit={this.onQueryBarSubmit} dateRangeFrom={this.state.dateRangeFrom} dateRangeTo={this.state.dateRangeTo} + timeFilter={this.getTimeFilter()} savedQueryService={this.savedQueryService} saveAsNewQueryFormComponent={saveAsNewQueryFormComponent} saveFormComponent={saveQueryFormComponent} @@ -528,6 +553,7 @@ class SearchBarUI extends C } suggestionsAbstraction={this.props.suggestionsAbstraction} renderQueryInputAppend={this.props.renderQueryInputAppend} + queryBarMenuRef={this.queryBarMenuRef} /> ) : undefined; @@ -621,14 +647,6 @@ class SearchBarUI extends C ); } - private hasFiltersOrQuery() { - const hasFilters = Boolean(this.props.filters && this.props.filters.length > 0); - const hasQuery = Boolean( - this.state.query && isOfQueryType(this.state.query) && this.state.query.query - ); - return hasFilters || hasQuery; - } - private renderSavedQueryManagement = memoizeOne( ( onClearSavedQuery: SearchBarOwnProps['onClearSavedQuery'], @@ -639,11 +657,11 @@ class SearchBarUI extends C this.setState({ openQueryBarMenu: false })} - hasFiltersOrQuery={this.hasFiltersOrQuery()} /> ); diff --git a/src/plugins/unified_search/public/types.ts b/src/plugins/unified_search/public/types.ts index 73c581e8f4c27..fa74c87884bb3 100755 --- a/src/plugins/unified_search/public/types.ts +++ b/src/plugins/unified_search/public/types.ts @@ -92,6 +92,9 @@ export interface IUnifiedSearchPluginServices extends Partial { notifications: CoreStart['notifications']; application: CoreStart['application']; http: CoreStart['http']; + analytics: CoreStart['analytics']; + i18n: CoreStart['i18n']; + theme: CoreStart['theme']; storage: IStorageWrapper; docLinks: DocLinksStart; data: DataPublicPluginStart; diff --git a/src/plugins/unified_search/tsconfig.json b/src/plugins/unified_search/tsconfig.json index d5842db6d1c58..7a70c4aafe2a3 100644 --- a/src/plugins/unified_search/tsconfig.json +++ b/src/plugins/unified_search/tsconfig.json @@ -44,6 +44,7 @@ "@kbn/ml-string-hash", "@kbn/code-editor", "@kbn/calculate-width-from-char-count", + "@kbn/react-kibana-context-render", ], "exclude": [ "target/**/*", diff --git a/test/accessibility/apps/discover.ts b/test/accessibility/apps/discover.ts index 4deb2acb66d74..8a4dc8de7a52b 100644 --- a/test/accessibility/apps/discover.ts +++ b/test/accessibility/apps/discover.ts @@ -109,7 +109,13 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await PageObjects.discover.clickSavedQueriesPopOver(); await testSubjects.click('saved-query-management-load-button'); await savedQueryManagementComponent.deleteSavedQuery('test'); - await a11y.testAppSnapshot(); + await a11y.testAppSnapshot({ + // The saved query selectable search input has invalid aria attrs after + // the query is deleted and the `emptyMessage` is displayed, and it fails + // with this error, likely because the list is replaced by `emptyMessage`: + // [aria-valid-attr-value]: Ensures all ARIA attributes have valid values + excludeTestSubj: ['saved-query-management-search-input'], + }); }); // adding a11y tests for the new data grid diff --git a/test/api_integration/apis/saved_queries/index.js b/test/api_integration/apis/saved_queries/index.ts similarity index 77% rename from test/api_integration/apis/saved_queries/index.js rename to test/api_integration/apis/saved_queries/index.ts index 6f531e8026940..fd029c8764f01 100644 --- a/test/api_integration/apis/saved_queries/index.js +++ b/test/api_integration/apis/saved_queries/index.ts @@ -6,7 +6,9 @@ * Side Public License, v 1. */ -export default function ({ loadTestFile }) { +import { FtrProviderContext } from '../../ftr_provider_context'; + +export default function ({ loadTestFile }: FtrProviderContext) { describe('Saved queries', () => { loadTestFile(require.resolve('./saved_queries')); }); diff --git a/test/api_integration/apis/saved_queries/saved_queries.js b/test/api_integration/apis/saved_queries/saved_queries.js deleted file mode 100644 index eb3c1465e24de..0000000000000 --- a/test/api_integration/apis/saved_queries/saved_queries.js +++ /dev/null @@ -1,154 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ -import expect from '@kbn/expect'; -import { ELASTIC_HTTP_VERSION_HEADER } from '@kbn/core-http-common'; -import { SAVED_QUERY_BASE_URL } from '@kbn/data-plugin/common'; - -// node scripts/functional_tests --config test/api_integration/config.js --grep="search session" - -const mockSavedQuery = { - title: 'my title', - description: 'my description', - query: { - query: 'foo: bar', - language: 'kql', - }, - filters: [], -}; - -export default function ({ getService }) { - const esArchiver = getService('esArchiver'); - const supertest = getService('supertest'); - void SAVED_QUERY_BASE_URL; - - describe('Saved queries API', function () { - before(async () => { - await esArchiver.emptyKibanaIndex(); - await esArchiver.loadIfNeeded('test/functional/fixtures/es_archiver/logstash_functional'); - }); - - after(async () => { - await esArchiver.unload('test/functional/fixtures/es_archiver/logstash_functional'); - }); - - it('should return 200 for create saved query', () => - supertest - .post(`${SAVED_QUERY_BASE_URL}/_create`) - .set(ELASTIC_HTTP_VERSION_HEADER, '1') - .send(mockSavedQuery) - .expect(200) - .then(({ body }) => { - expect(body.id).to.have.length(36); - expect(body.attributes.title).to.be('my title'); - expect(body.attributes.description).to.be('my description'); - })); - - it('should return 400 for create invalid saved query', () => - supertest - .post(`${SAVED_QUERY_BASE_URL}/_create`) - .set(ELASTIC_HTTP_VERSION_HEADER, '1') - .send({ description: 'my description' }) - .expect(400)); - - it('should return 200 for update saved query', () => - supertest - .post(`${SAVED_QUERY_BASE_URL}/_create`) - .set(ELASTIC_HTTP_VERSION_HEADER, '1') - .send(mockSavedQuery) - .expect(200) - .then(({ body }) => - supertest - .put(`${SAVED_QUERY_BASE_URL}/${body.id}`) - .set(ELASTIC_HTTP_VERSION_HEADER, '1') - .send({ - ...mockSavedQuery, - title: 'my new title', - }) - .expect(200) - .then((res) => { - expect(res.body.id).to.be(body.id); - expect(res.body.attributes.title).to.be('my new title'); - }) - )); - - it('should return 404 for update non-existent saved query', () => - supertest - .put(`${SAVED_QUERY_BASE_URL}/invalid_id`) - .set(ELASTIC_HTTP_VERSION_HEADER, '1') - .send(mockSavedQuery) - .expect(404)); - - it('should return 200 for get saved query', () => - supertest - .post(`${SAVED_QUERY_BASE_URL}/_create`) - .set(ELASTIC_HTTP_VERSION_HEADER, '1') - .send(mockSavedQuery) - .expect(200) - .then(({ body }) => - supertest - .get(`${SAVED_QUERY_BASE_URL}/${body.id}`) - .set(ELASTIC_HTTP_VERSION_HEADER, '1') - .expect(200) - .then((res) => { - expect(res.body.id).to.be(body.id); - expect(res.body.attributes.title).to.be(body.attributes.title); - }) - )); - - it('should return 404 for get non-existent saved query', () => - supertest - .get(`${SAVED_QUERY_BASE_URL}/invalid_id`) - .set(ELASTIC_HTTP_VERSION_HEADER, '1') - .expect(404)); - - it('should return 200 for saved query count', () => - supertest - .get(`${SAVED_QUERY_BASE_URL}/_count`) - .set(ELASTIC_HTTP_VERSION_HEADER, '1') - .expect(200)); - - it('should return 200 for find saved queries', () => - supertest - .post(`${SAVED_QUERY_BASE_URL}/_find`) - .set(ELASTIC_HTTP_VERSION_HEADER, '1') - .send({}) - .expect(200)); - - it('should return 400 for bad find saved queries request', () => - supertest - .post(`${SAVED_QUERY_BASE_URL}/_find`) - .set(ELASTIC_HTTP_VERSION_HEADER, '1') - .send({ foo: 'bar' }) - .expect(400)); - - it('should return 200 for find all saved queries', () => - supertest - .post(`${SAVED_QUERY_BASE_URL}/_all`) - .set(ELASTIC_HTTP_VERSION_HEADER, '1') - .expect(200)); - - it('should return 200 for delete saved query', () => - supertest - .post(`${SAVED_QUERY_BASE_URL}/_create`) - .set(ELASTIC_HTTP_VERSION_HEADER, '1') - .send(mockSavedQuery) - .expect(200) - .then(({ body }) => - supertest - .delete(`${SAVED_QUERY_BASE_URL}/${body.id}`) - .set(ELASTIC_HTTP_VERSION_HEADER, '1') - .expect(200) - )); - - it('should return 404 for get non-existent saved query', () => - supertest - .delete(`${SAVED_QUERY_BASE_URL}/invalid_id`) - .set(ELASTIC_HTTP_VERSION_HEADER, '1') - .expect(404)); - }); -} diff --git a/test/api_integration/apis/saved_queries/saved_queries.ts b/test/api_integration/apis/saved_queries/saved_queries.ts new file mode 100644 index 0000000000000..3134ab6b80fdb --- /dev/null +++ b/test/api_integration/apis/saved_queries/saved_queries.ts @@ -0,0 +1,426 @@ +/* + * 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 expect from '@kbn/expect'; +import { ELASTIC_HTTP_VERSION_HEADER } from '@kbn/core-http-common'; +import { SavedQueryAttributes, SAVED_QUERY_BASE_URL } from '@kbn/data-plugin/common'; +import { FtrProviderContext } from '../../ftr_provider_context'; + +// node scripts/functional_tests --config test/api_integration/config.js --grep="search session" + +const mockSavedQuery: SavedQueryAttributes = { + title: 'my title', + description: 'my description', + query: { + query: 'foo: bar', + language: 'kql', + }, + filters: [], +}; + +export default function ({ getService }: FtrProviderContext) { + const esArchiver = getService('esArchiver'); + const supertest = getService('supertest'); + const kibanaServer = getService('kibanaServer'); + + const createQuery = (query: Partial = mockSavedQuery) => + supertest + .post(`${SAVED_QUERY_BASE_URL}/_create`) + .set(ELASTIC_HTTP_VERSION_HEADER, '1') + .send(query); + + const updateQuery = (id: string, query: Partial = mockSavedQuery) => + supertest + .put(`${SAVED_QUERY_BASE_URL}/${id}`) + .set(ELASTIC_HTTP_VERSION_HEADER, '1') + .send(query); + + const deleteQuery = (id: string) => + supertest.delete(`${SAVED_QUERY_BASE_URL}/${id}`).set(ELASTIC_HTTP_VERSION_HEADER, '1'); + + const getQuery = (id: string) => + supertest.get(`${SAVED_QUERY_BASE_URL}/${id}`).set(ELASTIC_HTTP_VERSION_HEADER, '1'); + + const findQueries = (options: { search?: string; perPage?: number; page?: number } = {}) => + supertest + .post(`${SAVED_QUERY_BASE_URL}/_find`) + .set(ELASTIC_HTTP_VERSION_HEADER, '1') + .send(options); + + const countQueries = () => + supertest.get(`${SAVED_QUERY_BASE_URL}/_count`).set(ELASTIC_HTTP_VERSION_HEADER, '1'); + + const isDuplicateTitle = (title: string, id?: string) => + supertest + .post(`${SAVED_QUERY_BASE_URL}/_is_duplicate_title`) + .set(ELASTIC_HTTP_VERSION_HEADER, '1') + .send({ title, id }); + + describe('Saved queries API', function () { + before(async () => { + await esArchiver.emptyKibanaIndex(); + await esArchiver.loadIfNeeded('test/functional/fixtures/es_archiver/logstash_functional'); + }); + + after(async () => { + await esArchiver.unload('test/functional/fixtures/es_archiver/logstash_functional'); + }); + + afterEach(async () => { + await kibanaServer.savedObjects.clean({ types: ['query'] }); + }); + + describe('create', () => { + it('should return 200 for create saved query', () => + createQuery() + .expect(200) + .then(({ body }) => { + expect(body.id).to.have.length(36); + expect(body.attributes.title).to.be('my title'); + expect(body.attributes.description).to.be('my description'); + })); + + it('should return 400 for create invalid saved query', () => + createQuery({ description: 'my description' }) + .expect(400) + .then(({ body }) => { + expect(body.message).to.be( + '[request body.title]: expected value of type [string] but got [undefined]' + ); + })); + + it('should return 400 for create saved query with duplicate title', () => + createQuery() + .expect(200) + .then(() => + createQuery() + .expect(400) + .then(({ body }) => { + expect(body.message).to.be('Query with title "my title" already exists'); + }) + )); + + it('should leave filters and timefilter undefined if not provided', () => + createQuery({ ...mockSavedQuery, filters: undefined, timefilter: undefined }) + .expect(200) + .then(({ body }) => + getQuery(body.id) + .expect(200) + .then(({ body: body2 }) => { + expect(body.attributes.filters).to.be(undefined); + expect(body.attributes.timefilter).to.be(undefined); + expect(body2.attributes.filters).to.be(undefined); + expect(body2.attributes.timefilter).to.be(undefined); + }) + )); + }); + + describe('update', () => { + it('should return 200 for update saved query', () => + createQuery() + .expect(200) + .then(({ body }) => + updateQuery(body.id, { + ...mockSavedQuery, + title: 'my updated title', + }) + .expect(200) + .then((res) => { + expect(res.body.id).to.be(body.id); + expect(res.body.attributes.title).to.be('my updated title'); + }) + )); + + it('should return 404 for update non-existent saved query', () => + updateQuery('invalid_id').expect(404)); + + it('should return 400 for update saved query with duplicate title', () => + createQuery() + .expect(200) + .then(({ body }) => + createQuery({ ...mockSavedQuery, title: 'my duplicate title' }) + .expect(200) + .then(() => + updateQuery(body.id, { ...mockSavedQuery, title: 'my duplicate title' }) + .expect(400) + .then(({ body: body2 }) => { + expect(body2.message).to.be( + 'Query with title "my duplicate title" already exists' + ); + }) + ) + )); + + it('should remove filters and timefilter if not provided', () => + createQuery({ + ...mockSavedQuery, + filters: [{ meta: {}, query: {} }], + timefilter: { + from: 'now-7d', + to: 'now', + refreshInterval: { + pause: false, + value: 60000, + }, + }, + }) + .expect(200) + .then(({ body }) => + updateQuery(body.id, { + ...mockSavedQuery, + filters: undefined, + timefilter: undefined, + }) + .expect(200) + .then(({ body: body2 }) => + getQuery(body2.id) + .expect(200) + .then(({ body: body3 }) => { + expect(body.attributes.filters).not.to.be(undefined); + expect(body.attributes.timefilter).not.to.be(undefined); + expect(body2.attributes.filters).to.be(undefined); + expect(body2.attributes.timefilter).to.be(undefined); + expect(body3.attributes.filters).to.be(undefined); + expect(body3.attributes.timefilter).to.be(undefined); + }) + ) + )); + }); + + describe('delete', () => { + it('should return 200 for delete saved query', () => + createQuery() + .expect(200) + .then(({ body }) => deleteQuery(body.id).expect(200))); + + it('should return 404 for delete non-existent saved query', () => + deleteQuery('invalid_id').expect(404)); + }); + + describe('get', () => { + it('should return 200 for get saved query', () => + createQuery() + .expect(200) + .then(({ body }) => + getQuery(body.id) + .expect(200) + .then((res) => { + expect(res.body.id).to.be(body.id); + expect(res.body.attributes.title).to.be(body.attributes.title); + }) + )); + + it('should return 404 for get non-existent saved query', () => + getQuery('invalid_id').expect(404)); + }); + + describe('find', () => { + it('should return 200 for find saved queries', () => findQueries().expect(200)); + + it('should return 400 for bad find saved queries request', () => + findQueries({ foo: 'bar' } as any) + .expect(400) + .then(({ body }) => { + expect(body.message).to.be('[request body.foo]: definition for this key is missing'); + })); + + it('should return expected queries for find saved queries', async () => { + await createQuery().expect(200); + + const result = await createQuery({ ...mockSavedQuery, title: 'my title 2' }).expect(200); + + await findQueries() + .expect(200) + .then((res) => { + expect(res.body.total).to.be(2); + expect(res.body.savedQueries.length).to.be(2); + expect(res.body.savedQueries.map((q: any) => q.attributes.title)).to.eql([ + 'my title', + 'my title 2', + ]); + }); + + await deleteQuery(result.body.id).expect(200); + + await findQueries() + .expect(200) + .then((res) => { + expect(res.body.total).to.be(1); + expect(res.body.savedQueries.length).to.be(1); + expect(res.body.savedQueries.map((q: any) => q.attributes.title)).to.eql(['my title']); + }); + }); + + it('should return expected queries for find saved queries with a search', async () => { + await createQuery().expect(200); + await createQuery({ ...mockSavedQuery, title: 'my title 2' }).expect(200); + + const result = await createQuery({ ...mockSavedQuery, title: 'my title 2 again' }).expect( + 200 + ); + + await findQueries({ search: 'itle 2' }) + .expect(200) + .then((res) => { + expect(res.body.total).to.be(2); + expect(res.body.savedQueries.length).to.be(2); + expect(res.body.savedQueries.map((q: any) => q.attributes.title)).to.eql([ + 'my title 2', + 'my title 2 again', + ]); + }); + + await deleteQuery(result.body.id).expect(200); + + await findQueries({ search: 'itle 2' }) + .expect(200) + .then((res) => { + expect(res.body.total).to.be(1); + expect(res.body.savedQueries.length).to.be(1); + expect(res.body.savedQueries.map((q: any) => q.attributes.title)).to.eql([ + 'my title 2', + ]); + }); + }); + + it('should support pagination for find saved queries', async () => { + await createQuery().expect(200); + await createQuery({ ...mockSavedQuery, title: 'my title 2' }).expect(200); + await createQuery({ ...mockSavedQuery, title: 'my title 3' }).expect(200); + + await findQueries({ perPage: 2 }) + .expect(200) + .then((res) => { + expect(res.body.total).to.be(3); + expect(res.body.savedQueries.length).to.be(2); + expect(res.body.savedQueries.map((q: any) => q.attributes.title)).to.eql([ + 'my title', + 'my title 2', + ]); + }); + + await findQueries({ perPage: 2, page: 2 }) + .expect(200) + .then((res) => { + expect(res.body.total).to.be(3); + expect(res.body.savedQueries.length).to.be(1); + expect(res.body.savedQueries.map((q: any) => q.attributes.title)).to.eql([ + 'my title 3', + ]); + }); + }); + + it('should support pagination for find saved queries with a search', async () => { + await createQuery().expect(200); + await createQuery({ ...mockSavedQuery, title: 'my title 2' }).expect(200); + await createQuery({ ...mockSavedQuery, title: 'my title 3' }).expect(200); + await createQuery({ ...mockSavedQuery, title: 'not a match' }).expect(200); + + await findQueries({ perPage: 2, search: 'itle' }) + .expect(200) + .then((res) => { + expect(res.body.total).to.be(3); + expect(res.body.savedQueries.length).to.be(2); + expect(res.body.savedQueries.map((q: any) => q.attributes.title)).to.eql([ + 'my title', + 'my title 2', + ]); + }); + + await findQueries({ perPage: 2, page: 2, search: 'itle' }) + .expect(200) + .then((res) => { + expect(res.body.total).to.be(3); + expect(res.body.savedQueries.length).to.be(1); + expect(res.body.savedQueries.map((q: any) => q.attributes.title)).to.eql([ + 'my title 3', + ]); + }); + }); + + it('should support searching for queries containing special characters', async () => { + await createQuery({ ...mockSavedQuery, title: 'query <> title' }).expect(200); + + await findQueries({ search: 'ry <> ti' }) + .expect(200) + .then((res) => { + expect(res.body.total).to.be(1); + expect(res.body.savedQueries.length).to.be(1); + expect(res.body.savedQueries.map((q: any) => q.attributes.title)).to.eql([ + 'query <> title', + ]); + }); + }); + }); + + describe('count', () => { + it('should return 200 for saved query count', () => countQueries().expect(200)); + + it('should return expected counts for saved query count', async () => { + await countQueries() + .expect(200) + .then((res) => { + expect(res.text).to.be('0'); + }); + + await createQuery().expect(200); + + const result = await createQuery({ ...mockSavedQuery, title: 'my title 2' }).expect(200); + + await countQueries() + .expect(200) + .then((res) => { + expect(res.text).to.be('2'); + }); + + await deleteQuery(result.body.id).expect(200); + + await countQueries() + .expect(200) + .then((res) => { + expect(res.text).to.be('1'); + }); + }); + }); + + describe('isDuplicateTitle', () => { + it('should return isDuplicate = true for _is_duplicate_title check with a duplicate title', () => + createQuery() + .expect(200) + .then(({ body }) => + isDuplicateTitle(body.attributes.title) + .expect(200) + .then(({ body: body2 }) => { + expect(body2.isDuplicate).to.be(true); + }) + )); + + it('should return isDuplicate = false for _is_duplicate_title check with a duplicate title and matching ID', () => + createQuery() + .expect(200) + .then(({ body }) => + isDuplicateTitle(body.attributes.title, body.id) + .expect(200) + .then(({ body: body2 }) => { + expect(body2.isDuplicate).to.be(false); + }) + )); + + it('should return isDuplicate = false for _is_duplicate_title check with a unique title', () => + createQuery() + .expect(200) + .then(() => + isDuplicateTitle('my unique title') + .expect(200) + .then(({ body }) => { + expect(body.isDuplicate).to.be(false); + }) + )); + }); + }); +} diff --git a/test/functional/page_objects/discover_page.ts b/test/functional/page_objects/discover_page.ts index 658e235c77d33..1d81faaf8a7fd 100644 --- a/test/functional/page_objects/discover_page.ts +++ b/test/functional/page_objects/discover_page.ts @@ -641,11 +641,17 @@ export class DiscoverPageObject extends FtrService { } public async saveCurrentSavedQuery() { - await this.testSubjects.click('savedQueryFormSaveButton'); + await this.testSubjects.existOrFail('savedQueryFormSaveButton'); + await this.retry.try(async () => { + if (await this.testSubjects.exists('savedQueryFormSaveButton')) { + await this.testSubjects.click('savedQueryFormSaveButton'); + } + await this.testSubjects.missingOrFail('queryBarMenuPanel'); + }); } public async deleteSavedQuery() { - await this.testSubjects.click('delete-saved-query-TEST-button'); + await this.testSubjects.click('delete-saved-query-button'); } public async confirmDeletionOfSavedQuery() { diff --git a/test/functional/services/saved_query_management_component.ts b/test/functional/services/saved_query_management_component.ts index 7822ed8f77a89..fed8a2e66f601 100644 --- a/test/functional/services/saved_query_management_component.ts +++ b/test/functional/services/saved_query_management_component.ts @@ -87,6 +87,9 @@ export class SavedQueryManagementComponentService extends FtrService { await this.testSubjects.click('saved-query-management-load-button'); await this.testSubjects.click(`~load-saved-query-${title}-button`); await this.testSubjects.click('saved-query-management-apply-changes-button'); + await this.retry.try(async () => { + await this.testSubjects.missingOrFail('queryBarMenuPanel'); + }); await this.retry.try(async () => { await this.openSavedQueryManagementComponent(); const selectedSavedQueryText = await this.testSubjects.getVisibleText('savedQueryTitle'); @@ -105,7 +108,7 @@ export class SavedQueryManagementComponentService extends FtrService { } await this.testSubjects.click(`~load-saved-query-${title}-button`); await this.retry.waitFor('delete saved query', async () => { - await this.testSubjects.click(`delete-saved-query-${title}-button`); + await this.testSubjects.click(`delete-saved-query-button`); const exists = await this.testSubjects.exists('confirmModalTitleText'); return exists === true; }); @@ -149,6 +152,9 @@ export class SavedQueryManagementComponentService extends FtrService { } await this.testSubjects.click('savedQueryFormSaveButton'); + await this.retry.try(async () => { + await this.testSubjects.missingOrFail('saveQueryForm'); + }); } async savedQueryExist(title: string) { @@ -160,8 +166,8 @@ export class SavedQueryManagementComponentService extends FtrService { } async savedQueryExistOrFail(title: string) { - await this.openSavedQueryManagementComponent(); await this.retry.waitFor('load saved query', async () => { + await this.openSavedQueryManagementComponent(); const shouldClickLoadMenu = await this.testSubjects.exists( 'saved-query-management-load-button' ); diff --git a/x-pack/plugins/lens/public/app_plugin/lens_top_nav.tsx b/x-pack/plugins/lens/public/app_plugin/lens_top_nav.tsx index 67f35ef8c99f9..0186804edc814 100644 --- a/x-pack/plugins/lens/public/app_plugin/lens_top_nav.tsx +++ b/x-pack/plugins/lens/public/app_plugin/lens_top_nav.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import { isEqual } from 'lodash'; +import { cloneDeep, isEqual } from 'lodash'; import { i18n } from '@kbn/i18n'; import React, { useCallback, useEffect, useMemo, useState, useRef } from 'react'; import { isOfAggregateQueryType } from '@kbn/es-query'; @@ -856,7 +856,12 @@ export const LensTopNavMenu = ({ const onSavedQueryUpdatedWrapped = useCallback( (newSavedQuery) => { - const savedQueryFilters = newSavedQuery.attributes.filters || []; + // If the user tries to load the same saved query that is already loaded, + // we will receive the same object reference which was previously frozen + // by Redux Toolkit. `filterManager.setFilters` will then try to modify + // the query's filters, which will throw an error. To avoid this, we need + // to clone the filters before passing them to `filterManager.setFilters`. + const savedQueryFilters = cloneDeep(newSavedQuery.attributes.filters || []); const globalFilters = data.query.filterManager.getGlobalFilters(); data.query.filterManager.setFilters([...globalFilters, ...savedQueryFilters]); dispatchSetState({ diff --git a/x-pack/plugins/security_solution/public/common/lib/kibana/kibana_react.mock.ts b/x-pack/plugins/security_solution/public/common/lib/kibana/kibana_react.mock.ts index b2cda32060382..8525f40d47d19 100644 --- a/x-pack/plugins/security_solution/public/common/lib/kibana/kibana_react.mock.ts +++ b/x-pack/plugins/security_solution/public/common/lib/kibana/kibana_react.mock.ts @@ -146,18 +146,17 @@ export const createStartServicesMock = ( ...data.query, savedQueries: { ...data.query.savedQueries, - getAllSavedQueries: jest.fn(() => - Promise.resolve({ - id: '123', - attributes: { - total: 123, - }, - }) - ), findSavedQueries: jest.fn(() => Promise.resolve({ total: 123, - queries: [], + queries: [ + { + id: '123', + attributes: { + total: 123, + }, + }, + ], }) ), }, 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..57e1dee846c0a 100644 --- a/x-pack/plugins/threat_intelligence/public/mocks/test_providers.tsx +++ b/x-pack/plugins/threat_intelligence/public/mocks/test_providers.tsx @@ -71,18 +71,17 @@ const dataServiceMock = { ...data.query, savedQueries: { ...data.query.savedQueries, - getAllSavedQueries: jest.fn(() => - Promise.resolve({ - id: '123', - attributes: { - total: 123, - }, - }) - ), findSavedQueries: jest.fn(() => Promise.resolve({ total: 123, - queries: [], + queries: [ + { + id: '123', + attributes: { + total: 123, + }, + }, + ], }) ), }, diff --git a/x-pack/plugins/translations/translations/fr-FR.json b/x-pack/plugins/translations/translations/fr-FR.json index 9f2a66925c226..de90e90f58e0a 100644 --- a/x-pack/plugins/translations/translations/fr-FR.json +++ b/x-pack/plugins/translations/translations/fr-FR.json @@ -6200,19 +6200,16 @@ "unifiedSearch.queryBarTopRow.submitButton.run": "Exécuter la requête", "unifiedSearch.queryBarTopRow.submitButton.update": "Nécessite une mise à jour", "unifiedSearch.search.searchBar.savedQueryDescriptionText": "Enregistrez le texte et les filtres de la requête que vous souhaitez réutiliser.", - "unifiedSearch.search.searchBar.savedQueryForm.titleConflictText": "Ce nom est en conflit avec une requête existante", "unifiedSearch.search.searchBar.savedQueryForm.titleExistsText": "Un nom est requis.", "unifiedSearch.search.searchBar.savedQueryFormSaveButtonText": "Enregistrer la requête", "unifiedSearch.search.searchBar.savedQueryIncludeFiltersLabelText": "Inclure les filtres", "unifiedSearch.search.searchBar.savedQueryIncludeTimeFilterLabelText": "Inclure le filtre temporel", "unifiedSearch.search.searchBar.savedQueryMultipleNamespacesDeleteWarning": "Cette requête enregistrée est partagée sur plusieurs espaces. Si vous la supprimez, elle disparaît de tous les espaces où elle est partagée. Vous ne pouvez pas annuler cette action.", - "unifiedSearch.search.searchBar.savedQueryNameHelpText": "Le nom ne peut pas contenir d'espace au début ni à la fin, et il doit être unique.", "unifiedSearch.search.searchBar.savedQueryNameLabelText": "Nom", "unifiedSearch.search.searchBar.savedQueryNoSavedQueriesText": "Aucune requête enregistrée.", "unifiedSearch.search.searchBar.savedQueryPopoverApplyFilterSetLabel": "Charger la requête", "unifiedSearch.search.searchBar.savedQueryPopoverConfirmDeletionCancelButtonText": "Annuler", "unifiedSearch.search.searchBar.savedQueryPopoverConfirmDeletionConfirmButtonText": "Supprimer", - "unifiedSearch.search.searchBar.savedQueryPopoverManageLabel": "Gérer les objets enregistrés", "unifiedSearch.search.searchBar.savedQueryPopoverSaveAsNewButtonAriaLabel": "Enregistrer en tant que nouvelle requête", "unifiedSearch.search.searchBar.savedQueryPopoverSaveAsNewButtonText": "Enregistrer en tant que nouvelle", "unifiedSearch.search.searchBar.savedQueryPopoverSaveChangesButtonText": "Mettre à jour la recherche", diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 248e4fddb7f3c..deb0b7bceb6d1 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -6215,19 +6215,16 @@ "unifiedSearch.queryBarTopRow.submitButton.run": "クエリを実行", "unifiedSearch.queryBarTopRow.submitButton.update": "更新が必要です", "unifiedSearch.search.searchBar.savedQueryDescriptionText": "再度使用するクエリテキストとフィルターを保存します。", - "unifiedSearch.search.searchBar.savedQueryForm.titleConflictText": "名前が既存のクエリと競合しています", "unifiedSearch.search.searchBar.savedQueryForm.titleExistsText": "名前が必要です。", "unifiedSearch.search.searchBar.savedQueryFormSaveButtonText": "クエリを保存", "unifiedSearch.search.searchBar.savedQueryIncludeFiltersLabelText": "フィルターを含める", "unifiedSearch.search.searchBar.savedQueryIncludeTimeFilterLabelText": "時間フィルターを含める", "unifiedSearch.search.searchBar.savedQueryMultipleNamespacesDeleteWarning": "この保存されたクエリは複数のスペースで共有されます。削除すると、それが共有されているすべてのスペースから削除されます。この操作は元に戻すことができません。", - "unifiedSearch.search.searchBar.savedQueryNameHelpText": "名前の始めと終わりにはスペースを使用できません。名前は一意でなければなりません。", "unifiedSearch.search.searchBar.savedQueryNameLabelText": "名前", "unifiedSearch.search.searchBar.savedQueryNoSavedQueriesText": "保存されたクエリがありません。", "unifiedSearch.search.searchBar.savedQueryPopoverApplyFilterSetLabel": "クエリを読み込む", "unifiedSearch.search.searchBar.savedQueryPopoverConfirmDeletionCancelButtonText": "キャンセル", "unifiedSearch.search.searchBar.savedQueryPopoverConfirmDeletionConfirmButtonText": "削除", - "unifiedSearch.search.searchBar.savedQueryPopoverManageLabel": "保存されたオブジェクトを管理", "unifiedSearch.search.searchBar.savedQueryPopoverSaveAsNewButtonAriaLabel": "新しいクエリとして保存", "unifiedSearch.search.searchBar.savedQueryPopoverSaveAsNewButtonText": "新規保存", "unifiedSearch.search.searchBar.savedQueryPopoverSaveChangesButtonText": "クエリの更新", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 6c23e8d2f26a3..2393a8e4391db 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -6308,19 +6308,16 @@ "unifiedSearch.queryBarTopRow.submitButton.run": "运行查询", "unifiedSearch.queryBarTopRow.submitButton.update": "需要更新", "unifiedSearch.search.searchBar.savedQueryDescriptionText": "保存想要再次使用的查询文本和筛选。", - "unifiedSearch.search.searchBar.savedQueryForm.titleConflictText": "名称与现有查询有冲突", "unifiedSearch.search.searchBar.savedQueryForm.titleExistsText": "“名称”必填。", "unifiedSearch.search.searchBar.savedQueryFormSaveButtonText": "保存查询", "unifiedSearch.search.searchBar.savedQueryIncludeFiltersLabelText": "包括筛选", "unifiedSearch.search.searchBar.savedQueryIncludeTimeFilterLabelText": "包括时间筛选", "unifiedSearch.search.searchBar.savedQueryMultipleNamespacesDeleteWarning": "此已保存查询将在多个工作区中共享。如果将其删除,则会从进行共享的每个工作区中删除该项。此操作无法撤消。", - "unifiedSearch.search.searchBar.savedQueryNameHelpText": "名称不能包含前导或尾随空格,并且必须唯一。", "unifiedSearch.search.searchBar.savedQueryNameLabelText": "名称", "unifiedSearch.search.searchBar.savedQueryNoSavedQueriesText": "无已保存查询。", "unifiedSearch.search.searchBar.savedQueryPopoverApplyFilterSetLabel": "加载查询", "unifiedSearch.search.searchBar.savedQueryPopoverConfirmDeletionCancelButtonText": "取消", "unifiedSearch.search.searchBar.savedQueryPopoverConfirmDeletionConfirmButtonText": "删除", - "unifiedSearch.search.searchBar.savedQueryPopoverManageLabel": "管理已保存对象", "unifiedSearch.search.searchBar.savedQueryPopoverSaveAsNewButtonAriaLabel": "另存为新查询", "unifiedSearch.search.searchBar.savedQueryPopoverSaveAsNewButtonText": "另存为新的", "unifiedSearch.search.searchBar.savedQueryPopoverSaveChangesButtonText": "更新查询", diff --git a/x-pack/test/security_solution_cypress/cypress/tasks/api_calls/saved_queries.ts b/x-pack/test/security_solution_cypress/cypress/tasks/api_calls/saved_queries.ts index 0ec356e83727c..2105a9b57d9b9 100644 --- a/x-pack/test/security_solution_cypress/cypress/tasks/api_calls/saved_queries.ts +++ b/x-pack/test/security_solution_cypress/cypress/tasks/api_calls/saved_queries.ts @@ -45,7 +45,7 @@ export const deleteSavedQueries = () => { const kibanaIndexUrl = `${Cypress.env('ELASTICSEARCH_URL')}/.kibana_\*`; rootRequest({ method: 'POST', - url: `${kibanaIndexUrl}/_delete_by_query?conflicts=proceed`, + url: `${kibanaIndexUrl}/_delete_by_query?conflicts=proceed&refresh`, body: { query: { bool: { From b965b4c281bc5aa756b92006b4168419d74be3ff Mon Sep 17 00:00:00 2001 From: Ash <1849116+ashokaditya@users.noreply.github.com> Date: Mon, 12 Feb 2024 18:21:01 +0100 Subject: [PATCH 09/83] [Security Solution][Endpoint] Add beta badge to sentinel one connector cards/flyout and responder/isolation action flyouts (#176228) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary Replaces Technical Preview badge with a Beta badge for Sentinel One connector cards and connector form flyouts. Additionally it shows Beta badge on Responder for sentinel one alerts as well as Isolate/Release action flyouts. ### TODO/DONE - [x] Update beta badge tooltip text on all used instances - [x] beta badges on alerts flyout/responder behind feature flag ### Screens ### Connectors list flyout Screenshot 2024-02-05 at 2 14 12 PM ### Sentinel one connector forms #### Create Screenshot 2024-02-05 at 2 14 26 PM #### Edit Screenshot 2024-02-05 at 2 31 04 PM #### Isolate/Release flyout forms for SentinelOne ![Screenshot 2024-02-07 at 1 03 32 PM](https://github.com/elastic/kibana/assets/1849116/92f238e7-4d9c-45aa-8b73-40fdc24dea43) ![Screenshot 2024-02-07 at 1 03 13 PM](https://github.com/elastic/kibana/assets/1849116/ec0b5afc-88f9-424b-992c-5d65076d2490) #### Response Console for SentinelOne ![Screenshot 2024-02-07 at 11 51 22 AM](https://github.com/elastic/kibana/assets/1849116/4b5b9a52-5493-4ae2-8f77-65bed1a7d182) ### 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 - [ ] [Flaky Test Runner](https://ci-stats.kibana.dev/trigger_flaky_test_runner/1) was used on any tests changed - [x] 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)) - [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> --- .../public/common/translations.ts | 5 ++ .../isolate_host/header.test.tsx | 61 ++++++++++++++++--- .../document_details/isolate_host/header.tsx | 45 +++++++++----- .../hooks/use_with_show_responder.tsx | 22 ++++++- .../plugins/security_solution/tsconfig.json | 2 +- .../common/experimental_features.ts | 4 ++ .../public/connector_types/index.ts | 9 ++- .../sentinelone/sentinelone.ts | 10 ++- .../action_type_menu.tsx | 21 ++++--- .../beta_badge_props.tsx | 10 +++ .../create_connector_flyout/header.tsx | 37 +++++++---- .../create_connector_flyout/index.test.tsx | 16 ++--- .../create_connector_flyout/index.tsx | 3 +- .../edit_connector_flyout/header.tsx | 44 ++++++++----- .../edit_connector_flyout/index.test.tsx | 12 ++-- .../edit_connector_flyout/index.tsx | 5 +- .../triggers_actions_ui/public/types.ts | 59 ++++++++++-------- 17 files changed, 259 insertions(+), 106 deletions(-) diff --git a/x-pack/plugins/security_solution/public/common/translations.ts b/x-pack/plugins/security_solution/public/common/translations.ts index 676546b63b224..f3f17b8eb81a5 100644 --- a/x-pack/plugins/security_solution/public/common/translations.ts +++ b/x-pack/plugins/security_solution/public/common/translations.ts @@ -19,6 +19,11 @@ export const BETA = i18n.translate('xpack.securitySolution.pages.common.beta', { defaultMessage: 'Beta', }); +export const BETA_TOOLTIP = i18n.translate('xpack.securitySolution.pages.common.beta.tooltip', { + defaultMessage: + 'This functionality is in beta and is subject to change. The design and code is less mature than official GA features and is being provided as-is with no warranties. Beta features are not subject to the support SLA of official GA features.', +}); + export const UPDATE_ALERT_STATUS_FAILED = (conflicts: number) => i18n.translate('xpack.securitySolution.pages.common.updateAlertStatusFailed', { values: { conflicts }, diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/isolate_host/header.test.tsx b/x-pack/plugins/security_solution/public/flyout/document_details/isolate_host/header.test.tsx index fa4b57a4313fa..6b147f89261e3 100644 --- a/x-pack/plugins/security_solution/public/flyout/document_details/isolate_host/header.test.tsx +++ b/x-pack/plugins/security_solution/public/flyout/document_details/isolate_host/header.test.tsx @@ -9,11 +9,18 @@ import React from 'react'; import { render } from '@testing-library/react'; import { __IntlProvider as IntlProvider } from '@kbn/i18n-react'; import { useIsolateHostPanelContext } from './context'; +import { useIsExperimentalFeatureEnabled } from '../../../common/hooks/use_experimental_features'; import { PanelHeader } from './header'; import { FLYOUT_HEADER_TITLE_TEST_ID } from './test_ids'; +import { isAlertFromSentinelOneEvent } from '../../../common/utils/sentinelone_alert_check'; +jest.mock('../../../common/hooks/use_experimental_features'); +jest.mock('../../../common/utils/sentinelone_alert_check'); jest.mock('./context'); +const mockUseIsExperimentalFeatureEnabled = useIsExperimentalFeatureEnabled as jest.Mock; +const mockIsAlertFromSentinelOneEvent = isAlertFromSentinelOneEvent as jest.Mock; + const renderPanelHeader = () => render( @@ -22,21 +29,57 @@ const renderPanelHeader = () => ); describe('', () => { - (useIsolateHostPanelContext as jest.Mock).mockReturnValue({ isolateAction: 'isolateHost' }); + beforeEach(() => { + mockUseIsExperimentalFeatureEnabled.mockReturnValue(false); + }); + + it.each([ + { + isolateAction: 'isolateHost', + title: 'Isolate host', + }, + { + isolateAction: 'unisolateHost', + title: 'Release host', + }, + ])('should display release host message', ({ isolateAction, title }) => { + (useIsolateHostPanelContext as jest.Mock).mockReturnValue({ isolateAction }); - it('should display isolate host message', () => { const { getByTestId } = renderPanelHeader(); expect(getByTestId(FLYOUT_HEADER_TITLE_TEST_ID)).toBeInTheDocument(); - expect(getByTestId(FLYOUT_HEADER_TITLE_TEST_ID)).toHaveTextContent('Isolate host'); + expect(getByTestId(FLYOUT_HEADER_TITLE_TEST_ID)).toHaveTextContent(title); }); - it('should display release host message', () => { - (useIsolateHostPanelContext as jest.Mock).mockReturnValue({ isolateAction: 'unisolateHost' }); + it.each(['isolateHost', 'unisolateHost'])( + 'should display beta badge on %s host message for SentinelOne alerts', + (action) => { + (useIsolateHostPanelContext as jest.Mock).mockReturnValue({ + isolateAction: action, + }); + mockUseIsExperimentalFeatureEnabled.mockReturnValue(true); + mockIsAlertFromSentinelOneEvent.mockReturnValue(true); - const { getByTestId } = renderPanelHeader(); + const { getByTestId } = renderPanelHeader(); - expect(getByTestId(FLYOUT_HEADER_TITLE_TEST_ID)).toBeInTheDocument(); - expect(getByTestId(FLYOUT_HEADER_TITLE_TEST_ID)).toHaveTextContent('Release host'); - }); + expect(getByTestId(FLYOUT_HEADER_TITLE_TEST_ID)).toBeInTheDocument(); + expect(getByTestId(FLYOUT_HEADER_TITLE_TEST_ID)).toHaveTextContent('Beta'); + } + ); + + it.each(['isolateHost', 'unisolateHost'])( + 'should not display beta badge on %s host message for non-SentinelOne alerts', + (action) => { + (useIsolateHostPanelContext as jest.Mock).mockReturnValue({ + isolateAction: action, + }); + mockUseIsExperimentalFeatureEnabled.mockReturnValue(true); + mockIsAlertFromSentinelOneEvent.mockReturnValue(false); + + const { getByTestId } = renderPanelHeader(); + + expect(getByTestId(FLYOUT_HEADER_TITLE_TEST_ID)).toBeInTheDocument(); + expect(getByTestId(FLYOUT_HEADER_TITLE_TEST_ID)).not.toHaveTextContent('Beta'); + } + ); }); diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/isolate_host/header.tsx b/x-pack/plugins/security_solution/public/flyout/document_details/isolate_host/header.tsx index 77fec7da38456..da1d933a01013 100644 --- a/x-pack/plugins/security_solution/public/flyout/document_details/isolate_host/header.tsx +++ b/x-pack/plugins/security_solution/public/flyout/document_details/isolate_host/header.tsx @@ -5,10 +5,13 @@ * 2.0. */ -import { EuiTitle } from '@elastic/eui'; +import { EuiBetaBadge, EuiFlexGroup, EuiFlexItem, EuiTitle } from '@elastic/eui'; import type { FC } from 'react'; import React from 'react'; import { FormattedMessage } from '@kbn/i18n-react'; +import { useIsExperimentalFeatureEnabled } from '../../../common/hooks/use_experimental_features'; +import { BETA, BETA_TOOLTIP } from '../../../common/translations'; +import { isAlertFromSentinelOneEvent } from '../../../common/utils/sentinelone_alert_check'; import { useIsolateHostPanelContext } from './context'; import { FLYOUT_HEADER_TITLE_TEST_ID } from './test_ids'; import { FlyoutHeader } from '../../shared/components/flyout_header'; @@ -17,20 +20,34 @@ import { FlyoutHeader } from '../../shared/components/flyout_header'; * Document details expandable right section header for the isolate host panel */ export const PanelHeader: FC = () => { - const { isolateAction } = useIsolateHostPanelContext(); + const { isolateAction, dataFormattedForFieldBrowser: data } = useIsolateHostPanelContext(); + const isSentinelOneAlert = isAlertFromSentinelOneEvent({ data }); + const isSentinelOneV1Enabled = useIsExperimentalFeatureEnabled( + 'responseActionsSentinelOneV1Enabled' + ); - const title = - isolateAction === 'isolateHost' ? ( - - ) : ( - - ); + const title = ( + + + {isolateAction === 'isolateHost' ? ( + + ) : ( + + )} + + {isSentinelOneV1Enabled && isSentinelOneAlert && ( + + + + )} + + ); return ( diff --git a/x-pack/plugins/security_solution/public/management/hooks/use_with_show_responder.tsx b/x-pack/plugins/security_solution/public/management/hooks/use_with_show_responder.tsx index cb216aa7b42ad..7db2c97e82151 100644 --- a/x-pack/plugins/security_solution/public/management/hooks/use_with_show_responder.tsx +++ b/x-pack/plugins/security_solution/public/management/hooks/use_with_show_responder.tsx @@ -6,6 +6,8 @@ */ import React, { useCallback } from 'react'; +import { EuiBetaBadge, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; +import { BETA, BETA_TOOLTIP } from '../../common/translations'; import { useLicense } from '../../common/hooks/use_license'; import type { ImmutableArray } from '../../../common/endpoint/types'; import { @@ -26,6 +28,7 @@ import { import { useConsoleManager } from '../components/console'; import { MissingEncryptionKeyCallout } from '../components/missing_encryption_key_callout'; import { RESPONDER_PAGE_TITLE } from './translations'; +import { useIsExperimentalFeatureEnabled } from '../../common/hooks/use_experimental_features'; type ShowResponseActionsConsole = (props: ResponderInfoProps) => void; @@ -50,6 +53,9 @@ export const useWithShowResponder = (): ShowResponseActionsConsole => { const consoleManager = useConsoleManager(); const endpointPrivileges = useUserPrivileges().endpointPrivileges; const isEnterpriseLicense = useLicense().isEnterprise(); + const isSentinelOneV1Enabled = useIsExperimentalFeatureEnabled( + 'responseActionsSentinelOneV1Enabled' + ); return useCallback( (props: ResponderInfoProps) => { @@ -126,7 +132,19 @@ export const useWithShowResponder = (): ShowResponseActionsConsole => { hostName, }, consoleProps, - PageTitleComponent: () => <>{RESPONDER_PAGE_TITLE}, + PageTitleComponent: () => { + if (isSentinelOneV1Enabled && agentType === 'sentinel_one') { + return ( + + {RESPONDER_PAGE_TITLE} + + + + + ); + } + return <>{RESPONDER_PAGE_TITLE}; + }, ActionComponents: endpointPrivileges.canReadActionsLogManagement ? [ActionLogButton] : undefined, @@ -140,6 +158,6 @@ export const useWithShowResponder = (): ShowResponseActionsConsole => { .show(); } }, - [endpointPrivileges, isEnterpriseLicense, consoleManager] + [endpointPrivileges, isEnterpriseLicense, isSentinelOneV1Enabled, consoleManager] ); }; diff --git a/x-pack/plugins/security_solution/tsconfig.json b/x-pack/plugins/security_solution/tsconfig.json index 71721668cecc6..99d6524aa3d7b 100644 --- a/x-pack/plugins/security_solution/tsconfig.json +++ b/x-pack/plugins/security_solution/tsconfig.json @@ -184,6 +184,6 @@ "@kbn/elastic-assistant-common", "@kbn/core-elasticsearch-server-mocks", "@kbn/lens-embeddable-utils", - "@kbn/esql-utils" + "@kbn/esql-utils", ] } diff --git a/x-pack/plugins/stack_connectors/common/experimental_features.ts b/x-pack/plugins/stack_connectors/common/experimental_features.ts index fee440e86b8d5..7bec75eaeb0a9 100644 --- a/x-pack/plugins/stack_connectors/common/experimental_features.ts +++ b/x-pack/plugins/stack_connectors/common/experimental_features.ts @@ -13,7 +13,11 @@ export type ExperimentalFeatures = typeof allowedExperimentalValues; */ export const allowedExperimentalValues = Object.freeze({ isMustacheAutocompleteOn: false, + // set to true to show tech preview badge on sentinel one connector sentinelOneConnectorOn: true, + // set to true to show beta badge on sentinel one connector + // TODO: set to true when 8.13 is ready + sentinelOneConnectorOnBeta: false, }); export type ExperimentalConfigKeys = Array; diff --git a/x-pack/plugins/stack_connectors/public/connector_types/index.ts b/x-pack/plugins/stack_connectors/public/connector_types/index.ts index a2297dac9d6bf..12991bcc4d055 100644 --- a/x-pack/plugins/stack_connectors/public/connector_types/index.ts +++ b/x-pack/plugins/stack_connectors/public/connector_types/index.ts @@ -69,7 +69,14 @@ export function registerConnectorTypes({ connectorTypeRegistry.register(getTinesConnectorType()); connectorTypeRegistry.register(getD3SecurityConnectorType()); - if (ExperimentalFeaturesService.get().sentinelOneConnectorOn) { + // get sentinelOne connector type + // when either feature flag is enabled + if ( + // 8.12 + ExperimentalFeaturesService.get().sentinelOneConnectorOn || + // 8.13 + ExperimentalFeaturesService.get().sentinelOneConnectorOnBeta + ) { connectorTypeRegistry.register(getSentinelOneConnectorType()); } } diff --git a/x-pack/plugins/stack_connectors/public/connector_types/sentinelone/sentinelone.ts b/x-pack/plugins/stack_connectors/public/connector_types/sentinelone/sentinelone.ts index b01fa9fbaed5d..0b92334e90268 100644 --- a/x-pack/plugins/stack_connectors/public/connector_types/sentinelone/sentinelone.ts +++ b/x-pack/plugins/stack_connectors/public/connector_types/sentinelone/sentinelone.ts @@ -11,15 +11,16 @@ import type { ActionTypeModel as ConnectorTypeModel, GenericValidationResult, } from '@kbn/triggers-actions-ui-plugin/public'; +import { getIsExperimentalFeatureEnabled } from '../../common/get_experimental_features'; import { SENTINELONE_CONNECTOR_ID, SENTINELONE_TITLE, SUB_ACTION, } from '../../../common/sentinelone/constants'; import type { + SentinelOneActionParams, SentinelOneConfig, SentinelOneSecrets, - SentinelOneActionParams, } from '../../../common/sentinelone/types'; interface ValidationErrors { @@ -31,11 +32,16 @@ export function getConnectorType(): ConnectorTypeModel< SentinelOneSecrets, SentinelOneActionParams > { + const isSentinelOneBetaBadgeEnabled = getIsExperimentalFeatureEnabled( + 'sentinelOneConnectorOnBeta' + ); + return { id: SENTINELONE_CONNECTOR_ID, actionTypeTitle: SENTINELONE_TITLE, iconClass: lazy(() => import('./logo')), - isExperimental: true, + isBeta: isSentinelOneBetaBadgeEnabled ? true : undefined, + isExperimental: isSentinelOneBetaBadgeEnabled ? undefined : true, selectMessage: i18n.translate( 'xpack.stackConnectors.security.sentinelone.config.selectMessageText', { diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_type_menu.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_type_menu.tsx index f4717bb512a0c..e0ce5dcceec67 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_type_menu.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_type_menu.tsx @@ -6,9 +6,8 @@ */ import React, { useEffect, useState } from 'react'; -import { EuiFlexItem, EuiCard, EuiIcon, EuiFlexGrid, EuiSpacer } from '@elastic/eui'; +import { EuiCard, EuiFlexGrid, EuiFlexItem, EuiIcon, EuiSpacer, EuiToolTip } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import { EuiToolTip } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n-react'; import { ActionType, ActionTypeIndex, ActionTypeRegistryContract } from '../../../types'; import { loadActionTypes } from '../../lib/action_connector_api'; @@ -16,7 +15,7 @@ import { actionTypeCompare } from '../../lib/action_type_compare'; import { checkActionTypeEnabled } from '../../lib/check_action_type_enabled'; import { useKibana } from '../../../common/lib/kibana'; import { SectionLoading } from '../../components/section_loading'; -import { betaBadgeProps } from './beta_badge_props'; +import { betaBadgeProps, technicalPreviewBadgeProps } from './beta_badge_props'; interface Props { onActionTypeChange: (actionType: ActionType) => void; @@ -77,12 +76,13 @@ export const ActionTypeMenu = ({ })(); // eslint-disable-next-line react-hooks/exhaustive-deps }, []); + const registeredActionTypes = Object.entries(actionTypesIndex ?? []) .filter( ([id, details]) => actionTypeRegistry.has(id) && - details.enabledInConfig === true && - !actionTypeRegistry.get(id).hideInUi + !actionTypeRegistry.get(id).hideInUi && + details.enabledInConfig ) .map(([id, actionType]) => { const actionTypeModel = actionTypeRegistry.get(id); @@ -91,6 +91,7 @@ export const ActionTypeMenu = ({ selectMessage: actionTypeModel ? actionTypeModel.selectMessage : '', actionType, name: actionType.name, + isBeta: actionTypeModel.isBeta, isExperimental: actionTypeModel.isExperimental, }; }); @@ -101,7 +102,13 @@ export const ActionTypeMenu = ({ const checkEnabledResult = checkActionTypeEnabled(item.actionType); const card = ( } @@ -117,7 +124,7 @@ export const ActionTypeMenu = ({ return ( {checkEnabledResult.isEnabled && card} - {checkEnabledResult.isEnabled === false && ( + {!checkEnabledResult.isEnabled && ( {card} diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/beta_badge_props.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/beta_badge_props.tsx index ddd8f4b26a032..3e151eb832f1e 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/beta_badge_props.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/beta_badge_props.tsx @@ -8,6 +8,16 @@ import { i18n } from '@kbn/i18n'; export const betaBadgeProps = { + label: i18n.translate('xpack.triggersActionsUI.betaBadgeLabel', { + defaultMessage: 'Beta', + }), + tooltipContent: i18n.translate('xpack.triggersActionsUI.betaBadgeDescription', { + defaultMessage: + 'This functionality is in beta and is subject to change. The design and code is less mature than official GA features and is being provided as-is with no warranties. Beta features are not subject to the support SLA of official GA features.', + }), +}; + +export const technicalPreviewBadgeProps = { label: i18n.translate('xpack.triggersActionsUI.technicalPreviewBadgeLabel', { defaultMessage: 'Technical preview', }), diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/create_connector_flyout/header.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/create_connector_flyout/header.tsx index 2c12431dc12cb..428740e88f66e 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/create_connector_flyout/header.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/create_connector_flyout/header.tsx @@ -8,18 +8,18 @@ import React, { memo } from 'react'; import { EuiBadge, - EuiTitle, + EuiBetaBadge, EuiFlexGroup, EuiFlexItem, + EuiFlyoutHeader, EuiIcon, + EuiSpacer, EuiText, - EuiFlyoutHeader, + EuiTitle, IconType, - EuiSpacer, - EuiBetaBadge, } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n-react'; -import { betaBadgeProps } from '../beta_badge_props'; +import { betaBadgeProps, technicalPreviewBadgeProps } from '../beta_badge_props'; interface Props { icon?: IconType | null; @@ -27,6 +27,7 @@ interface Props { actionTypeMessage?: string | null; compatibility?: string[] | null; isExperimental?: boolean; + isBeta?: boolean; } const FlyoutHeaderComponent: React.FC = ({ @@ -35,6 +36,7 @@ const FlyoutHeaderComponent: React.FC = ({ actionTypeMessage, compatibility, isExperimental, + isBeta, }) => { return ( @@ -61,14 +63,23 @@ const FlyoutHeaderComponent: React.FC = ({ - {actionTypeName && isExperimental && ( - - - - )} + {actionTypeName + ? isBeta && ( + + + + ) + : isExperimental && ( + + + + )} {actionTypeMessage} diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/create_connector_flyout/index.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/create_connector_flyout/index.test.tsx index 554fb9aff3c30..8c6454f9427af 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/create_connector_flyout/index.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/create_connector_flyout/index.test.tsx @@ -9,9 +9,9 @@ import React, { lazy } from 'react'; import { actionTypeRegistryMock } from '../../../action_type_registry.mock'; import userEvent from '@testing-library/user-event'; -import { waitFor, act } from '@testing-library/react'; +import { act, waitFor } from '@testing-library/react'; import CreateConnectorFlyout from '.'; -import { betaBadgeProps } from '../beta_badge_props'; +import { technicalPreviewBadgeProps } from '../beta_badge_props'; import { AppMockRenderer, createAppMockRenderer } from '../../test_utils'; jest.mock('../../../lib/action_connector_api', () => ({ @@ -392,7 +392,7 @@ describe('CreateConnectorFlyout', () => { expect(getByText(`selectMessage-${actionTypeModel.id}`)).toBeInTheDocument(); }); - it('does not show beta badge when isExperimental is undefined', async () => { + it('does not show tech preview badge when isExperimental is undefined', async () => { const { queryByText } = appMockRenderer.render( { /> ); await act(() => Promise.resolve()); - expect(queryByText(betaBadgeProps.label)).not.toBeInTheDocument(); + expect(queryByText(technicalPreviewBadgeProps.label)).not.toBeInTheDocument(); }); - it('does not show beta badge when isExperimental is false', async () => { + it('does not show tech preview badge when isExperimental is false', async () => { actionTypeRegistry.get.mockReturnValue({ ...actionTypeModel, isExperimental: false }); const { queryByText } = appMockRenderer.render( { /> ); await act(() => Promise.resolve()); - expect(queryByText(betaBadgeProps.label)).not.toBeInTheDocument(); + expect(queryByText(technicalPreviewBadgeProps.label)).not.toBeInTheDocument(); }); - it('shows beta badge when isExperimental is true', async () => { + it('shows tech preview badge when isExperimental is true', async () => { actionTypeRegistry.get.mockReturnValue({ ...actionTypeModel, isExperimental: true }); const { getByText } = appMockRenderer.render( { /> ); await act(() => Promise.resolve()); - expect(getByText(betaBadgeProps.label)).toBeInTheDocument(); + expect(getByText(technicalPreviewBadgeProps.label)).toBeInTheDocument(); }); }); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/create_connector_flyout/index.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/create_connector_flyout/index.tsx index 5cf6f6f8de69b..c0c6941e68410 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/create_connector_flyout/index.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/create_connector_flyout/index.tsx @@ -21,8 +21,8 @@ import { FormattedMessage } from '@kbn/i18n-react'; import { ActionConnector, ActionType, - ActionTypeModel, ActionTypeIndex, + ActionTypeModel, ActionTypeRegistryContract, } from '../../../../types'; import { hasSaveActionsCapability } from '../../../lib/capabilities'; @@ -211,6 +211,7 @@ const CreateConnectorFlyoutComponent: React.FC = ({ actionTypeMessage={actionTypeModel?.selectMessage} compatibility={getConnectorCompatibility(actionType?.supportedFeatureIds)} isExperimental={actionTypeModel?.isExperimental} + isBeta={actionTypeModel?.isBeta} /> : null} diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/edit_connector_flyout/header.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/edit_connector_flyout/header.tsx index f60b791df9773..d23322042a610 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/edit_connector_flyout/header.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/edit_connector_flyout/header.tsx @@ -8,26 +8,27 @@ import React, { memo } from 'react'; import { css } from '@emotion/react'; import { - EuiTitle, + EuiBetaBadge, EuiFlexGroup, EuiFlexItem, - EuiIcon, - EuiText, EuiFlyoutHeader, - IconType, - EuiBetaBadge, + EuiIcon, EuiTab, EuiTabs, + EuiText, + EuiTitle, + IconType, useEuiTheme, } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n-react'; import { i18n } from '@kbn/i18n'; -import { betaBadgeProps } from '../beta_badge_props'; +import { betaBadgeProps, technicalPreviewBadgeProps } from '../beta_badge_props'; import { EditConnectorTabs } from '../../../../types'; import { useKibana } from '../../../../common/lib/kibana'; import { hasExecuteActionsCapability } from '../../../lib/capabilities'; const FlyoutHeaderComponent: React.FC<{ + isBeta?: boolean; isExperimental?: boolean; isPreconfigured: boolean; connectorName: string; @@ -38,6 +39,7 @@ const FlyoutHeaderComponent: React.FC<{ icon?: IconType | null; }> = ({ icon, + isBeta = false, isExperimental = false, isPreconfigured, connectorName, @@ -89,11 +91,18 @@ const FlyoutHeaderComponent: React.FC<{ />
- {isExperimental && ( + {isBeta ? ( + ) : ( + isExperimental && ( + + ) )} @@ -117,13 +126,20 @@ const FlyoutHeaderComponent: React.FC<{
- {isExperimental && ( - - - + {isBeta ? ( + + ) : ( + isExperimental && ( + + + + ) )} )} diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/edit_connector_flyout/index.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/edit_connector_flyout/index.test.tsx index 8570250e0d387..001cab3fc0720 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/edit_connector_flyout/index.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/edit_connector_flyout/index.test.tsx @@ -9,10 +9,10 @@ import React, { lazy } from 'react'; import { actionTypeRegistryMock } from '../../../action_type_registry.mock'; import userEvent from '@testing-library/user-event'; -import { waitFor, act } from '@testing-library/react'; +import { act, waitFor } from '@testing-library/react'; import EditConnectorFlyout from '.'; import { ActionConnector, EditConnectorTabs, GenericValidationResult } from '../../../../types'; -import { betaBadgeProps } from '../beta_badge_props'; +import { technicalPreviewBadgeProps } from '../beta_badge_props'; import { AppMockRenderer, createAppMockRenderer } from '../../test_utils'; const updateConnectorResponse = { @@ -311,7 +311,7 @@ describe('EditConnectorFlyout', () => { expect(getByTestId('preconfiguredBadge')).toBeInTheDocument(); }); - it('does not show beta badge when isExperimental is false', async () => { + it('does not show tech preview badge when isExperimental is false', async () => { const { queryByText } = appMockRenderer.render( { /> ); await act(() => Promise.resolve()); - expect(queryByText(betaBadgeProps.label)).not.toBeInTheDocument(); + expect(queryByText(technicalPreviewBadgeProps.label)).not.toBeInTheDocument(); }); - it('shows beta badge when isExperimental is true', async () => { + it('shows tech preview badge when isExperimental is true', async () => { actionTypeRegistry.get.mockReturnValue({ ...actionTypeModel, isExperimental: true }); const { getByText } = appMockRenderer.render( { /> ); await act(() => Promise.resolve()); - expect(getByText(betaBadgeProps.label)).toBeInTheDocument(); + expect(getByText(technicalPreviewBadgeProps.label)).toBeInTheDocument(); }); }); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/edit_connector_flyout/index.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/edit_connector_flyout/index.tsx index b01725c489379..94e582962940b 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/edit_connector_flyout/index.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/edit_connector_flyout/index.tsx @@ -6,11 +6,11 @@ */ import React, { memo, ReactNode, useCallback, useEffect, useRef, useState } from 'react'; -import { EuiFlyout, EuiFlyoutBody, EuiButton, EuiConfirmModal } from '@elastic/eui'; +import { EuiButton, EuiConfirmModal, EuiFlyout, EuiFlyoutBody } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n-react'; import { i18n } from '@kbn/i18n'; import { ActionTypeExecutorResult, isActionTypeExecutorResult } from '@kbn/actions-plugin/common'; -import { Option, none, some } from 'fp-ts/lib/Option'; +import { none, Option, some } from 'fp-ts/lib/Option'; import { ReadOnlyConnectorMessage } from './read_only'; import { ActionConnector, @@ -233,6 +233,7 @@ const EditConnectorFlyoutComponent: React.FC = ({ setTab={handleSetTab} selectedTab={selectedTab} icon={actionTypeModel?.iconClass} + isBeta={actionTypeModel?.isBeta} isExperimental={actionTypeModel?.isExperimental} /> diff --git a/x-pack/plugins/triggers_actions_ui/public/types.ts b/x-pack/plugins/triggers_actions_ui/public/types.ts index ba5afb74ecfd8..a48e53d74d4af 100644 --- a/x-pack/plugins/triggers_actions_ui/public/types.ts +++ b/x-pack/plugins/triggers_actions_ui/public/types.ts @@ -7,54 +7,55 @@ import type { Moment } from 'moment'; import type { ComponentType, ReactNode, RefObject } from 'react'; +import React from 'react'; import type { PublicMethodsOf } from '@kbn/utility-types'; import type { DocLinksStart } from '@kbn/core/public'; +import { HttpSetup } from '@kbn/core/public'; import type { ChartsPluginSetup } from '@kbn/charts-plugin/public'; import type { DataPublicPluginStart } from '@kbn/data-plugin/public'; import type { DataViewsPublicPluginStart } from '@kbn/data-views-plugin/public'; import type { UnifiedSearchPublicPluginStart } from '@kbn/unified-search-plugin/public'; import type { - IconType, - RecursivePartial, EuiDataGridCellValueElementProps, - EuiDataGridToolBarAdditionalControlsOptions, + EuiDataGridColumnCellAction, + EuiDataGridOnColumnResizeHandler, EuiDataGridProps, EuiDataGridRefProps, - EuiDataGridColumnCellAction, + EuiDataGridToolBarAdditionalControlsOptions, EuiDataGridToolBarVisibilityOptions, EuiSuperSelectOption, - EuiDataGridOnColumnResizeHandler, + IconType, + RecursivePartial, } from '@elastic/eui'; -import type { RuleCreationValidConsumer, ValidFeatureId } from '@kbn/rule-data-utils'; import { EuiDataGridColumn, EuiDataGridControlColumn, EuiDataGridSorting } from '@elastic/eui'; -import { HttpSetup } from '@kbn/core/public'; +import type { RuleCreationValidConsumer, ValidFeatureId } from '@kbn/rule-data-utils'; import { KueryNode } from '@kbn/es-query'; import { ActionType, - AlertHistoryEsIndexConnectorId, - AlertHistoryDocumentTemplate, ALERT_HISTORY_PREFIX, AlertHistoryDefaultIndexName, + AlertHistoryDocumentTemplate, + AlertHistoryEsIndexConnectorId, AsApiContract, } from '@kbn/actions-plugin/common'; import { ActionGroup, - RuleActionParam, - SanitizedRule as AlertingSanitizedRule, - ResolvedSanitizedRule, - RuleAction, - RuleTaskState, + ActionVariable, + AlertingFrameworkHealth, + AlertStatus, AlertSummary as RuleSummary, ExecutionDuration, - AlertStatus, + MaintenanceWindow, RawAlertInstance, - AlertingFrameworkHealth, + ResolvedSanitizedRule, + RuleAction, + RuleActionParam, + RuleLastRun, RuleNotifyWhenType, - RuleTypeParams, + RuleTaskState, RuleTypeMetaData, - ActionVariable, - RuleLastRun, - MaintenanceWindow, + RuleTypeParams, + SanitizedRule as AlertingSanitizedRule, } from '@kbn/alerting-plugin/common'; import type { BulkOperationError } from '@kbn/alerting-plugin/server'; import { RuleRegistrySearchRequestPagination } from '@kbn/rule-registry-plugin/common'; @@ -63,7 +64,6 @@ import { QueryDslQueryContainer, SortCombinations, } from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; -import React from 'react'; import { ActionsPublicPluginSetup } from '@kbn/actions-plugin/public'; import type { RuleType, RuleTypeIndex } from '@kbn/triggers-actions-ui-types'; import { TypeRegistry } from './application/type_registry'; @@ -72,23 +72,23 @@ import type { RuleTagFilterProps } from './application/sections/rules_list/compo import type { RuleStatusFilterProps } from './application/sections/rules_list/components/rule_status_filter'; import type { RulesListProps } from './application/sections/rules_list/components/rules_list'; import type { - RuleTagBadgeProps, RuleTagBadgeOptions, + RuleTagBadgeProps, } from './application/sections/rules_list/components/rule_tag_badge'; import type { - RuleEventLogListProps, RuleEventLogListOptions, + RuleEventLogListProps, } from './application/sections/rule_details/components/rule_event_log_list'; import type { GlobalRuleEventLogListProps } from './application/sections/rule_details/components/global_rule_event_log_list'; import type { AlertSummaryTimeRange } from './application/sections/alert_summary_widget/types'; import type { CreateConnectorFlyoutProps } from './application/sections/action_connector_form/create_connector_flyout'; import type { EditConnectorFlyoutProps } from './application/sections/action_connector_form/edit_connector_flyout'; import type { - FieldBrowserOptions, + BrowserFieldItem, CreateFieldComponent, - GetFieldTableColumns, + FieldBrowserOptions, FieldBrowserProps, - BrowserFieldItem, + GetFieldTableColumns, } from './application/sections/field_browser/types'; import { RulesListVisibleColumns } from './application/sections/rules_list/components/rules_list_column_selector'; import { TimelineItem } from './application/sections/alerts_table/bulk_actions/components/toolbar'; @@ -173,11 +173,13 @@ export interface ConnectorValidationError { } export type ConnectorValidationFunc = () => Promise; + export interface ActionConnectorFieldsProps { readOnly: boolean; isEdit: boolean; registerPreSubmitValidator: (validator: ConnectorValidationFunc) => void; } + export interface ActionReadOnlyElementProps { connectorId: string; connectorName: string; @@ -209,10 +211,12 @@ interface BulkOperationAttributesByIds { ids: string[]; filter?: never; } + interface BulkOperationAttributesByFilter { ids?: never; filter: KueryNode | null; } + export type BulkOperationAttributesWithoutHttp = | BulkOperationAttributesByIds | BulkOperationAttributesByFilter; @@ -282,6 +286,7 @@ export interface ActionTypeModel; defaultRecoveredActionParams?: RecursivePartial; customConnectorSelectItem?: CustomConnectorSelectionItem; + isBeta?: boolean; isExperimental?: boolean; subtype?: Array<{ id: string; name: string }>; convertParamsBetweenGroups?: (params: ActionParams) => ActionParams | {}; @@ -471,6 +476,7 @@ export interface RuleAddProps< useRuleProducer?: boolean; initialSelectedConsumer?: RuleCreationValidConsumer | null; } + export interface RuleDefinitionProps { rule: Rule; ruleTypeRegistry: RuleTypeRegistryContract; @@ -505,6 +511,7 @@ export interface InspectQuery { request: string[]; response: string[]; } + export type GetInspectQuery = () => InspectQuery; export type Alert = EcsFieldsResponse; From b64dc62a1c31b1285ee59173e2784fd13d3e7169 Mon Sep 17 00:00:00 2001 From: Sander Philipse <94373878+sphilipse@users.noreply.github.com> Date: Mon, 12 Feb 2024 18:26:27 +0100 Subject: [PATCH 10/83] [Search] Don't skip ahead if connector doesn't have config items (#176725) ## Summary This fixes an issue where connectors config would skip ahead on setting a service type. --- .../connectors/connector_config/connector_configuration.tsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/x-pack/plugins/serverless_search/public/application/components/connectors/connector_config/connector_configuration.tsx b/x-pack/plugins/serverless_search/public/application/components/connectors/connector_config/connector_configuration.tsx index aedbe9f450c7f..f4634cbfc91e7 100644 --- a/x-pack/plugins/serverless_search/public/application/components/connectors/connector_config/connector_configuration.tsx +++ b/x-pack/plugins/serverless_search/public/application/components/connectors/connector_config/connector_configuration.tsx @@ -36,13 +36,14 @@ export const ConnectorConfiguration: React.FC = ({ const step = connector.status === ConnectorStatus.CREATED ? 'link' - : connector.status === ConnectorStatus.NEEDS_CONFIGURATION + : connector.status === ConnectorStatus.NEEDS_CONFIGURATION && + Object.keys(connector.configuration || {}).length > 0 ? 'configure' : connector.status === ConnectorStatus.CONFIGURED ? 'connect' : 'connected'; setCurrentStep(step); - }, [connector.status, setCurrentStep]); + }, [connector.status, setCurrentStep, connector.configuration]); const steps: EuiStepsHorizontalProps['steps'] = [ { title: i18n.translate('xpack.serverlessSearch.connectors.config.linkToElasticTitle', { From bc3346f078427b6ed2d32c40a6378a07fc7f2a6a Mon Sep 17 00:00:00 2001 From: Jordan <51442161+JordanSh@users.noreply.github.com> Date: Mon, 12 Feb 2024 19:36:40 +0200 Subject: [PATCH 11/83] [Cloud Security] Evaluated column buttons (#176498) --- .../use_benchmark_dynamic_values.test.ts | 100 ++++++++++++ .../hooks/use_benchmark_dynamic_values.ts | 149 ++++++++++++++++++ .../pages/benchmarks/benchmarks_table.tsx | 100 ++++++------ .../public/pages/rules/rules_counters.tsx | 92 ++--------- .../translations/translations/fr-FR.json | 2 +- .../translations/translations/ja-JP.json | 2 +- .../translations/translations/zh-CN.json | 2 +- 7 files changed, 317 insertions(+), 130 deletions(-) create mode 100644 x-pack/plugins/cloud_security_posture/public/common/hooks/use_benchmark_dynamic_values.test.ts create mode 100644 x-pack/plugins/cloud_security_posture/public/common/hooks/use_benchmark_dynamic_values.ts diff --git a/x-pack/plugins/cloud_security_posture/public/common/hooks/use_benchmark_dynamic_values.test.ts b/x-pack/plugins/cloud_security_posture/public/common/hooks/use_benchmark_dynamic_values.test.ts new file mode 100644 index 0000000000000..6207885b60ab0 --- /dev/null +++ b/x-pack/plugins/cloud_security_posture/public/common/hooks/use_benchmark_dynamic_values.test.ts @@ -0,0 +1,100 @@ +/* + * 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 { useBenchmarkDynamicValues } from './use_benchmark_dynamic_values'; +import { renderHook } from '@testing-library/react-hooks/dom'; +import { useCspIntegrationLink } from '../navigation/use_csp_integration_link'; +import { BenchmarksCisId } from '../../../common/types/benchmarks/v2'; + +jest.mock('../navigation/use_csp_integration_link'); + +describe('useBenchmarkDynamicValues', () => { + const setupMocks = (cspmIntegrationLink: string, kspmIntegrationLink: string) => { + (useCspIntegrationLink as jest.Mock) + .mockReturnValueOnce(cspmIntegrationLink) + .mockReturnValueOnce(kspmIntegrationLink); + }; + + it('should return the correct dynamic benchmark values for each provided benchmark ID', () => { + setupMocks('cspm-integration-link', 'kspm-integration-link'); + const { result } = renderHook(() => useBenchmarkDynamicValues()); + + const benchmarkValuesCisAws = result.current.getBenchmarkDynamicValues('cis_aws', 3); + expect(benchmarkValuesCisAws).toEqual({ + integrationType: 'CSPM', + integrationName: 'AWS', + resourceName: 'Accounts', + resourceCountLabel: 'accounts', + integrationLink: 'cspm-integration-link', + learnMoreLink: 'https://ela.st/cspm-get-started', + }); + + const benchmarkValuesCisGcp = result.current.getBenchmarkDynamicValues('cis_gcp', 0); + expect(benchmarkValuesCisGcp).toEqual({ + integrationType: 'CSPM', + integrationName: 'GCP', + resourceName: 'Projects', + resourceCountLabel: 'projects', + integrationLink: 'cspm-integration-link', + learnMoreLink: 'https://ela.st/cspm-get-started', + }); + + const benchmarkValuesCisAzure = result.current.getBenchmarkDynamicValues('cis_azure', 1); + expect(benchmarkValuesCisAzure).toEqual({ + integrationType: 'CSPM', + integrationName: 'Azure', + resourceName: 'Subscriptions', + resourceCountLabel: 'subscription', + integrationLink: 'cspm-integration-link', + learnMoreLink: 'https://ela.st/cspm-get-started', + }); + + const benchmarkValuesCisK8s = result.current.getBenchmarkDynamicValues('cis_k8s', 0); + expect(benchmarkValuesCisK8s).toEqual({ + integrationType: 'KSPM', + integrationName: 'Kubernetes', + resourceName: 'Clusters', + resourceCountLabel: 'clusters', + integrationLink: 'kspm-integration-link', + learnMoreLink: 'https://ela.st/kspm-get-started', + }); + + const benchmarkValuesCisEks = result.current.getBenchmarkDynamicValues('cis_eks'); + expect(benchmarkValuesCisEks).toEqual({ + integrationType: 'KSPM', + integrationName: 'EKS', + resourceName: 'Clusters', + resourceCountLabel: 'clusters', + integrationLink: 'kspm-integration-link', + learnMoreLink: 'https://ela.st/kspm-get-started', + }); + + const benchmarkValuesInvalid = result.current.getBenchmarkDynamicValues( + 'invalid_benchmark' as BenchmarksCisId + ); + expect(benchmarkValuesInvalid).toEqual({}); + }); + + it('should return the correct resource plurals based on the provided resource count', () => { + const { result } = renderHook(() => useBenchmarkDynamicValues()); + + const benchmarkValuesCisAws = result.current.getBenchmarkDynamicValues('cis_aws', 3); + expect(benchmarkValuesCisAws.resourceCountLabel).toBe('accounts'); + + const benchmarkValuesCisGcp = result.current.getBenchmarkDynamicValues('cis_gcp', 0); + expect(benchmarkValuesCisGcp.resourceCountLabel).toBe('projects'); + + const benchmarkValuesCisAzure = result.current.getBenchmarkDynamicValues('cis_azure', 1); + expect(benchmarkValuesCisAzure.resourceCountLabel).toBe('subscription'); + + const benchmarkValuesCisK8s = result.current.getBenchmarkDynamicValues('cis_k8s', 0); + expect(benchmarkValuesCisK8s.resourceCountLabel).toBe('clusters'); + + const benchmarkValuesCisEks = result.current.getBenchmarkDynamicValues('cis_eks'); + expect(benchmarkValuesCisEks.resourceCountLabel).toBe('clusters'); + }); +}); diff --git a/x-pack/plugins/cloud_security_posture/public/common/hooks/use_benchmark_dynamic_values.ts b/x-pack/plugins/cloud_security_posture/public/common/hooks/use_benchmark_dynamic_values.ts new file mode 100644 index 0000000000000..5fe3f8f69050a --- /dev/null +++ b/x-pack/plugins/cloud_security_posture/public/common/hooks/use_benchmark_dynamic_values.ts @@ -0,0 +1,149 @@ +/* + * 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 { i18n } from '@kbn/i18n'; +import { useCspIntegrationLink } from '../navigation/use_csp_integration_link'; +import { CSPM_POLICY_TEMPLATE, KSPM_POLICY_TEMPLATE } from '../../../common/constants'; +import { BenchmarksCisId } from '../../../common/types/benchmarks/v2'; + +type BenchmarkDynamicNames = + | { + integrationType: 'CSPM'; + integrationName: 'AWS'; + resourceName: 'Accounts'; + } + | { + integrationType: 'CSPM'; + integrationName: 'GCP'; + resourceName: 'Projects'; + } + | { + integrationType: 'CSPM'; + integrationName: 'Azure'; + resourceName: 'Subscriptions'; + } + | { + integrationType: 'KSPM'; + integrationName: 'Kubernetes'; + resourceName: 'Clusters'; + } + | { + integrationType: 'KSPM'; + integrationName: 'EKS'; + resourceName: 'Clusters'; + }; + +export type BenchmarkDynamicValues = BenchmarkDynamicNames & { + resourceCountLabel: string; + integrationLink: string; + learnMoreLink: string; +}; + +export type GetBenchmarkDynamicValues = ( + benchmarkId: BenchmarksCisId, + resourceCount?: number +) => BenchmarkDynamicValues; + +export const useBenchmarkDynamicValues = () => { + const cspmIntegrationLink = useCspIntegrationLink(CSPM_POLICY_TEMPLATE) || ''; + const kspmIntegrationLink = useCspIntegrationLink(KSPM_POLICY_TEMPLATE) || ''; + + /** + * Retrieves dynamic benchmark values based on the provided benchmark ID and resource count. + * + * @param {BenchmarksCisId} benchmarkId - The benchmark ID. + * @param {number} [resourceCount] - The count of resources (optional). + * @returns {BenchmarkDynamicValues} The dynamic benchmark values including integration details, + * resource name, resource count label in plurals/singular, integration link, and learn more link. + * + * @example + * const benchmarkValues = getBenchmarkDynamicValues('cis_aws', 3); + * // Returns: + * // { + * // integrationType: 'CSPM', + * // integrationName: 'AWS', + * // resourceName: 'Accounts', + * // resourceCountLabel: 'accounts', + * // integrationLink: 'cspm-integration-link', + * // learnMoreLink: 'https://ela.st/cspm-get-started' + * // } + */ + const getBenchmarkDynamicValues: GetBenchmarkDynamicValues = ( + benchmarkId: BenchmarksCisId, + resourceCount?: number + ): BenchmarkDynamicValues => { + switch (benchmarkId) { + case 'cis_aws': + return { + integrationType: 'CSPM', + integrationName: 'AWS', + resourceName: 'Accounts', + resourceCountLabel: i18n.translate('xpack.csp.benchmarkDynamicValues.AwsAccountPlural', { + defaultMessage: '{resourceCount, plural, one {account} other {accounts}}', + values: { resourceCount: resourceCount || 0 }, + }), + integrationLink: cspmIntegrationLink, + learnMoreLink: 'https://ela.st/cspm-get-started', + }; + case 'cis_gcp': + return { + integrationType: 'CSPM', + integrationName: 'GCP', + resourceName: 'Projects', + resourceCountLabel: i18n.translate('xpack.csp.benchmarkDynamicValues.GcpAccountPlural', { + defaultMessage: '{resourceCount, plural, one {project} other {projects}}', + values: { resourceCount: resourceCount || 0 }, + }), + integrationLink: cspmIntegrationLink, + learnMoreLink: 'https://ela.st/cspm-get-started', + }; + case 'cis_azure': + return { + integrationType: 'CSPM', + integrationName: 'Azure', + resourceName: 'Subscriptions', + resourceCountLabel: i18n.translate( + 'xpack.csp.benchmarkDynamicValues.AzureAccountPlural', + { + defaultMessage: '{resourceCount, plural, one {subscription} other {subscriptions}}', + values: { resourceCount: resourceCount || 0 }, + } + ), + integrationLink: cspmIntegrationLink, + learnMoreLink: 'https://ela.st/cspm-get-started', + }; + case 'cis_k8s': + return { + integrationType: 'KSPM', + integrationName: 'Kubernetes', + resourceName: 'Clusters', + resourceCountLabel: i18n.translate('xpack.csp.benchmarkDynamicValues.K8sAccountPlural', { + defaultMessage: '{resourceCount, plural, one {cluster} other {clusters}}', + values: { resourceCount: resourceCount || 0 }, + }), + integrationLink: kspmIntegrationLink, + learnMoreLink: 'https://ela.st/kspm-get-started', + }; + case 'cis_eks': + return { + integrationType: 'KSPM', + integrationName: 'EKS', + resourceName: 'Clusters', + resourceCountLabel: i18n.translate('xpack.csp.benchmarkDynamicValues.EksAccountPlural', { + defaultMessage: '{resourceCount, plural, one {cluster} other {clusters}}', + values: { resourceCount: resourceCount || 0 }, + }), + integrationLink: kspmIntegrationLink, + learnMoreLink: 'https://ela.st/kspm-get-started', + }; + default: + return {} as BenchmarkDynamicValues; + } + }; + + return { getBenchmarkDynamicValues }; +}; diff --git a/x-pack/plugins/cloud_security_posture/public/pages/benchmarks/benchmarks_table.tsx b/x-pack/plugins/cloud_security_posture/public/pages/benchmarks/benchmarks_table.tsx index 25690410a9780..32cb0c6f11f98 100644 --- a/x-pack/plugins/cloud_security_posture/public/pages/benchmarks/benchmarks_table.tsx +++ b/x-pack/plugins/cloud_security_posture/public/pages/benchmarks/benchmarks_table.tsx @@ -15,12 +15,14 @@ import { EuiFlexGroup, EuiFlexItem, EuiLink, + EuiButtonEmpty, } from '@elastic/eui'; import React, { useMemo } from 'react'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n-react'; import { generatePath } from 'react-router-dom'; -import { useKibana } from '@kbn/kibana-react-plugin/public'; +import { FINDINGS_GROUPING_OPTIONS } from '../../common/constants'; +import { useNavigateFindings } from '../../common/hooks/use_navigate_findings'; import type { BenchmarkScore, Benchmark, BenchmarksCisId } from '../../../common/types/latest'; import * as TEST_SUBJ from './test_subjects'; import { isCommonError } from '../../components/cloud_posture_page'; @@ -29,6 +31,11 @@ import { ComplianceScoreBar } from '../../components/compliance_score_bar'; import { getBenchmarkCisName, getBenchmarkApplicableTo } from '../../../common/utils/helpers'; import { CISBenchmarkIcon } from '../../components/cis_benchmark_icon'; import { benchmarksNavigation } from '../../common/navigation/constants'; +import { + GetBenchmarkDynamicValues, + useBenchmarkDynamicValues, +} from '../../common/hooks/use_benchmark_dynamic_values'; +import { useKibana } from '../../common/hooks/use_kibana'; export const ERROR_STATE_TEST_SUBJECT = 'benchmark_page_error'; @@ -62,51 +69,6 @@ const BenchmarkButtonLink = ({ ); }; -export const getBenchmarkPlurals = (benchmarkId: string, accountEvaluation: number) => { - switch (benchmarkId) { - case 'cis_k8s': - return ( - - ); - case 'cis_azure': - return ( - - ); - case 'cis_aws': - return ( - - ); - case 'cis_eks': - return ( - - ); - case 'cis_gcp': - return ( - - ); - } -}; - const ErrorMessageComponent = (error: { error: unknown }) => ( ( ); -const BENCHMARKS_TABLE_COLUMNS: Array> = [ +const getBenchmarkTableColumns = ( + getBenchmarkDynamicValues: GetBenchmarkDynamicValues, + navToFindings: any +): Array> => [ { field: 'id', name: i18n.translate('xpack.csp.benchmarks.benchmarksTable.integrationBenchmarkCisName', { @@ -198,7 +163,40 @@ const BENCHMARKS_TABLE_COLUMNS: Array> = [ width: '17.5%', 'data-test-subj': TEST_SUBJ.BENCHMARKS_TABLE_COLUMNS.EVALUATED, render: (benchmarkEvaluation: Benchmark['evaluation'], benchmark: Benchmark) => { - return getBenchmarkPlurals(benchmark.id, benchmarkEvaluation); + const { resourceCountLabel, integrationLink } = getBenchmarkDynamicValues( + benchmark.id, + benchmarkEvaluation + ); + + if (benchmarkEvaluation === 0) { + return ( + + {i18n.translate('xpack.csp.benchmarks.benchmarksTable.addIntegrationTitle', { + defaultMessage: 'Add {resourceCountLabel}', + values: { resourceCountLabel }, + })} + + ); + } + + const isKspmBenchmark = ['cis_k8s', 'cis_eks'].includes(benchmark.id); + const groupByField = isKspmBenchmark + ? FINDINGS_GROUPING_OPTIONS.ORCHESTRATOR_CLUSTER_NAME + : FINDINGS_GROUPING_OPTIONS.CLOUD_ACCOUNT_NAME; + + return ( + { + navToFindings({ 'rule.benchmark.id': benchmark.id }, [groupByField]); + }} + > + {i18n.translate('xpack.csp.benchmarks.benchmarksTable.accountsCountTitle', { + defaultMessage: '{benchmarkEvaluation} {resourceCountLabel}', + values: { benchmarkEvaluation, resourceCountLabel }, + })} + + ); }, }, { @@ -215,6 +213,7 @@ const BENCHMARKS_TABLE_COLUMNS: Array> = [ return ( ); + return ( { + const { getBenchmarkDynamicValues } = useBenchmarkDynamicValues(); + const navToFindings = useNavigateFindings(); + const pagination: Pagination = { pageIndex: Math.max(pageIndex - 1, 0), pageSize, @@ -261,7 +263,7 @@ export const BenchmarksTable = ({ [item.id, item.version].join('/')} pagination={pagination} onChange={onChange} diff --git a/x-pack/plugins/cloud_security_posture/public/pages/rules/rules_counters.tsx b/x-pack/plugins/cloud_security_posture/public/pages/rules/rules_counters.tsx index 9c311406fe172..ec8d4a653c222 100644 --- a/x-pack/plugins/cloud_security_posture/public/pages/rules/rules_counters.tsx +++ b/x-pack/plugins/cloud_security_posture/public/pages/rules/rules_counters.tsx @@ -18,19 +18,13 @@ import { i18n } from '@kbn/i18n'; import { useParams } from 'react-router-dom'; import { Chart, Partition, PartitionLayout, Settings } from '@elastic/charts'; import { FormattedMessage } from '@kbn/i18n-react'; +import { useBenchmarkDynamicValues } from '../../common/hooks/use_benchmark_dynamic_values'; import { getPostureScorePercentage } from '../compliance_dashboard/compliance_charts/compliance_score_chart'; import { RULE_COUNTERS_TEST_SUBJ } from './test_subjects'; import noDataIllustration from '../../assets/illustrations/no_data_illustration.svg'; -import { BenchmarksCisId } from '../../../common/types/benchmarks/v2'; -import { useCspIntegrationLink } from '../../common/navigation/use_csp_integration_link'; import { useNavigateFindings } from '../../common/hooks/use_navigate_findings'; import { cloudPosturePages } from '../../common/navigation/constants'; -import { - CSPM_POLICY_TEMPLATE, - KSPM_POLICY_TEMPLATE, - RULE_FAILED, - RULE_PASSED, -} from '../../../common/constants'; +import { RULE_FAILED, RULE_PASSED } from '../../../common/constants'; import { statusColors } from '../../common/constants'; import { useCspBenchmarkIntegrationsV2 } from '../benchmarks/use_csp_benchmark_integrations'; import { CspCounterCard } from '../../components/csp_counter_card'; @@ -98,11 +92,10 @@ export const RulesCounters = ({ setEnabledDisabledItemsFilter: (filterState: string) => void; }) => { const { http } = useKibana().services; + const { getBenchmarkDynamicValues } = useBenchmarkDynamicValues(); const rulesPageParams = useParams<{ benchmarkId: string; benchmarkVersion: string }>(); const getBenchmarks = useCspBenchmarkIntegrationsV2(); const navToFindings = useNavigateFindings(); - const cspmIntegrationLink = useCspIntegrationLink(CSPM_POLICY_TEMPLATE) || ''; - const kspmIntegrationLink = useCspIntegrationLink(KSPM_POLICY_TEMPLATE) || ''; const benchmarkRulesStats = getBenchmarks.data?.items.find( (benchmark) => @@ -114,52 +107,7 @@ export const RulesCounters = ({ return <>; } - const benchmarkDynamicValues: Record< - BenchmarksCisId, - { - integrationType: string; - integrationName: string; - resourceName: string; - integrationLink: string; - learnMoreLink: string; - } - > = { - cis_aws: { - integrationType: 'CSPM', - integrationName: 'AWS', - resourceName: 'Accounts', - integrationLink: cspmIntegrationLink, - learnMoreLink: 'https://ela.st/cspm-get-started', - }, - cis_gcp: { - integrationType: 'CSPM', - integrationName: 'GCP', - resourceName: 'Projects', - integrationLink: cspmIntegrationLink, - learnMoreLink: 'https://ela.st/cspm-get-started', - }, - cis_azure: { - integrationType: 'CSPM', - integrationName: 'Azure', - resourceName: 'Subscriptions', - integrationLink: cspmIntegrationLink, - learnMoreLink: 'https://ela.st/cspm-get-started', - }, - cis_k8s: { - integrationType: 'KSPM', - integrationName: 'Kubernetes', - resourceName: 'Clusters', - integrationLink: kspmIntegrationLink, - learnMoreLink: 'https://ela.st/kspm-get-started', - }, - cis_eks: { - integrationType: 'KSPM', - integrationName: 'EKS', - resourceName: 'Clusters', - integrationLink: kspmIntegrationLink, - learnMoreLink: 'https://ela.st/kspm-get-started', - }, - }; + const benchmarkValues = getBenchmarkDynamicValues(benchmarkRulesStats.id); if (benchmarkRulesStats.score.totalFindings === 0) { return ( @@ -182,10 +130,8 @@ export const RulesCounters = ({ id="xpack.csp.rulesPage.rulesCounterEmptyState.emptyStateTitle" defaultMessage="Add {integrationResourceName} to get started" values={{ - integrationResourceName: `${ - benchmarkDynamicValues[benchmarkRulesStats.id].integrationName - } - ${benchmarkDynamicValues[benchmarkRulesStats.id].resourceName}`, + integrationResourceName: `${benchmarkValues.integrationName} + ${benchmarkValues.resourceName}`, }} /> @@ -196,32 +142,23 @@ export const RulesCounters = ({ id="xpack.csp.rulesPage.rulesCounterEmptyState.emptyStateDescription" defaultMessage="Add your {resourceName} in {integrationType} to begin detecing misconfigurations" values={{ - resourceName: - benchmarkDynamicValues[benchmarkRulesStats.id].resourceName.toLowerCase(), - integrationType: benchmarkDynamicValues[benchmarkRulesStats.id].integrationType, + resourceName: benchmarkValues.resourceName.toLowerCase(), + integrationType: benchmarkValues.integrationType, }} />

} actions={[ - + , - + {i18n.translate('xpack.csp.rulesCounters.accountsEvaluatedButton', { defaultMessage: 'Add more {resourceName}', values: { - resourceName: - benchmarkDynamicValues[benchmarkRulesStats.id].resourceName.toLowerCase(), + resourceName: benchmarkValues.resourceName.toLowerCase(), }, })} diff --git a/x-pack/plugins/translations/translations/fr-FR.json b/x-pack/plugins/translations/translations/fr-FR.json index de90e90f58e0a..5c830f3ff1702 100644 --- a/x-pack/plugins/translations/translations/fr-FR.json +++ b/x-pack/plugins/translations/translations/fr-FR.json @@ -42811,4 +42811,4 @@ "xpack.serverlessObservability.nav.projectSettings": "Paramètres de projet", "xpack.serverlessObservability.nav.visualizations": "Visualisations" } -} \ No newline at end of file +} diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index deb0b7bceb6d1..6f31a8d2dd91a 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -42803,4 +42803,4 @@ "xpack.serverlessObservability.nav.projectSettings": "プロジェクト設定", "xpack.serverlessObservability.nav.visualizations": "ビジュアライゼーション" } -} \ No newline at end of file +} diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 2393a8e4391db..85e80203cc509 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -42783,4 +42783,4 @@ "xpack.serverlessObservability.nav.projectSettings": "项目设置", "xpack.serverlessObservability.nav.visualizations": "可视化" } -} \ No newline at end of file +} From 788745e810487a48d905e813c711891ce9e58de7 Mon Sep 17 00:00:00 2001 From: Nathan Reese Date: Mon, 12 Feb 2024 10:40:34 -0700 Subject: [PATCH 12/83] decouple dashboard drilldown from Embeddables framework (#176188) part of https://github.com/elastic/kibana/issues/175138 PR decouples FlyoutCreateDrilldownAction, FlyoutEditDrilldownAction, and EmbeddableToDashboardDrilldown from Embeddable framework. --------- Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../interfaces/presentation_container.ts | 16 +- .../presentation_containers/mocks.ts | 2 + .../presentation_publishing/index.ts | 4 + .../interfaces/has_supported_triggers.ts | 15 ++ .../locator/get_dashboard_locator_params.ts | 33 ++- .../create/create_dashboard.test.ts | 2 + .../embeddable/create/create_dashboard.ts | 45 +++- .../public/lib/triggers/triggers.ts | 3 + .../dashboard_link_component.tsx | 8 +- src/plugins/links/tsconfig.json | 3 +- .../plugins/dashboard_enhanced/kibana.jsonc | 3 +- .../drilldowns/actions/drilldown_shared.ts | 43 ++-- .../flyout_create_drilldown.test.tsx | 220 +++++++++--------- .../flyout_create_drilldown.tsx | 109 ++++----- .../flyout_edit_drilldown.test.tsx | 200 ++++++++-------- .../flyout_edit_drilldown.tsx | 79 +++---- .../drilldowns/actions/test_helpers.ts | 49 ---- ...embeddable_to_dashboard_drilldown.test.tsx | 22 +- .../embeddable_to_dashboard_drilldown.tsx | 8 +- .../plugins/dashboard_enhanced/tsconfig.json | 4 +- .../public/actions/drilldown_grouping.ts | 5 +- .../interfaces/has_dynamic_actions.ts | 20 ++ .../embeddable_enhanced/public/index.ts | 4 + .../embeddable_enhanced/public/types.ts | 3 + 24 files changed, 463 insertions(+), 437 deletions(-) create mode 100644 packages/presentation/presentation_publishing/interfaces/has_supported_triggers.ts delete mode 100644 x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/test_helpers.ts create mode 100644 x-pack/plugins/embeddable_enhanced/public/embeddables/interfaces/has_dynamic_actions.ts diff --git a/packages/presentation/presentation_containers/interfaces/presentation_container.ts b/packages/presentation/presentation_containers/interfaces/presentation_container.ts index a92c5af7cbd0a..b0c7c3cdb64da 100644 --- a/packages/presentation/presentation_containers/interfaces/presentation_container.ts +++ b/packages/presentation/presentation_containers/interfaces/presentation_container.ts @@ -27,16 +27,18 @@ export type PresentationContainer = Partial & removePanel: (panelId: string) => void; canRemovePanels?: () => boolean; replacePanel: (idToRemove: string, newPanel: PanelPackage) => Promise; + getChildIds: () => string[]; + getChild: (childId: string) => unknown; }; -export const apiIsPresentationContainer = ( - unknownApi: unknown | null -): unknownApi is PresentationContainer => { +export const apiIsPresentationContainer = (api: unknown | null): api is PresentationContainer => { return Boolean( - (unknownApi as PresentationContainer)?.removePanel !== undefined && - (unknownApi as PresentationContainer)?.registerPanelApi !== undefined && - (unknownApi as PresentationContainer)?.replacePanel !== undefined && - (unknownApi as PresentationContainer)?.addNewPanel !== undefined + typeof (api as PresentationContainer)?.removePanel === 'function' && + typeof (api as PresentationContainer)?.registerPanelApi === 'function' && + typeof (api as PresentationContainer)?.replacePanel === 'function' && + typeof (api as PresentationContainer)?.addNewPanel === 'function' && + typeof (api as PresentationContainer)?.getChildIds === 'function' && + typeof (api as PresentationContainer)?.getChild === 'function' ); }; diff --git a/packages/presentation/presentation_containers/mocks.ts b/packages/presentation/presentation_containers/mocks.ts index 6d4610075c9d5..ac84c8cc5fd4b 100644 --- a/packages/presentation/presentation_containers/mocks.ts +++ b/packages/presentation/presentation_containers/mocks.ts @@ -17,5 +17,7 @@ export const getMockPresentationContainer = (): PresentationContainer => { registerPanelApi: jest.fn(), lastSavedState: new Subject(), getLastSavedStateForChild: jest.fn(), + getChildIds: jest.fn(), + getChild: jest.fn(), }; }; diff --git a/packages/presentation/presentation_publishing/index.ts b/packages/presentation/presentation_publishing/index.ts index 80d2c5870efbe..10c2f644b16b1 100644 --- a/packages/presentation/presentation_publishing/index.ts +++ b/packages/presentation/presentation_publishing/index.ts @@ -89,6 +89,10 @@ export { } from './interfaces/publishes_saved_object_id'; export { apiHasUniqueId, type HasUniqueId } from './interfaces/has_uuid'; export { apiHasDisableTriggers, type HasDisableTriggers } from './interfaces/has_disable_triggers'; +export { + apiHasSupportedTriggers, + type HasSupportedTriggers, +} from './interfaces/has_supported_triggers'; export { apiPublishesViewMode, apiPublishesWritableViewMode, diff --git a/packages/presentation/presentation_publishing/interfaces/has_supported_triggers.ts b/packages/presentation/presentation_publishing/interfaces/has_supported_triggers.ts new file mode 100644 index 0000000000000..d3a04522abb87 --- /dev/null +++ b/packages/presentation/presentation_publishing/interfaces/has_supported_triggers.ts @@ -0,0 +1,15 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +export interface HasSupportedTriggers { + supportedTriggers: () => string[]; +} + +export const apiHasSupportedTriggers = (api: unknown | null): api is HasSupportedTriggers => { + return Boolean(api && typeof (api as HasSupportedTriggers).supportedTriggers === 'function'); +}; diff --git a/src/plugins/dashboard/public/dashboard_app/locator/get_dashboard_locator_params.ts b/src/plugins/dashboard/public/dashboard_app/locator/get_dashboard_locator_params.ts index ff078be8f62f1..0843029ac3fc0 100644 --- a/src/plugins/dashboard/public/dashboard_app/locator/get_dashboard_locator_params.ts +++ b/src/plugins/dashboard/public/dashboard_app/locator/get_dashboard_locator_params.ts @@ -6,41 +6,34 @@ * Side Public License, v 1. */ -import { isQuery, isTimeRange } from '@kbn/data-plugin/common'; -import { Filter, isFilterPinned, Query, TimeRange } from '@kbn/es-query'; -import { EmbeddableInput, IEmbeddable } from '@kbn/embeddable-plugin/public'; -import { DashboardDrilldownOptions } from '@kbn/presentation-util-plugin/public'; -import { DashboardLocatorParams } from '../../dashboard_container'; - -interface EmbeddableQueryInput extends EmbeddableInput { - query?: Query; - filters?: Filter[]; - timeRange?: TimeRange; -} +import { isFilterPinned, type Query } from '@kbn/es-query'; +import type { HasParentApi, PublishesLocalUnifiedSearch } from '@kbn/presentation-publishing'; +import type { DashboardDrilldownOptions } from '@kbn/presentation-util-plugin/public'; +import type { DashboardLocatorParams } from '../../dashboard_container'; export const getDashboardLocatorParamsFromEmbeddable = ( - source: IEmbeddable, + api: Partial>>, options: DashboardDrilldownOptions ): Partial => { const params: DashboardLocatorParams = {}; - const input = source.getInput(); - if (isQuery(input.query) && options.useCurrentFilters) { - params.query = input.query; + const query = api.parentApi?.localQuery?.value; + if (query && options.useCurrentFilters) { + params.query = query as Query; } // if useCurrentDashboardDataRange is enabled, then preserve current time range // if undefined is passed, then destination dashboard will figure out time range itself // for brush event this time range would be overwritten - if (isTimeRange(input.timeRange) && options.useCurrentDateRange) { - params.timeRange = input.timeRange; + const timeRange = api.localTimeRange?.value ?? api.parentApi?.localTimeRange?.value; + if (timeRange && options.useCurrentDateRange) { + params.timeRange = timeRange; } // if useCurrentDashboardFilters enabled, then preserve all the filters (pinned, unpinned, and from controls) // otherwise preserve only pinned - params.filters = options.useCurrentFilters - ? input.filters - : input.filters?.filter((f) => isFilterPinned(f)); + const filters = api.parentApi?.localFilters?.value ?? []; + params.filters = options.useCurrentFilters ? filters : filters?.filter((f) => isFilterPinned(f)); return params; }; diff --git a/src/plugins/dashboard/public/dashboard_container/embeddable/create/create_dashboard.test.ts b/src/plugins/dashboard/public/dashboard_container/embeddable/create/create_dashboard.test.ts index 5ae88b7c3bcd3..12eebe31da173 100644 --- a/src/plugins/dashboard/public/dashboard_container/embeddable/create/create_dashboard.test.ts +++ b/src/plugins/dashboard/public/dashboard_container/embeddable/create/create_dashboard.test.ts @@ -428,7 +428,9 @@ test('creates a control group from the control group factory and waits for it to untilInitialized: jest.fn(), getInput: jest.fn().mockReturnValue({}), getInput$: jest.fn().mockReturnValue(new Observable()), + getOutput: jest.fn().mockReturnValue({}), getOutput$: jest.fn().mockReturnValue(new Observable()), + onFiltersPublished$: new Observable(), unsavedChanges: new BehaviorSubject(undefined), } as unknown as ControlGroupContainer; const mockControlGroupFactory = { diff --git a/src/plugins/dashboard/public/dashboard_container/embeddable/create/create_dashboard.ts b/src/plugins/dashboard/public/dashboard_container/embeddable/create/create_dashboard.ts index 93b849a122ce0..d7d34fa42f078 100644 --- a/src/plugins/dashboard/public/dashboard_container/embeddable/create/create_dashboard.ts +++ b/src/plugins/dashboard/public/dashboard_container/embeddable/create/create_dashboard.ts @@ -23,11 +23,13 @@ import { reactEmbeddableRegistryHasKey, ViewMode, } from '@kbn/embeddable-plugin/public'; -import { TimeRange } from '@kbn/es-query'; +import { compareFilters, Filter, TimeRange } from '@kbn/es-query'; import { lazyLoadReduxToolsPackage } from '@kbn/presentation-util-plugin/public'; import { cloneDeep, identity, omit, pickBy } from 'lodash'; -import { Subject } from 'rxjs'; +import { BehaviorSubject, combineLatest, Subject } from 'rxjs'; +import { map, distinctUntilChanged, startWith } from 'rxjs/operators'; import { v4 } from 'uuid'; +import { combineDashboardFiltersWithControlGroupFilters } from './controls/dashboard_control_group_integration'; import { DashboardContainerInput, DashboardPanelState } from '../../../../common'; import { DEFAULT_DASHBOARD_INPUT, @@ -462,5 +464,44 @@ export const initializeDashboard = async ({ setTimeout(() => dashboard.dispatch.setAnimatePanelTransforms(true), 500) ); + // -------------------------------------------------------------------------------------- + // Set parentApi.localFilters to include dashboardContainer filters and control group filters + // -------------------------------------------------------------------------------------- + untilDashboardReady().then((dashboardContainer) => { + if (!dashboardContainer.controlGroup) { + return; + } + + function getCombinedFilters() { + return combineDashboardFiltersWithControlGroupFilters( + dashboardContainer.getInput().filters ?? [], + dashboardContainer.controlGroup! + ); + } + + const localFilters = new BehaviorSubject(getCombinedFilters()); + dashboardContainer.localFilters = localFilters; + + const inputFilters$ = dashboardContainer.getInput$().pipe( + startWith(dashboardContainer.getInput()), + map((input) => input.filters), + distinctUntilChanged((previous, current) => { + return compareFilters(previous ?? [], current ?? []); + }) + ); + + // Can not use onFiltersPublished$ directly since it does not have an intial value and + // combineLatest will not emit until each observable emits at least one value + const controlGroupFilters$ = dashboardContainer.controlGroup.onFiltersPublished$.pipe( + startWith(dashboardContainer.controlGroup.getOutput().filters) + ); + + dashboardContainer.integrationSubscriptions.add( + combineLatest([inputFilters$, controlGroupFilters$]).subscribe(() => { + localFilters.next(getCombinedFilters()); + }) + ); + }); + return { input: initialDashboardInput, searchSessionId: initialSearchSessionId }; }; diff --git a/src/plugins/embeddable/public/lib/triggers/triggers.ts b/src/plugins/embeddable/public/lib/triggers/triggers.ts index e60e6a2146a7c..edefdb191e123 100644 --- a/src/plugins/embeddable/public/lib/triggers/triggers.ts +++ b/src/plugins/embeddable/public/lib/triggers/triggers.ts @@ -13,6 +13,9 @@ import { Trigger, RowClickContext } from '@kbn/ui-actions-plugin/public'; import { BooleanRelation } from '@kbn/es-query'; import { IEmbeddable } from '..'; +/** + * @deprecated use `EmbeddableApiContext` from `@kbn/presentation-publishing` + */ export interface EmbeddableContext { embeddable: T; } diff --git a/src/plugins/links/public/components/dashboard_link/dashboard_link_component.tsx b/src/plugins/links/public/components/dashboard_link/dashboard_link_component.tsx index b6bbe8fa5f68b..90bf4a03002b7 100644 --- a/src/plugins/links/public/components/dashboard_link/dashboard_link_component.tsx +++ b/src/plugins/links/public/components/dashboard_link/dashboard_link_component.tsx @@ -22,6 +22,7 @@ import { DashboardDrilldownOptions, DEFAULT_DASHBOARD_DRILLDOWN_OPTIONS, } from '@kbn/presentation-util-plugin/public'; +import type { HasParentApi, PublishesLocalUnifiedSearch } from '@kbn/presentation-publishing'; import { DASHBOARD_LINK_TYPE, @@ -115,7 +116,12 @@ export const DashboardLinkComponent = ({ const params: DashboardLocatorParams = { dashboardId: link.destination, - ...getDashboardLocatorParamsFromEmbeddable(linksEmbeddable, linkOptions), + ...getDashboardLocatorParamsFromEmbeddable( + linksEmbeddable as Partial< + PublishesLocalUnifiedSearch & HasParentApi> + >, + linkOptions + ), }; const locator = dashboardContainer.locator; diff --git a/src/plugins/links/tsconfig.json b/src/plugins/links/tsconfig.json index 0b4430d5acc59..3431fdfd78a65 100644 --- a/src/plugins/links/tsconfig.json +++ b/src/plugins/links/tsconfig.json @@ -31,7 +31,8 @@ "@kbn/usage-collection-plugin", "@kbn/visualizations-plugin", "@kbn/core-mount-utils-browser", - "@kbn/presentation-containers" + "@kbn/presentation-containers", + "@kbn/presentation-publishing" ], "exclude": ["target/**/*"] } diff --git a/x-pack/plugins/dashboard_enhanced/kibana.jsonc b/x-pack/plugins/dashboard_enhanced/kibana.jsonc index 88bb64bb00503..c37509d0a669a 100644 --- a/x-pack/plugins/dashboard_enhanced/kibana.jsonc +++ b/x-pack/plugins/dashboard_enhanced/kibana.jsonc @@ -21,7 +21,8 @@ "kibanaReact", "kibanaUtils", "imageEmbeddable", - "presentationUtil" + "presentationUtil", + "uiActions" ] } } diff --git a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/drilldown_shared.ts b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/drilldown_shared.ts index b9cfe79e19fa9..65b47346e50aa 100644 --- a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/drilldown_shared.ts +++ b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/drilldown_shared.ts @@ -6,13 +6,17 @@ */ import { APPLY_FILTER_TRIGGER } from '@kbn/data-plugin/public'; +import { SELECT_RANGE_TRIGGER, VALUE_CLICK_TRIGGER } from '@kbn/embeddable-plugin/public'; import { - SELECT_RANGE_TRIGGER, - VALUE_CLICK_TRIGGER, - IEmbeddable, - Container as EmbeddableContainer, -} from '@kbn/embeddable-plugin/public'; -import { isEnhancedEmbeddable } from '@kbn/embeddable-enhanced-plugin/public'; + apiIsPresentationContainer, + type PresentationContainer, +} from '@kbn/presentation-containers'; +import { + PublishesPanelTitle, + type HasUniqueId, + type HasParentApi, +} from '@kbn/presentation-publishing'; +import { apiHasDynamicActions } from '@kbn/embeddable-enhanced-plugin/public'; import { UiActionsEnhancedDrilldownTemplate as DrilldownTemplate } from '@kbn/ui-actions-enhanced-plugin/public'; /** @@ -36,31 +40,22 @@ export function ensureNestedTriggers(triggers: string[]): string[] { return triggers; } -const isEmbeddableContainer = (x: unknown): x is EmbeddableContainer => - x instanceof EmbeddableContainer; - /** * Given a dashboard panel embeddable, it will find the parent (dashboard * container embeddable), then iterate through all the dashboard panels and * generate DrilldownTemplate for each existing drilldown. */ export const createDrilldownTemplatesFromSiblings = ( - embeddable: IEmbeddable + embeddable: Partial & HasParentApi> ): DrilldownTemplate[] => { - const templates: DrilldownTemplate[] = []; - const embeddableId = embeddable.id; - - const container = embeddable.getRoot(); + const parentApi = embeddable.parentApi; + if (!apiIsPresentationContainer(parentApi)) return []; - if (!container) return templates; - if (!isEmbeddableContainer(container)) return templates; - - const childrenIds = (container as EmbeddableContainer).getChildIds(); - - for (const childId of childrenIds) { - const child = (container as EmbeddableContainer).getChild(childId); - if (child.id === embeddableId) continue; - if (!isEnhancedEmbeddable(child)) continue; + const templates: DrilldownTemplate[] = []; + for (const childId of parentApi.getChildIds()) { + const child = parentApi.getChild(childId) as Partial; + if (childId === embeddable.uuid) continue; + if (!apiHasDynamicActions(child)) continue; const events = child.enhancements.dynamicActions.state.get().events; for (const event of events) { @@ -68,7 +63,7 @@ export const createDrilldownTemplatesFromSiblings = ( id: event.eventId, name: event.action.name, icon: 'dashboardApp', - description: child.getTitle() || child.id, + description: child.panelTitle?.value ?? child.uuid ?? '', config: event.action.config, factoryId: event.action.factoryId, triggers: event.triggers, diff --git a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_create_drilldown/flyout_create_drilldown.test.tsx b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_create_drilldown/flyout_create_drilldown.test.tsx index 165f80cdf51e8..50fe2e570b9a6 100644 --- a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_create_drilldown/flyout_create_drilldown.test.tsx +++ b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_create_drilldown/flyout_create_drilldown.test.tsx @@ -5,164 +5,152 @@ * 2.0. */ -import { Subject } from 'rxjs'; +import { BehaviorSubject, Subject } from 'rxjs'; +import { + UiActionsEnhancedMemoryActionStorage as MemoryActionStorage, + UiActionsEnhancedDynamicActionManager as DynamicActionManager, +} from '@kbn/ui-actions-enhanced-plugin/public'; +import type { ViewMode } from '@kbn/presentation-publishing'; import { FlyoutCreateDrilldownAction, OpenFlyoutAddDrilldownParams, } from './flyout_create_drilldown'; import { coreMock } from '@kbn/core/public/mocks'; -import { ViewMode } from '@kbn/embeddable-plugin/public'; -import { MockEmbeddable, enhanceEmbeddable } from '../test_helpers'; import { uiActionsEnhancedPluginMock } from '@kbn/ui-actions-enhanced-plugin/public/mocks'; import { UiActionsEnhancedActionFactory } from '@kbn/ui-actions-enhanced-plugin/public'; -const overlays = coreMock.createStart().overlays; -const uiActionsEnhanced = uiActionsEnhancedPluginMock.createStartContract(); - -const actionParams: OpenFlyoutAddDrilldownParams = { - start: () => ({ - core: { - overlays, - application: { - currentAppId$: new Subject(), +function createAction( + allPossibleTriggers = ['VALUE_CLICK_TRIGGER'], + overlays = coreMock.createStart().overlays +) { + const uiActionsEnhanced = uiActionsEnhancedPluginMock.createStartContract(); + const params: OpenFlyoutAddDrilldownParams = { + start: () => ({ + core: { + overlays, + application: { + currentAppId$: new Subject(), + }, + theme: { + theme$: new Subject(), + }, + } as any, + plugins: { + uiActionsEnhanced, }, - theme: { - theme$: new Subject(), - }, - } as any, - plugins: { - uiActionsEnhanced, - }, - self: {}, - }), -}; + self: {}, + }), + }; -test('should create', () => { - expect(() => new FlyoutCreateDrilldownAction(actionParams)).not.toThrow(); -}); + uiActionsEnhanced.getActionFactories.mockImplementation(() => [ + { + supportedTriggers: () => allPossibleTriggers, + isCompatibleLicense: () => true, + } as unknown as UiActionsEnhancedActionFactory, + ]); + return new FlyoutCreateDrilldownAction(params); +} + +const compatibleEmbeddableApi = { + enhancements: { + dynamicActions: new DynamicActionManager({ + storage: new MemoryActionStorage(), + isCompatible: async () => true, + uiActions: uiActionsEnhancedPluginMock.createStartContract(), + }), + }, + parentApi: { + type: 'dashboard', + }, + supportedTriggers: () => { + return ['VALUE_CLICK_TRIGGER']; + }, + viewMode: new BehaviorSubject('edit'), +}; test('title is a string', () => { - expect(typeof new FlyoutCreateDrilldownAction(actionParams).getDisplayName() === 'string').toBe( - true - ); + expect(typeof createAction().getDisplayName() === 'string').toBe(true); }); test('icon exists', () => { - expect(typeof new FlyoutCreateDrilldownAction(actionParams).getIconType() === 'string').toBe( - true - ); + expect(typeof createAction().getIconType() === 'string').toBe(true); }); -interface CompatibilityParams { - isEdit?: boolean; - isValueClickTriggerSupported?: boolean; - isEmbeddableEnhanced?: boolean; - rootType?: string; - actionFactoriesTriggers?: string[]; -} - describe('isCompatible', () => { - const drilldownAction = new FlyoutCreateDrilldownAction(actionParams); - - async function assertCompatibility( - { - isEdit = true, - isValueClickTriggerSupported = true, - isEmbeddableEnhanced = true, - rootType = 'dashboard', - actionFactoriesTriggers = ['VALUE_CLICK_TRIGGER'], - }: CompatibilityParams, - expectedResult: boolean = true - ): Promise { - uiActionsEnhanced.getActionFactories.mockImplementation(() => [ - { - supportedTriggers: () => actionFactoriesTriggers, - isCompatibleLicense: () => true, - } as unknown as UiActionsEnhancedActionFactory, - ]); - - let embeddable = new MockEmbeddable( - { id: '', viewMode: isEdit ? ViewMode.EDIT : ViewMode.VIEW }, - { - supportedTriggers: isValueClickTriggerSupported ? ['VALUE_CLICK_TRIGGER'] : [], - } - ); - - embeddable.rootType = rootType; - - if (isEmbeddableEnhanced) { - embeddable = enhanceEmbeddable(embeddable); - } - - const result = await drilldownAction.isCompatible({ - embeddable, - }); - - expect(result).toBe(expectedResult); - } - - const assertNonCompatibility = (params: CompatibilityParams) => - assertCompatibility(params, false); - test("compatible if dynamicUiActions enabled, 'VALUE_CLICK_TRIGGER' is supported, in edit mode", async () => { - await assertCompatibility({}); + const action = createAction(); + expect(await action.isCompatible({ embeddable: compatibleEmbeddableApi })).toBe(true); }); test('not compatible if embeddable is not enhanced', async () => { - await assertNonCompatibility({ - isEmbeddableEnhanced: false, - }); + const action = createAction(); + const embeddableApi = { + ...compatibleEmbeddableApi, + enhancements: undefined, + }; + expect(await action.isCompatible({ embeddable: embeddableApi })).toBe(false); }); test("not compatible if 'VALUE_CLICK_TRIGGER' is not supported", async () => { - await assertNonCompatibility({ - isValueClickTriggerSupported: false, - }); + const action = createAction(); + const embeddableApi = { + ...compatibleEmbeddableApi, + supportedTriggers: () => { + return []; + }, + }; + expect(await action.isCompatible({ embeddable: embeddableApi })).toBe(false); }); test('not compatible if in view mode', async () => { - await assertNonCompatibility({ - isEdit: false, - }); + const action = createAction(); + const embeddableApi = { + ...compatibleEmbeddableApi, + viewMode: new BehaviorSubject('view'), + }; + expect(await action.isCompatible({ embeddable: embeddableApi })).toBe(false); }); - test('not compatible if root embeddable is not "dashboard"', async () => { - await assertNonCompatibility({ - rootType: 'visualization', - }); + test('not compatible if parent embeddable is not "dashboard"', async () => { + const action = createAction(); + const embeddableApi = { + ...compatibleEmbeddableApi, + parentApi: { + type: 'visualization', + }, + }; + expect(await action.isCompatible({ embeddable: embeddableApi })).toBe(false); }); test('not compatible if no triggers intersect', async () => { - await assertNonCompatibility({ - actionFactoriesTriggers: [], - }); - await assertNonCompatibility({ - actionFactoriesTriggers: ['SELECT_RANGE_TRIGGER'], - }); + expect(await createAction([]).isCompatible({ embeddable: compatibleEmbeddableApi })).toBe( + false + ); + expect( + await createAction(['SELECT_RANGE_TRIGGER']).isCompatible({ + embeddable: compatibleEmbeddableApi, + }) + ).toBe(false); }); }); describe('execute', () => { - const drilldownAction = new FlyoutCreateDrilldownAction(actionParams); - - test('throws error if no dynamicUiActions', async () => { + test('throws if no dynamicUiActions', async () => { + const action = createAction(); + const embeddableApi = { + ...compatibleEmbeddableApi, + enhancements: undefined, + }; await expect( - drilldownAction.execute({ - embeddable: new MockEmbeddable({ id: '' }, {}), - }) - ).rejects.toThrowErrorMatchingInlineSnapshot( - `"Need embeddable to be EnhancedEmbeddable to execute FlyoutCreateDrilldownAction."` - ); + action.execute({ embeddable: embeddableApi }) + ).rejects.toThrowErrorMatchingInlineSnapshot(`"Action is incompatible"`); }); test('should open flyout', async () => { + const overlays = coreMock.createStart().overlays; const spy = jest.spyOn(overlays, 'openFlyout'); - const embeddable = enhanceEmbeddable(new MockEmbeddable({ id: '' }, {})); - - await drilldownAction.execute({ - embeddable, - }); - + const action = createAction(['VALUE_CLICK_TRIGGER'], overlays); + await action.execute({ embeddable: compatibleEmbeddableApi }); expect(spy).toBeCalled(); }); }); diff --git a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_create_drilldown/flyout_create_drilldown.tsx b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_create_drilldown/flyout_create_drilldown.tsx index 5bd16597c0b54..2c6bd8074a689 100644 --- a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_create_drilldown/flyout_create_drilldown.tsx +++ b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_create_drilldown/flyout_create_drilldown.tsx @@ -5,20 +5,37 @@ * 2.0. */ -import React from 'react'; -import { i18n } from '@kbn/i18n'; -import { distinctUntilChanged, filter, map, skip, take, takeUntil } from 'rxjs/operators'; -import { Subject } from 'rxjs'; -import { Action } from '@kbn/ui-actions-plugin/public'; -import { toMountPoint } from '@kbn/kibana-react-plugin/public'; -import { CONTEXT_MENU_TRIGGER, EmbeddableContext, ViewMode } from '@kbn/embeddable-plugin/public'; import { - isEnhancedEmbeddable, + apiHasDynamicActions, embeddableEnhancedDrilldownGrouping, + type HasDynamicActions, } from '@kbn/embeddable-enhanced-plugin/public'; +import { CONTEXT_MENU_TRIGGER } from '@kbn/embeddable-plugin/public'; +import { i18n } from '@kbn/i18n'; +import { toMountPoint } from '@kbn/kibana-react-plugin/public'; import { StartServicesGetter } from '@kbn/kibana-utils-plugin/public'; +import { + tracksOverlays, + type PresentationContainer, + type TracksOverlays, +} from '@kbn/presentation-containers'; +import { + apiCanAccessViewMode, + apiHasParentApi, + apiHasSupportedTriggers, + apiIsOfType, + getInheritedViewMode, + type CanAccessViewMode, + type EmbeddableApiContext, + type HasUniqueId, + type HasParentApi, + type HasSupportedTriggers, + type HasType, +} from '@kbn/presentation-publishing'; +import { Action, IncompatibleActionError } from '@kbn/ui-actions-plugin/public'; +import React from 'react'; import { StartDependencies } from '../../../../plugin'; -import { ensureNestedTriggers, createDrilldownTemplatesFromSiblings } from '../drilldown_shared'; +import { createDrilldownTemplatesFromSiblings, ensureNestedTriggers } from '../drilldown_shared'; export const OPEN_FLYOUT_ADD_DRILLDOWN = 'OPEN_FLYOUT_ADD_DRILLDOWN'; @@ -26,7 +43,19 @@ export interface OpenFlyoutAddDrilldownParams { start: StartServicesGetter>; } -export class FlyoutCreateDrilldownAction implements Action { +export type FlyoutCreateDrilldownActionApi = CanAccessViewMode & + HasDynamicActions & + HasParentApi> & + HasSupportedTriggers & + Partial; + +const isApiCompatible = (api: unknown | null): api is FlyoutCreateDrilldownActionApi => + apiHasDynamicActions(api) && + apiHasParentApi(api) && + apiCanAccessViewMode(api) && + apiHasSupportedTriggers(api); + +export class FlyoutCreateDrilldownAction implements Action { public readonly type = OPEN_FLYOUT_ADD_DRILLDOWN; public readonly id = OPEN_FLYOUT_ADD_DRILLDOWN; public order = 12; @@ -44,13 +73,15 @@ export class FlyoutCreateDrilldownAction implements Action { return 'plusInCircle'; } - private isEmbeddableCompatible(context: EmbeddableContext): boolean { - if (!isEnhancedEmbeddable(context.embeddable)) return false; - if (context.embeddable.getRoot().type !== 'dashboard') return false; - const supportedTriggers = [ - CONTEXT_MENU_TRIGGER, - ...(context.embeddable.supportedTriggers() || []), - ]; + public async isCompatible({ embeddable }: EmbeddableApiContext) { + if (!isApiCompatible(embeddable)) return false; + if ( + getInheritedViewMode(embeddable) !== 'edit' || + !apiIsOfType(embeddable.parentApi, 'dashboard') + ) + return false; + + const supportedTriggers = [CONTEXT_MENU_TRIGGER, ...embeddable.supportedTriggers()]; /** * Check if there is an intersection between all registered drilldowns possible triggers that they could be attached to @@ -67,36 +98,22 @@ export class FlyoutCreateDrilldownAction implements Action { ); } - public async isCompatible(context: EmbeddableContext) { - if (!context.embeddable?.getInput) return false; - const isEditMode = context.embeddable.getInput().viewMode === 'edit'; - return isEditMode && this.isEmbeddableCompatible(context); - } - - public async execute(context: EmbeddableContext) { + public async execute({ embeddable }: EmbeddableApiContext) { + if (!isApiCompatible(embeddable)) throw new IncompatibleActionError(); const { core, plugins } = this.params.start(); - const { embeddable } = context; - - if (!isEnhancedEmbeddable(embeddable)) { - throw new Error( - 'Need embeddable to be EnhancedEmbeddable to execute FlyoutCreateDrilldownAction.' - ); - } const templates = createDrilldownTemplatesFromSiblings(embeddable); - const closed$ = new Subject(); + const overlayTracker = tracksOverlays(embeddable.parentApi) ? embeddable.parentApi : undefined; const close = () => { - closed$.next(true); + if (overlayTracker) overlayTracker.clearOverlays(); handle.close(); }; - const closeFlyout = () => { - close(); - }; const triggers = [ ...ensureNestedTriggers(embeddable.supportedTriggers()), CONTEXT_MENU_TRIGGER, ]; + const handle = core.overlays.openFlyout( toMountPoint( { { ownFocus: true, 'data-test-subj': 'createDrilldownFlyout', + onClose: () => { + close(); + }, } ); - // Close flyout on application change. - core.application.currentAppId$ - .pipe(takeUntil(closed$), skip(1), take(1)) - .subscribe(closeFlyout); - - // Close flyout on dashboard switch to "view" mode or on embeddable destroy. - embeddable - .getInput$() - .pipe( - takeUntil(closed$), - map((input) => input.viewMode), - distinctUntilChanged(), - filter((mode) => mode !== ViewMode.EDIT), - take(1) - ) - .subscribe({ next: closeFlyout, complete: closeFlyout }); + overlayTracker?.openOverlay(handle); } } diff --git a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_edit_drilldown/flyout_edit_drilldown.test.tsx b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_edit_drilldown/flyout_edit_drilldown.test.tsx index fe0d6df5972c4..1aa64bd6e5a9e 100644 --- a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_edit_drilldown/flyout_edit_drilldown.test.tsx +++ b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_edit_drilldown/flyout_edit_drilldown.test.tsx @@ -5,152 +5,136 @@ * 2.0. */ -import { Subject } from 'rxjs'; +import { BehaviorSubject, Subject } from 'rxjs'; import { FlyoutEditDrilldownAction, FlyoutEditDrilldownParams } from './flyout_edit_drilldown'; import { coreMock } from '@kbn/core/public/mocks'; -import { ViewMode } from '@kbn/embeddable-plugin/public'; +import type { ViewMode } from '@kbn/presentation-publishing'; +import { + UiActionsEnhancedMemoryActionStorage as MemoryActionStorage, + UiActionsEnhancedDynamicActionManager as DynamicActionManager, +} from '@kbn/ui-actions-enhanced-plugin/public'; import { uiActionsEnhancedPluginMock } from '@kbn/ui-actions-enhanced-plugin/public/mocks'; -import { EnhancedEmbeddable } from '@kbn/embeddable-enhanced-plugin/public'; -import { MockEmbeddable, enhanceEmbeddable } from '../test_helpers'; -const overlays = coreMock.createStart().overlays; -const uiActionsPlugin = uiActionsEnhancedPluginMock.createPlugin(); -const uiActions = uiActionsPlugin.doStart(); - -uiActionsPlugin.setup.registerDrilldown({ - id: 'foo', - CollectConfig: {} as any, - createConfig: () => ({}), - isConfigValid: () => true, - execute: async () => {}, - getDisplayName: () => 'test', - supportedTriggers() { +function createAction(overlays = coreMock.createStart().overlays) { + const uiActionsPlugin = uiActionsEnhancedPluginMock.createPlugin(); + const uiActions = uiActionsPlugin.doStart(); + const params: FlyoutEditDrilldownParams = { + start: () => ({ + core: { + overlays, + application: { + currentAppId$: new Subject(), + }, + theme: { + theme$: new Subject(), + }, + } as any, + plugins: { + uiActionsEnhanced: uiActions, + }, + self: {}, + }), + }; + return new FlyoutEditDrilldownAction(params); +} + +const compatibleEmbeddableApi = { + enhancements: { + dynamicActions: new DynamicActionManager({ + storage: new MemoryActionStorage(), + isCompatible: async () => true, + uiActions: uiActionsEnhancedPluginMock.createStartContract(), + }), + }, + supportedTriggers: () => { return ['VALUE_CLICK_TRIGGER']; }, -}); - -const actionParams: FlyoutEditDrilldownParams = { - start: () => ({ - core: { - overlays, - application: { - currentAppId$: new Subject(), - }, - theme: { - theme$: new Subject(), - }, - } as any, - plugins: { - uiActionsEnhanced: uiActions, - }, - self: {}, - }), + viewMode: new BehaviorSubject('edit'), }; -test('should create', () => { - expect(() => new FlyoutEditDrilldownAction(actionParams)).not.toThrow(); +beforeAll(async () => { + await compatibleEmbeddableApi.enhancements.dynamicActions.createEvent( + { + config: {}, + factoryId: 'foo', + name: '', + }, + ['VALUE_CLICK_TRIGGER'] + ); }); test('title is a string', () => { - expect(typeof new FlyoutEditDrilldownAction(actionParams).getDisplayName() === 'string').toBe( - true - ); + expect(typeof createAction().getDisplayName() === 'string').toBe(true); }); test('icon exists', () => { - expect(typeof new FlyoutEditDrilldownAction(actionParams).getIconType() === 'string').toBe(true); + expect(typeof createAction().getIconType() === 'string').toBe(true); }); test('MenuItem exists', () => { - expect(new FlyoutEditDrilldownAction(actionParams).MenuItem).toBeDefined(); + expect(createAction().MenuItem).toBeDefined(); }); describe('isCompatible', () => { - function setupIsCompatible({ - isEdit = true, - isEmbeddableEnhanced = true, - }: { - isEdit?: boolean; - isEmbeddableEnhanced?: boolean; - } = {}) { - const action = new FlyoutEditDrilldownAction(actionParams); - const input = { - id: '', - viewMode: isEdit ? ViewMode.EDIT : ViewMode.VIEW, - }; - const embeddable = new MockEmbeddable(input, {}); - const context = { - embeddable: (isEmbeddableEnhanced - ? enhanceEmbeddable(embeddable, uiActions) - : embeddable) as EnhancedEmbeddable, - }; - - return { - action, - context, - }; - } + test("compatible if dynamicUiActions enabled (with event), 'VALUE_CLICK_TRIGGER' is supported, in edit mode", async () => { + const action = createAction(); + expect(await action.isCompatible({ embeddable: compatibleEmbeddableApi })).toBe(true); + }); test('not compatible if no drilldowns', async () => { - const { action, context } = setupIsCompatible(); - expect(await action.isCompatible(context)).toBe(false); + const embeddableApi = { + ...compatibleEmbeddableApi, + enhancements: { + dynamicActions: new DynamicActionManager({ + storage: new MemoryActionStorage(), + isCompatible: async () => true, + uiActions: uiActionsEnhancedPluginMock.createStartContract(), + }), + }, + }; + const action = createAction(); + expect(await action.isCompatible({ embeddable: embeddableApi })).toBe(false); }); test('not compatible if embeddable is not enhanced', async () => { - const { action, context } = setupIsCompatible({ isEmbeddableEnhanced: false }); - expect(await action.isCompatible(context)).toBe(false); + const embeddableApi = { + ...compatibleEmbeddableApi, + enhancements: undefined, + }; + const action = createAction(); + expect(await action.isCompatible({ embeddable: embeddableApi })).toBe(false); }); - describe('when has at least one drilldown', () => { - test('is compatible in edit mode', async () => { - const { action, context } = setupIsCompatible(); - - await context.embeddable.enhancements.dynamicActions.createEvent( - { - config: {}, - factoryId: 'foo', - name: '', - }, - ['VALUE_CLICK_TRIGGER'] - ); - - expect(await action.isCompatible(context)).toBe(true); - }); - - test('not compatible in view mode', async () => { - const { action, context } = setupIsCompatible({ isEdit: false }); - - await context.embeddable.enhancements.dynamicActions.createEvent( - { - config: {}, - factoryId: 'foo', - name: '', - }, - ['VALUE_CLICK_TRIGGER'] - ); - - expect(await action.isCompatible(context)).toBe(false); - }); + test('not compatible in view mode', async () => { + const embeddableApi = { + ...compatibleEmbeddableApi, + viewMode: new BehaviorSubject('view'), + }; + const action = createAction(); + expect(await action.isCompatible({ embeddable: embeddableApi })).toBe(false); }); }); describe('execute', () => { - const drilldownAction = new FlyoutEditDrilldownAction(actionParams); - test('throws error if no dynamicUiActions', async () => { + const action = createAction(); + const embeddableApi = { + ...compatibleEmbeddableApi, + enhancements: undefined, + }; await expect( - drilldownAction.execute({ - embeddable: new MockEmbeddable({ id: '' }, {}), + action.execute({ + embeddable: embeddableApi, }) - ).rejects.toThrowErrorMatchingInlineSnapshot( - `"Need embeddable to be EnhancedEmbeddable to execute FlyoutEditDrilldownAction."` - ); + ).rejects.toThrowErrorMatchingInlineSnapshot(`"Action is incompatible"`); }); test('should open flyout', async () => { + const overlays = coreMock.createStart().overlays; const spy = jest.spyOn(overlays, 'openFlyout'); - await drilldownAction.execute({ - embeddable: enhanceEmbeddable(new MockEmbeddable({ id: '' }, {})), + const action = createAction(overlays); + await action.execute({ + embeddable: compatibleEmbeddableApi, }); expect(spy).toBeCalled(); }); diff --git a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_edit_drilldown/flyout_edit_drilldown.tsx b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_edit_drilldown/flyout_edit_drilldown.tsx index aed94cd1c4adc..ebd4a6a3441e9 100644 --- a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_edit_drilldown/flyout_edit_drilldown.tsx +++ b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_edit_drilldown/flyout_edit_drilldown.tsx @@ -6,14 +6,28 @@ */ import React from 'react'; -import { distinctUntilChanged, filter, map, skip, take, takeUntil } from 'rxjs/operators'; -import { Subject } from 'rxjs'; -import { Action } from '@kbn/ui-actions-plugin/public'; +import { Action, IncompatibleActionError } from '@kbn/ui-actions-plugin/public'; import { toMountPoint } from '@kbn/kibana-react-plugin/public'; -import { EmbeddableContext, ViewMode, CONTEXT_MENU_TRIGGER } from '@kbn/embeddable-plugin/public'; import { - isEnhancedEmbeddable, + tracksOverlays, + type PresentationContainer, + type TracksOverlays, +} from '@kbn/presentation-containers'; +import { + apiCanAccessViewMode, + apiHasSupportedTriggers, + getInheritedViewMode, + type CanAccessViewMode, + type EmbeddableApiContext, + type HasUniqueId, + type HasParentApi, + type HasSupportedTriggers, +} from '@kbn/presentation-publishing'; +import { CONTEXT_MENU_TRIGGER } from '@kbn/embeddable-plugin/public'; +import { + apiHasDynamicActions, embeddableEnhancedDrilldownGrouping, + type HasDynamicActions, } from '@kbn/embeddable-enhanced-plugin/public'; import { StartServicesGetter } from '@kbn/kibana-utils-plugin/public'; import { txtDisplayName } from './i18n'; @@ -27,7 +41,16 @@ export interface FlyoutEditDrilldownParams { start: StartServicesGetter>; } -export class FlyoutEditDrilldownAction implements Action { +export type FlyoutEditDrilldownActionApi = CanAccessViewMode & + HasDynamicActions & + HasParentApi> & + HasSupportedTriggers & + Partial; + +const isApiCompatible = (api: unknown | null): api is FlyoutEditDrilldownActionApi => + apiHasDynamicActions(api) && apiCanAccessViewMode(api) && apiHasSupportedTriggers(api); + +export class FlyoutEditDrilldownAction implements Action { public readonly type = OPEN_FLYOUT_EDIT_DRILLDOWN; public readonly id = OPEN_FLYOUT_EDIT_DRILLDOWN; public order = 10; @@ -45,32 +68,22 @@ export class FlyoutEditDrilldownAction implements Action { public readonly MenuItem = MenuItem as any; - public async isCompatible({ embeddable }: EmbeddableContext) { - if (!embeddable?.getInput) return false; - if (embeddable.getInput().viewMode !== ViewMode.EDIT) return false; - if (!isEnhancedEmbeddable(embeddable)) return false; + public async isCompatible({ embeddable }: EmbeddableApiContext) { + if (!isApiCompatible(embeddable)) return false; + if (getInheritedViewMode(embeddable) !== 'edit') return false; return embeddable.enhancements.dynamicActions.state.get().events.length > 0; } - public async execute(context: EmbeddableContext) { + public async execute({ embeddable }: EmbeddableApiContext) { + if (!isApiCompatible(embeddable)) throw new IncompatibleActionError(); const { core, plugins } = this.params.start(); - const { embeddable } = context; - - if (!isEnhancedEmbeddable(embeddable)) { - throw new Error( - 'Need embeddable to be EnhancedEmbeddable to execute FlyoutEditDrilldownAction.' - ); - } const templates = createDrilldownTemplatesFromSiblings(embeddable); - const closed$ = new Subject(); + const overlayTracker = tracksOverlays(embeddable.parentApi) ? embeddable.parentApi : undefined; const close = () => { - closed$.next(true); + if (overlayTracker) overlayTracker.clearOverlays(); handle.close(); }; - const closeFlyout = () => { - close(); - }; const handle = core.overlays.openFlyout( toMountPoint( @@ -87,24 +100,12 @@ export class FlyoutEditDrilldownAction implements Action { { ownFocus: true, 'data-test-subj': 'editDrilldownFlyout', + onClose: () => { + close(); + }, } ); - // Close flyout on application change. - core.application.currentAppId$ - .pipe(takeUntil(closed$), skip(1), take(1)) - .subscribe(closeFlyout); - - // Close flyout on dashboard switch to "view" mode or on embeddable destroy. - embeddable - .getInput$() - .pipe( - takeUntil(closed$), - map((input) => input.viewMode), - distinctUntilChanged(), - filter((mode) => mode !== ViewMode.EDIT), - take(1) - ) - .subscribe({ next: closeFlyout, complete: closeFlyout }); + overlayTracker?.openOverlay(handle); } } diff --git a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/test_helpers.ts b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/test_helpers.ts deleted file mode 100644 index 067b27fd12e5b..0000000000000 --- a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/test_helpers.ts +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { Embeddable, EmbeddableInput } from '@kbn/embeddable-plugin/public'; -import { EnhancedEmbeddable } from '@kbn/embeddable-enhanced-plugin/public'; -import { - UiActionsEnhancedMemoryActionStorage as MemoryActionStorage, - UiActionsEnhancedDynamicActionManager as DynamicActionManager, - AdvancedUiActionsStart, -} from '@kbn/ui-actions-enhanced-plugin/public'; -import { uiActionsEnhancedPluginMock } from '@kbn/ui-actions-enhanced-plugin/public/mocks'; - -export class MockEmbeddable extends Embeddable { - public rootType = 'dashboard'; - public readonly type = 'mock'; - private readonly triggers: string[] = []; - constructor(initialInput: EmbeddableInput, params: { supportedTriggers?: string[] }) { - super(initialInput, {}, undefined); - this.triggers = params.supportedTriggers ?? []; - } - public render(node: HTMLElement) {} - public reload() {} - public supportedTriggers(): string[] { - return this.triggers; - } - public getRoot() { - return { - type: this.rootType, - } as Embeddable; - } -} - -export const enhanceEmbeddable = ( - embeddable: E, - uiActions: AdvancedUiActionsStart = uiActionsEnhancedPluginMock.createStartContract() -): EnhancedEmbeddable => { - (embeddable as EnhancedEmbeddable).enhancements = { - dynamicActions: new DynamicActionManager({ - storage: new MemoryActionStorage(), - isCompatible: async () => true, - uiActions, - }), - }; - return embeddable as EnhancedEmbeddable; -}; diff --git a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/embeddable_to_dashboard_drilldown/embeddable_to_dashboard_drilldown.test.tsx b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/embeddable_to_dashboard_drilldown/embeddable_to_dashboard_drilldown.test.tsx index e3bf4a4d468c8..00ca768a04848 100644 --- a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/embeddable_to_dashboard_drilldown/embeddable_to_dashboard_drilldown.test.tsx +++ b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/embeddable_to_dashboard_drilldown/embeddable_to_dashboard_drilldown.test.tsx @@ -6,15 +6,14 @@ */ import { Filter, RangeFilter, FilterStateStore, Query, TimeRange } from '@kbn/es-query'; -import { EmbeddableToDashboardDrilldown } from './embeddable_to_dashboard_drilldown'; +import { type Context, EmbeddableToDashboardDrilldown } from './embeddable_to_dashboard_drilldown'; import { AbstractDashboardDrilldownConfig as Config } from '../abstract_dashboard_drilldown'; import { savedObjectsServiceMock } from '@kbn/core/public/mocks'; -import { ApplyGlobalFilterActionContext } from '@kbn/unified-search-plugin/public'; import { DashboardLocatorParams } from '@kbn/dashboard-plugin/public'; import { StartDependencies } from '../../../plugin'; import { StartServicesGetter } from '@kbn/kibana-utils-plugin/public/core'; -import { EnhancedEmbeddableContext } from '@kbn/embeddable-enhanced-plugin/public'; import { DashboardAppLocatorDefinition } from '@kbn/dashboard-plugin/public/dashboard_app/locator/locator'; +import { BehaviorSubject } from 'rxjs'; describe('.isConfigValid()', () => { const drilldown = new EmbeddableToDashboardDrilldown({} as any); @@ -118,15 +117,18 @@ describe('.execute() & getHref', () => { const context = { filters: filtersFromEvent, embeddable: { - getInput: () => ({ - filters: [], - timeRange: { from: 'now-15m', to: 'now' }, - query: { query: 'test', language: 'kuery' }, - ...embeddableInput, - }), + parentApi: { + localFilters: new BehaviorSubject(embeddableInput.filters ? embeddableInput.filters : []), + localQuery: new BehaviorSubject( + embeddableInput.query ? embeddableInput.query : { query: 'test', language: 'kuery' } + ), + localTimeRange: new BehaviorSubject( + embeddableInput.timeRange ? embeddableInput.timeRange : { from: 'now-15m', to: 'now' } + ), + }, }, timeFieldName, - } as unknown as ApplyGlobalFilterActionContext & EnhancedEmbeddableContext; + } as Context; await drilldown.execute(completeConfig, context); diff --git a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/embeddable_to_dashboard_drilldown/embeddable_to_dashboard_drilldown.tsx b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/embeddable_to_dashboard_drilldown/embeddable_to_dashboard_drilldown.tsx index 0838297ee72e3..87790201b5099 100644 --- a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/embeddable_to_dashboard_drilldown/embeddable_to_dashboard_drilldown.tsx +++ b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/embeddable_to_dashboard_drilldown/embeddable_to_dashboard_drilldown.tsx @@ -5,6 +5,7 @@ * 2.0. */ import { extractTimeRange, isFilterPinned } from '@kbn/es-query'; +import type { HasParentApi, PublishesLocalUnifiedSearch } from '@kbn/presentation-publishing'; import type { KibanaLocation } from '@kbn/share-plugin/public'; import { cleanEmptyKeys, @@ -14,7 +15,6 @@ import { import { setStateToKbnUrl } from '@kbn/kibana-utils-plugin/public'; import { APPLY_FILTER_TRIGGER } from '@kbn/data-plugin/public'; import { ApplyGlobalFilterActionContext } from '@kbn/unified-search-plugin/public'; -import { EnhancedEmbeddableContext } from '@kbn/embeddable-enhanced-plugin/public'; import { IMAGE_CLICK_TRIGGER } from '@kbn/image-embeddable-plugin/public'; import { AbstractDashboardDrilldown, @@ -24,7 +24,11 @@ import { EMBEDDABLE_TO_DASHBOARD_DRILLDOWN } from './constants'; import { createExtract, createInject } from '../../../../common'; import { AbstractDashboardDrilldownConfig as Config } from '../abstract_dashboard_drilldown'; -type Context = EnhancedEmbeddableContext & ApplyGlobalFilterActionContext; +export type Context = ApplyGlobalFilterActionContext & { + embeddable: Partial< + PublishesLocalUnifiedSearch & HasParentApi> + >; +}; export type Params = AbstractDashboardDrilldownParams; /** diff --git a/x-pack/plugins/dashboard_enhanced/tsconfig.json b/x-pack/plugins/dashboard_enhanced/tsconfig.json index 4c08a46b6e2d6..0125f8c463513 100644 --- a/x-pack/plugins/dashboard_enhanced/tsconfig.json +++ b/x-pack/plugins/dashboard_enhanced/tsconfig.json @@ -19,7 +19,9 @@ "@kbn/unified-search-plugin", "@kbn/ui-actions-plugin", "@kbn/image-embeddable-plugin", - "@kbn/presentation-util-plugin" + "@kbn/presentation-util-plugin", + "@kbn/presentation-containers", + "@kbn/presentation-publishing" ], "exclude": ["target/**/*"] } diff --git a/x-pack/plugins/embeddable_enhanced/public/actions/drilldown_grouping.ts b/x-pack/plugins/embeddable_enhanced/public/actions/drilldown_grouping.ts index 86ca2030d2ae7..1e896817b3242 100644 --- a/x-pack/plugins/embeddable_enhanced/public/actions/drilldown_grouping.ts +++ b/x-pack/plugins/embeddable_enhanced/public/actions/drilldown_grouping.ts @@ -6,12 +6,9 @@ */ import { i18n } from '@kbn/i18n'; -import { IEmbeddable } from '@kbn/embeddable-plugin/public'; import { UiActionsPresentableGrouping as PresentableGrouping } from '@kbn/ui-actions-plugin/public'; -export const drilldownGrouping: PresentableGrouping<{ - embeddable?: IEmbeddable; -}> = [ +export const drilldownGrouping: PresentableGrouping = [ { id: 'drilldowns', getDisplayName: () => diff --git a/x-pack/plugins/embeddable_enhanced/public/embeddables/interfaces/has_dynamic_actions.ts b/x-pack/plugins/embeddable_enhanced/public/embeddables/interfaces/has_dynamic_actions.ts new file mode 100644 index 0000000000000..d92fd2138b56b --- /dev/null +++ b/x-pack/plugins/embeddable_enhanced/public/embeddables/interfaces/has_dynamic_actions.ts @@ -0,0 +1,20 @@ +/* + * 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 { UiActionsEnhancedDynamicActionManager as DynamicActionManager } from '@kbn/ui-actions-enhanced-plugin/public'; + +export interface HasDynamicActions { + enhancements: { + dynamicActions: DynamicActionManager; + }; +} + +export const apiHasDynamicActions = (api: unknown): api is HasDynamicActions => { + return Boolean( + api && typeof (api as HasDynamicActions).enhancements?.dynamicActions === 'object' + ); +}; diff --git a/x-pack/plugins/embeddable_enhanced/public/index.ts b/x-pack/plugins/embeddable_enhanced/public/index.ts index 63bd6d17ddcb9..53fec5c83522a 100644 --- a/x-pack/plugins/embeddable_enhanced/public/index.ts +++ b/x-pack/plugins/embeddable_enhanced/public/index.ts @@ -21,4 +21,8 @@ export function plugin(context: PluginInitializerContext) { export type { EnhancedEmbeddable, EnhancedEmbeddableContext } from './types'; export { isEnhancedEmbeddable } from './embeddables'; +export { + type HasDynamicActions, + apiHasDynamicActions, +} from './embeddables/interfaces/has_dynamic_actions'; export { drilldownGrouping as embeddableEnhancedDrilldownGrouping } from './actions'; diff --git a/x-pack/plugins/embeddable_enhanced/public/types.ts b/x-pack/plugins/embeddable_enhanced/public/types.ts index 7500a37e120cc..fd5a0f689f74b 100644 --- a/x-pack/plugins/embeddable_enhanced/public/types.ts +++ b/x-pack/plugins/embeddable_enhanced/public/types.ts @@ -17,6 +17,9 @@ export type EnhancedEmbeddable = E & { }; }; +/** + * @deprecated use `EmbeddableApiContext` from `@kbn/presentation-publishing` + */ export interface EnhancedEmbeddableContext { embeddable: EnhancedEmbeddable; } From 956fa0af4e1d6ea6e9da6baf45738ecd84be2af5 Mon Sep 17 00:00:00 2001 From: Faisal Kanout Date: Mon, 12 Feb 2024 20:50:43 +0300 Subject: [PATCH 13/83] =?UTF-8?q?[OBS-UX-MNGMT]=20Remove=20the=20Beta=20ba?= =?UTF-8?q?dge=20and=20GA=20the=20Custom=20Threshold=20rule=20=F0=9F=8E=89?= =?UTF-8?q?=20(#176514)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary It closes https://github.com/elastic/kibana/issues/172942 by removing the Beta badge. Related to https://github.com/elastic/observability-docs/issues/3549 Screenshot 2024-02-08 at 16 09 12 ## Release note: We are excited to announce the general availability of the Custom Threshold rule. The Custom Threshold rule is an Observability alerting rule that allows users to define and alert on custom threshold values. The rule can be customized to act on particular data views, using specific aggregations (like sum, average, min, max, percentile, rate, etc.) and via the use of basic math or boolean logic to define the custom thresholds. For more information, please refer to the [user guide](https://www.elastic.co/guide/en/observability/current/custom-threshold-alert.html). --- .../components/custom_threshold/mocks/custom_threshold_rule.ts | 2 +- .../custom_threshold/register_custom_threshold_rule_type.ts | 2 +- .../observability/custom_threshold_rule/avg_pct_fired.ts | 2 +- .../observability/custom_threshold_rule/avg_pct_no_data.ts | 2 +- .../observability/custom_threshold_rule/avg_us_fired.ts | 2 +- .../custom_threshold_rule/custom_eq_avg_bytes_fired.ts | 2 +- .../custom_threshold_rule/documents_count_fired.ts | 2 +- .../observability/custom_threshold_rule/group_by_fired.ts | 2 +- .../observability/custom_threshold_rule/p99_pct_fired.ts | 2 +- .../observability/custom_threshold_rule/rate_bytes_fired.ts | 2 +- .../observability/custom_threshold_rule/avg_pct_fired.ts | 2 +- .../observability/custom_threshold_rule/avg_pct_no_data.ts | 2 +- .../custom_threshold_rule/custom_eq_avg_bytes_fired.ts | 2 +- .../custom_threshold_rule/documents_count_fired.ts | 2 +- .../observability/custom_threshold_rule/group_by_fired.ts | 2 +- .../observability/custom_threshold_rule/p99_bytes_fired.ts | 2 +- 16 files changed, 16 insertions(+), 16 deletions(-) diff --git a/x-pack/plugins/observability/public/components/custom_threshold/mocks/custom_threshold_rule.ts b/x-pack/plugins/observability/public/components/custom_threshold/mocks/custom_threshold_rule.ts index 7616b1b9e2953..4961943f79712 100644 --- a/x-pack/plugins/observability/public/components/custom_threshold/mocks/custom_threshold_rule.ts +++ b/x-pack/plugins/observability/public/components/custom_threshold/mocks/custom_threshold_rule.ts @@ -247,7 +247,7 @@ export const buildCustomThresholdAlert = ( }, 'kibana.alert.evaluation.values': [2500, 5], 'kibana.alert.group': [{ field: 'host.name', value: 'host-1' }], - 'kibana.alert.rule.category': 'Custom threshold (Beta)', + 'kibana.alert.rule.category': 'Custom threshold', 'kibana.alert.rule.consumer': 'alerts', 'kibana.alert.rule.execution.uuid': '62dd07ef-ead9-4b1f-a415-7c83d03925f7', 'kibana.alert.rule.name': 'One condition', diff --git a/x-pack/plugins/observability/server/lib/rules/custom_threshold/register_custom_threshold_rule_type.ts b/x-pack/plugins/observability/server/lib/rules/custom_threshold/register_custom_threshold_rule_type.ts index df64a67ca8e4a..93286a2988e04 100644 --- a/x-pack/plugins/observability/server/lib/rules/custom_threshold/register_custom_threshold_rule_type.ts +++ b/x-pack/plugins/observability/server/lib/rules/custom_threshold/register_custom_threshold_rule_type.ts @@ -129,7 +129,7 @@ export function thresholdRuleType( return { id: OBSERVABILITY_THRESHOLD_RULE_TYPE_ID, name: i18n.translate('xpack.observability.threshold.ruleName', { - defaultMessage: 'Custom threshold (Beta)', + defaultMessage: 'Custom threshold', }), fieldsForAAD: CUSTOM_THRESHOLD_AAD_FIELDS, validate: { diff --git a/x-pack/test/alerting_api_integration/observability/custom_threshold_rule/avg_pct_fired.ts b/x-pack/test/alerting_api_integration/observability/custom_threshold_rule/avg_pct_fired.ts index 929913f15f39f..6120c7eb93087 100644 --- a/x-pack/test/alerting_api_integration/observability/custom_threshold_rule/avg_pct_fired.ts +++ b/x-pack/test/alerting_api_integration/observability/custom_threshold_rule/avg_pct_fired.ts @@ -185,7 +185,7 @@ export default function ({ getService }: FtrProviderContext) { expect(resp.hits.hits[0]._source).property( 'kibana.alert.rule.category', - 'Custom threshold (Beta)' + 'Custom threshold' ); expect(resp.hits.hits[0]._source).property('kibana.alert.rule.consumer', 'logs'); expect(resp.hits.hits[0]._source).property('kibana.alert.rule.name', 'Threshold rule'); diff --git a/x-pack/test/alerting_api_integration/observability/custom_threshold_rule/avg_pct_no_data.ts b/x-pack/test/alerting_api_integration/observability/custom_threshold_rule/avg_pct_no_data.ts index 1b662f2af5aee..9c265655dbe53 100644 --- a/x-pack/test/alerting_api_integration/observability/custom_threshold_rule/avg_pct_no_data.ts +++ b/x-pack/test/alerting_api_integration/observability/custom_threshold_rule/avg_pct_no_data.ts @@ -153,7 +153,7 @@ export default function ({ getService }: FtrProviderContext) { expect(resp.hits.hits[0]._source).property( 'kibana.alert.rule.category', - 'Custom threshold (Beta)' + 'Custom threshold' ); expect(resp.hits.hits[0]._source).property('kibana.alert.rule.consumer', 'logs'); expect(resp.hits.hits[0]._source).property('kibana.alert.rule.name', 'Threshold rule'); diff --git a/x-pack/test/alerting_api_integration/observability/custom_threshold_rule/avg_us_fired.ts b/x-pack/test/alerting_api_integration/observability/custom_threshold_rule/avg_us_fired.ts index 1a32a0af3612e..74b8d2e178a37 100644 --- a/x-pack/test/alerting_api_integration/observability/custom_threshold_rule/avg_us_fired.ts +++ b/x-pack/test/alerting_api_integration/observability/custom_threshold_rule/avg_us_fired.ts @@ -163,7 +163,7 @@ export default function ({ getService }: FtrProviderContext) { expect(resp.hits.hits[0]._source).property( 'kibana.alert.rule.category', - 'Custom threshold (Beta)' + 'Custom threshold' ); expect(resp.hits.hits[0]._source).property('kibana.alert.rule.consumer', 'logs'); expect(resp.hits.hits[0]._source).property('kibana.alert.rule.name', 'Threshold rule'); diff --git a/x-pack/test/alerting_api_integration/observability/custom_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 index d1c8a2927837c..b402f80c399ac 100644 --- a/x-pack/test/alerting_api_integration/observability/custom_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 @@ -182,7 +182,7 @@ export default function ({ getService }: FtrProviderContext) { expect(resp.hits.hits[0]._source).property( 'kibana.alert.rule.category', - 'Custom threshold (Beta)' + 'Custom threshold' ); expect(resp.hits.hits[0]._source).property('kibana.alert.rule.consumer', 'logs'); expect(resp.hits.hits[0]._source).property('kibana.alert.rule.name', 'Threshold rule'); diff --git a/x-pack/test/alerting_api_integration/observability/custom_threshold_rule/documents_count_fired.ts b/x-pack/test/alerting_api_integration/observability/custom_threshold_rule/documents_count_fired.ts index 43029573defd8..c3fa31140b803 100644 --- a/x-pack/test/alerting_api_integration/observability/custom_threshold_rule/documents_count_fired.ts +++ b/x-pack/test/alerting_api_integration/observability/custom_threshold_rule/documents_count_fired.ts @@ -182,7 +182,7 @@ export default function ({ getService }: FtrProviderContext) { expect(resp.hits.hits[0]._source).property( 'kibana.alert.rule.category', - 'Custom threshold (Beta)' + 'Custom threshold' ); expect(resp.hits.hits[0]._source).property('kibana.alert.rule.consumer', 'logs'); expect(resp.hits.hits[0]._source).property('kibana.alert.rule.name', 'Threshold rule'); diff --git a/x-pack/test/alerting_api_integration/observability/custom_threshold_rule/group_by_fired.ts b/x-pack/test/alerting_api_integration/observability/custom_threshold_rule/group_by_fired.ts index 55f2d61be2b0e..d28525f01118e 100644 --- a/x-pack/test/alerting_api_integration/observability/custom_threshold_rule/group_by_fired.ts +++ b/x-pack/test/alerting_api_integration/observability/custom_threshold_rule/group_by_fired.ts @@ -182,7 +182,7 @@ export default function ({ getService }: FtrProviderContext) { expect(resp.hits.hits[0]._source).property( 'kibana.alert.rule.category', - 'Custom threshold (Beta)' + 'Custom threshold' ); expect(resp.hits.hits[0]._source).property('kibana.alert.rule.consumer', 'logs'); expect(resp.hits.hits[0]._source).property('kibana.alert.rule.name', 'Threshold rule'); diff --git a/x-pack/test/alerting_api_integration/observability/custom_threshold_rule/p99_pct_fired.ts b/x-pack/test/alerting_api_integration/observability/custom_threshold_rule/p99_pct_fired.ts index 942d025dd11f3..05c86f07d8efb 100644 --- a/x-pack/test/alerting_api_integration/observability/custom_threshold_rule/p99_pct_fired.ts +++ b/x-pack/test/alerting_api_integration/observability/custom_threshold_rule/p99_pct_fired.ts @@ -180,7 +180,7 @@ export default function ({ getService }: FtrProviderContext) { expect(resp.hits.hits[0]._source).property( 'kibana.alert.rule.category', - 'Custom threshold (Beta)' + 'Custom threshold' ); expect(resp.hits.hits[0]._source).property('kibana.alert.rule.consumer', 'logs'); expect(resp.hits.hits[0]._source).property('kibana.alert.rule.name', 'Threshold rule'); diff --git a/x-pack/test/alerting_api_integration/observability/custom_threshold_rule/rate_bytes_fired.ts b/x-pack/test/alerting_api_integration/observability/custom_threshold_rule/rate_bytes_fired.ts index b76dd0c2581fc..dc41b1b91f29b 100644 --- a/x-pack/test/alerting_api_integration/observability/custom_threshold_rule/rate_bytes_fired.ts +++ b/x-pack/test/alerting_api_integration/observability/custom_threshold_rule/rate_bytes_fired.ts @@ -178,7 +178,7 @@ export default function ({ getService }: FtrProviderContext) { expect(resp.hits.hits[0]._source).property( 'kibana.alert.rule.category', - 'Custom threshold (Beta)' + 'Custom threshold' ); expect(resp.hits.hits[0]._source).property('kibana.alert.rule.consumer', 'logs'); expect(resp.hits.hits[0]._source).property('kibana.alert.rule.name', 'Threshold rule'); diff --git a/x-pack/test_serverless/api_integration/test_suites/observability/custom_threshold_rule/avg_pct_fired.ts b/x-pack/test_serverless/api_integration/test_suites/observability/custom_threshold_rule/avg_pct_fired.ts index 47ed06c1ad440..8141ebd43bbc4 100644 --- a/x-pack/test_serverless/api_integration/test_suites/observability/custom_threshold_rule/avg_pct_fired.ts +++ b/x-pack/test_serverless/api_integration/test_suites/observability/custom_threshold_rule/avg_pct_fired.ts @@ -167,7 +167,7 @@ export default function ({ getService }: FtrProviderContext) { expect(resp.hits.hits[0]._source).property( 'kibana.alert.rule.category', - 'Custom threshold (Beta)' + 'Custom threshold' ); expect(resp.hits.hits[0]._source).property('kibana.alert.rule.consumer', 'observability'); expect(resp.hits.hits[0]._source).property('kibana.alert.rule.name', 'Threshold rule'); diff --git a/x-pack/test_serverless/api_integration/test_suites/observability/custom_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 index 0411c6948eae1..e8cff15170cc9 100644 --- a/x-pack/test_serverless/api_integration/test_suites/observability/custom_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 @@ -138,7 +138,7 @@ export default function ({ getService }: FtrProviderContext) { expect(resp.hits.hits[0]._source).property( 'kibana.alert.rule.category', - 'Custom threshold (Beta)' + 'Custom threshold' ); expect(resp.hits.hits[0]._source).property('kibana.alert.rule.consumer', 'observability'); expect(resp.hits.hits[0]._source).property('kibana.alert.rule.name', 'Threshold rule'); diff --git a/x-pack/test_serverless/api_integration/test_suites/observability/custom_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 index 8b6d76a380748..c236b4cc93261 100644 --- a/x-pack/test_serverless/api_integration/test_suites/observability/custom_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 @@ -176,7 +176,7 @@ export default function ({ getService }: FtrProviderContext) { expect(resp.hits.hits[0]._source).property( 'kibana.alert.rule.category', - 'Custom threshold (Beta)' + 'Custom threshold' ); expect(resp.hits.hits[0]._source).property('kibana.alert.rule.consumer', 'observability'); expect(resp.hits.hits[0]._source).property('kibana.alert.rule.name', 'Threshold rule'); diff --git a/x-pack/test_serverless/api_integration/test_suites/observability/custom_threshold_rule/documents_count_fired.ts b/x-pack/test_serverless/api_integration/test_suites/observability/custom_threshold_rule/documents_count_fired.ts index 15c34602e0a78..2353fe0c82bf0 100644 --- a/x-pack/test_serverless/api_integration/test_suites/observability/custom_threshold_rule/documents_count_fired.ts +++ b/x-pack/test_serverless/api_integration/test_suites/observability/custom_threshold_rule/documents_count_fired.ts @@ -167,7 +167,7 @@ export default function ({ getService }: FtrProviderContext) { expect(resp.hits.hits[0]._source).property( 'kibana.alert.rule.category', - 'Custom threshold (Beta)' + 'Custom threshold' ); expect(resp.hits.hits[0]._source).property('kibana.alert.rule.consumer', 'observability'); expect(resp.hits.hits[0]._source).property('kibana.alert.rule.name', 'Threshold rule'); diff --git a/x-pack/test_serverless/api_integration/test_suites/observability/custom_threshold_rule/group_by_fired.ts b/x-pack/test_serverless/api_integration/test_suites/observability/custom_threshold_rule/group_by_fired.ts index 264057cacff1c..c0694cf59c435 100644 --- a/x-pack/test_serverless/api_integration/test_suites/observability/custom_threshold_rule/group_by_fired.ts +++ b/x-pack/test_serverless/api_integration/test_suites/observability/custom_threshold_rule/group_by_fired.ts @@ -185,7 +185,7 @@ export default function ({ getService }: FtrProviderContext) { expect(resp.hits.hits[0]._source).property( 'kibana.alert.rule.category', - 'Custom threshold (Beta)' + 'Custom threshold' ); expect(resp.hits.hits[0]._source).property('kibana.alert.rule.consumer', 'observability'); expect(resp.hits.hits[0]._source).property('kibana.alert.rule.name', 'Threshold rule'); diff --git a/x-pack/test_serverless/api_integration/test_suites/observability/custom_threshold_rule/p99_bytes_fired.ts b/x-pack/test_serverless/api_integration/test_suites/observability/custom_threshold_rule/p99_bytes_fired.ts index 70f3192c117e9..c00139c36380d 100644 --- a/x-pack/test_serverless/api_integration/test_suites/observability/custom_threshold_rule/p99_bytes_fired.ts +++ b/x-pack/test_serverless/api_integration/test_suites/observability/custom_threshold_rule/p99_bytes_fired.ts @@ -152,7 +152,7 @@ export default function ({ getService }: FtrProviderContext) { expect(resp.hits.hits[0]._source).property( 'kibana.alert.rule.category', - 'Custom threshold (Beta)' + 'Custom threshold' ); expect(resp.hits.hits[0]._source).property('kibana.alert.rule.consumer', 'observability'); expect(resp.hits.hits[0]._source).property('kibana.alert.rule.name', 'Threshold rule'); From e93add06a47305f916cdcb7de1175f8ffd531e27 Mon Sep 17 00:00:00 2001 From: Davis Plumlee <56367316+dplumlee@users.noreply.github.com> Date: Mon, 12 Feb 2024 12:57:15 -0500 Subject: [PATCH 14/83] [Security Solution] Per-field diffs test plan (#176474) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary Resolves https://github.com/elastic/kibana/issues/176473 This PR introduces a test plan for the per-field diff preview. This preview is displayed in the upgrade prebuilt rule flyout under the `Updates` tab. Screenshot 2024-02-08 at 1 35 05 AM --- .../installation_and_upgrade.md | 98 +++++++++++++++++++ 1 file changed, 98 insertions(+) diff --git a/x-pack/plugins/security_solution/docs/testing/test_plans/detection_response/prebuilt_rules/installation_and_upgrade.md b/x-pack/plugins/security_solution/docs/testing/test_plans/detection_response/prebuilt_rules/installation_and_upgrade.md index b60609b45be9d..41e379906eb42 100644 --- a/x-pack/plugins/security_solution/docs/testing/test_plans/detection_response/prebuilt_rules/installation_and_upgrade.md +++ b/x-pack/plugins/security_solution/docs/testing/test_plans/detection_response/prebuilt_rules/installation_and_upgrade.md @@ -64,6 +64,11 @@ Status: `in progress`. The current test plan matches `Milestone 2` of the [Rule - [**Scenario: Properties with semantically equal values should not be shown as modified**](#scenario-properties-with-semantically-equal-values-should-not-be-shown-as-modified) - [**Scenario: Unchanged sections of a rule should be hidden by default**](#scenario-unchanged-sections-of-a-rule-should-be-hidden-by-default) - [**Scenario: Properties should be sorted alphabetically**](#scenario-properties-should-be-sorted-alphabetically) + - [Rule upgrade workflow: viewing rule changes in per-field diff view](#rule-upgrade-workflow-viewing-rule-changes-in-per-field-diff-view) + - [**Scenario: User can see changes in a side-by-side per-field diff view**](#scenario-user-can-see-changes-in-a-side-by-side-per-field-diff-view) + - [**Scenario: Field groupings should be rendered together in the same accordion panel**](#scenario-field-groupings-should-be-rendered-together-in-the-same-accordion-panel) + - [**Scenario: Undefined values are displayed with empty diffs**](#scenario-undefined-values-are-displayed-with-empty-diffs) + - [**Scenario: Field diff components have the same grouping and order as in rule details overview**](#scenario-field-diff-components-have-the-same-grouping-and-order-as-in-rule-details-overview) - [Rule upgrade workflow: preserving rule bound data](#rule-upgrade-workflow-preserving-rule-bound-data) - [**Scenario: Rule bound data is preserved after upgrading a rule to a newer version with the same rule type**](#scenario-rule-bound-data-is-preserved-after-upgrading-a-rule-to-a-newer-version-with-the-same-rule-type) - [**Scenario: Rule bound data is preserved after upgrading a rule to a newer version with a different rule type**](#scenario-rule-bound-data-is-preserved-after-upgrading-a-rule-to-a-newer-version-with-a-different-rule-type) @@ -953,6 +958,99 @@ When a user expands all hidden sections Then all properties of the rule should be sorted alphabetically ``` +### Rule upgrade workflow: viewing rule changes in per-field diff view + +#### **Scenario: User can see changes in a side-by-side per-field diff view** + +**Automation**: 1 e2e test + +```Gherkin +Given X prebuilt rules are installed in Kibana +And for Y of these rules new versions are available +When user opens the Rule Updates table and selects a rule +Then the per-field upgrade preview should open +And rule changes should be displayed in a two-column diff view with each field in its own accordion component +And all field diff accordions should be open by default +And correct rule version numbers should be displayed in their respective columns +When the user selects another rule without closing the preview +Then the preview should display the changes for the newly selected rule +``` + +#### **Scenario: User can see changes when updated rule is a different rule type** + +**Automation**: 1 UI integration test + +```Gherkin +Given a prebuilt rule is installed in Kibana +And this rule has an update available that changes the rule type +When user opens the upgrade preview +Then the rule type changes should be displayed in grouped field diffs with corresponding query fields +And a tooltip is displayed with information about changing rule types +``` + +#### **Scenario: Field groupings should be rendered together in the same accordion panel** + +**Automation**: 1 UI integration test + +```Gherkin +Given a prebuilt rule is installed in Kibana +And this rule contains one or more values +When user opens the upgrade preview +The diff accordion panel should display its grouped rule properties +And each property should have its name displayed inside the panel above its value + +Examples: +| field | +| data_source | +| kql_query | +| eql_query | +| esql_query | +| threat_query | +| rule_schedule | +| rule_name_override | +| timestamp_override | +| timeline_template | +| building_block | +| threshold | +``` + +#### **Scenario: Undefined values are displayed with empty diffs** + +**Automation**: 1 UI integration test + +```Gherkin +Given a prebuilt rule is installed in Kibana +And this rule has field in the version that didn't exist in the version +When a user opens the upgrade preview +Then the preview should open +And the old/new field should render an empty panel + +Examples: +| version_one | version_two | +| target | current | +| current | target | +``` + +#### **Scenario: Field diff components have the same grouping and order as in rule details overview** + +**Automation**: 1 UI integration test + +```Gherkin +Given a prebuilt rule is installed in Kibana +And this rule has multiple fields that are different between the current and target version +When a user opens the upgrade preview +Then the multiple field diff accordions should be sorted in the same order as on the rule details overview tab +And the field diff accordions should be grouped inside its corresponding
accordion +And any
accordion that doesn't have fields inside it shouldn't be displayed + +Examples: +| section | +| About | +| Definition | +| Schedule | +| Setup Guide | +``` + ### Rule upgrade workflow: preserving rule bound data #### **Scenario: Rule bound data is preserved after upgrading a rule to a newer version with the same rule type** From 0f31c0bff36a375f47aefc41d604551e56fac181 Mon Sep 17 00:00:00 2001 From: Julia Rechkunova Date: Mon, 12 Feb 2024 19:21:00 +0100 Subject: [PATCH 15/83] [Discover] Unskip update data view test (#176508) - Closes https://github.com/elastic/kibana/issues/174066 95x https://buildkite.com/elastic/kibana-flaky-test-suite-runner/builds/5099 ### 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 - [x] [Flaky Test Runner](https://ci-stats.kibana.dev/trigger_flaky_test_runner/1) was used on any tests changed --- .../data_views/_data_view_create_delete.ts | 3 +-- test/functional/page_objects/settings_page.ts | 16 ++++++++++++---- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/test/functional/apps/management/data_views/_data_view_create_delete.ts b/test/functional/apps/management/data_views/_data_view_create_delete.ts index 651dbce7ada02..e3bc2240887ad 100644 --- a/test/functional/apps/management/data_views/_data_view_create_delete.ts +++ b/test/functional/apps/management/data_views/_data_view_create_delete.ts @@ -20,8 +20,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const es = getService('es'); const PageObjects = getPageObjects(['settings', 'common', 'header']); - // FLAKY: https://github.com/elastic/kibana/issues/174066 - describe.skip('creating and deleting default data view', function describeIndexTests() { + describe('creating and deleting default data view', function describeIndexTests() { before(async function () { await esArchiver.emptyKibanaIndex(); await esArchiver.loadIfNeeded( diff --git a/test/functional/page_objects/settings_page.ts b/test/functional/page_objects/settings_page.ts index 3208fb782e272..12ff79829fa7e 100644 --- a/test/functional/page_objects/settings_page.ts +++ b/test/functional/page_objects/settings_page.ts @@ -215,14 +215,14 @@ export class SettingsPageObject extends FtrService { } async getSaveDataViewButtonActive() { - await this.retry.try(async () => { - expect( + await this.retry.waitFor('active save button', async () => { + return ( ( await this.find.allByCssSelector( '[data-test-subj="saveIndexPatternButton"]:not(.euiButton-isDisabled)' ) - ).length - ).to.be(1); + ).length === 1 + ); }); return await this.testSubjects.find('saveIndexPatternButton'); } @@ -615,7 +615,13 @@ export class SettingsPageObject extends FtrService { await this.clickEditIndexButton(); await this.header.waitUntilLoadingHasFinished(); + let hasSubmittedTheForm = false; + await this.retry.try(async () => { + if (hasSubmittedTheForm && !(await this.testSubjects.exists('indexPatternEditorFlyout'))) { + // the flyout got closed + return; + } if (dataViewName) { await this.setNameField(dataViewName); } @@ -627,6 +633,8 @@ export class SettingsPageObject extends FtrService { const indexPatternSaveBtn = await this.getSaveIndexPatternButton(); await indexPatternSaveBtn.click(); + hasSubmittedTheForm = true; + const form = await this.testSubjects.findAll('indexPatternEditorForm'); const hasValidationErrors = form.length !== 0 && (await form[0].getAttribute('data-validation-error')) === '1'; From 7a8d328cfa0f59516124a760710d0d5831680abf Mon Sep 17 00:00:00 2001 From: Jill Guyonnet Date: Mon, 12 Feb 2024 18:21:36 +0000 Subject: [PATCH 16/83] [Fleet] Fix flaky serverless API integration tests (#176614) ## Summary Closes https://github.com/elastic/kibana/issues/176352 Closes https://github.com/elastic/kibana/issues/176399 https://github.com/elastic/kibana/pull/175315 added the possibility to configure new Fleet Server hosts in serverless, with the constraint that the host URL must match the default URL. The API integration tests written to test this have been flaky, probably due to request timeout when fetching all Fleet Server hosts. This PR improves this by directly retrieving the default Fleet Server host by id. This fix has been tested using the Flaky Test Runner Pipeline, with 25 test runs for observability and security project types: https://buildkite.com/elastic/kibana-flaky-test-suite-runner/builds/5140 ### 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 - [x] [Flaky Test Runner](https://ci-stats.kibana.dev/trigger_flaky_test_runner/1) was used on any tests changed --- .../routes/fleet_server_hosts/handler.test.ts | 34 ++++++++----------- .../routes/fleet_server_hosts/handler.ts | 22 ++++-------- .../test_suites/observability/fleet/fleet.ts | 3 +- .../test_suites/security/fleet/fleet.ts | 3 +- 4 files changed, 23 insertions(+), 39 deletions(-) diff --git a/x-pack/plugins/fleet/server/routes/fleet_server_hosts/handler.test.ts b/x-pack/plugins/fleet/server/routes/fleet_server_hosts/handler.test.ts index e65942a50c934..3d7b35682dc6e 100644 --- a/x-pack/plugins/fleet/server/routes/fleet_server_hosts/handler.test.ts +++ b/x-pack/plugins/fleet/server/routes/fleet_server_hosts/handler.test.ts @@ -33,14 +33,10 @@ describe('fleet server hosts handler', () => { jest .spyOn(fleetServerService, 'updateFleetServerHost') .mockResolvedValue({ id: 'host1' } as any); - jest.spyOn(fleetServerService, 'listFleetServerHosts').mockResolvedValue({ - items: [ - { id: SERVERLESS_DEFAULT_FLEET_SERVER_HOST_ID, host_urls: ['http://elasticsearch:9200'] }, - ] as any, - total: 1, - page: 1, - perPage: 1, - }); + jest.spyOn(fleetServerService, 'getFleetServerHost').mockResolvedValue({ + id: SERVERLESS_DEFAULT_FLEET_SERVER_HOST_ID, + host_urls: ['http://elasticsearch:9200'], + } as any); jest .spyOn(agentPolicyService, 'bumpAllAgentPoliciesForFleetServerHosts') .mockResolvedValue({} as any); @@ -118,17 +114,17 @@ describe('fleet server hosts handler', () => { expect(res).toEqual({ body: { item: { id: 'host1' } } }); }); - // it('should return ok on put in stateful if host url is different from default', async () => { - // jest - // .spyOn(appContextService, 'getCloud') - // .mockReturnValue({ isServerlessEnabled: false } as any); + it('should return ok on put in stateful if host url is different from default', async () => { + jest + .spyOn(appContextService, 'getCloud') + .mockReturnValue({ isServerlessEnabled: false } as any); - // const res = await putFleetServerHostHandler( - // mockContext, - // { body: { host_urls: ['http://localhost:8080'] }, params: { outputId: 'host1' } } as any, - // mockResponse as any - // ); + const res = await putFleetServerHostHandler( + mockContext, + { body: { host_urls: ['http://localhost:8080'] }, params: { outputId: 'host1' } } as any, + mockResponse as any + ); - // expect(res).toEqual({ body: { item: { id: 'host1' } } }); - // }); + expect(res).toEqual({ body: { item: { id: 'host1' } } }); + }); }); diff --git a/x-pack/plugins/fleet/server/routes/fleet_server_hosts/handler.ts b/x-pack/plugins/fleet/server/routes/fleet_server_hosts/handler.ts index eddce8df7c3e0..7e6c14506f3da 100644 --- a/x-pack/plugins/fleet/server/routes/fleet_server_hosts/handler.ts +++ b/x-pack/plugins/fleet/server/routes/fleet_server_hosts/handler.ts @@ -36,27 +36,17 @@ async function checkFleetServerHostsWriteAPIsAllowed( return; } - const defaultFleetServerHost = await getDefaultFleetServerHost(soClient); - if ( - defaultFleetServerHost === undefined || - !isEqual(hostUrls, defaultFleetServerHost.host_urls) - ) { + const serverlessDefaultFleetServerHost = await getFleetServerHost( + soClient, + SERVERLESS_DEFAULT_FLEET_SERVER_HOST_ID + ); + if (!isEqual(hostUrls, serverlessDefaultFleetServerHost.host_urls)) { throw new FleetServerHostUnauthorizedError( - `Fleet server host must have default URL in serverless${ - defaultFleetServerHost ? ': ' + defaultFleetServerHost.host_urls : '' - }` + `Fleet server host must have default URL in serverless: ${serverlessDefaultFleetServerHost.host_urls}` ); } } -async function getDefaultFleetServerHost(soClient: SavedObjectsClientContract) { - const res = await listFleetServerHosts(soClient); - const fleetServerHosts = res.items; - return fleetServerHosts.find( - (fleetServerHost) => fleetServerHost.id === SERVERLESS_DEFAULT_FLEET_SERVER_HOST_ID - ); -} - export const postFleetServerHost: RequestHandler< undefined, undefined, diff --git a/x-pack/test_serverless/api_integration/test_suites/observability/fleet/fleet.ts b/x-pack/test_serverless/api_integration/test_suites/observability/fleet/fleet.ts index 73f582f9aa9cb..98866bc50f431 100644 --- a/x-pack/test_serverless/api_integration/test_suites/observability/fleet/fleet.ts +++ b/x-pack/test_serverless/api_integration/test_suites/observability/fleet/fleet.ts @@ -12,8 +12,7 @@ export default function ({ getService }: FtrProviderContext) { const svlCommonApi = getService('svlCommonApi'); const supertest = getService('supertest'); - // Failing: See https://github.com/elastic/kibana/issues/176352 - describe.skip('fleet', function () { + describe('fleet', function () { it('rejects request to create a new fleet server hosts if host url is different from default', async () => { const { body, status } = await supertest .post('/api/fleet/fleet_server_hosts') diff --git a/x-pack/test_serverless/api_integration/test_suites/security/fleet/fleet.ts b/x-pack/test_serverless/api_integration/test_suites/security/fleet/fleet.ts index ba184e7687794..98866bc50f431 100644 --- a/x-pack/test_serverless/api_integration/test_suites/security/fleet/fleet.ts +++ b/x-pack/test_serverless/api_integration/test_suites/security/fleet/fleet.ts @@ -12,8 +12,7 @@ export default function ({ getService }: FtrProviderContext) { const svlCommonApi = getService('svlCommonApi'); const supertest = getService('supertest'); - // FLAKY: https://github.com/elastic/kibana/issues/176399 - describe.skip('fleet', function () { + describe('fleet', function () { it('rejects request to create a new fleet server hosts if host url is different from default', async () => { const { body, status } = await supertest .post('/api/fleet/fleet_server_hosts') From 30f333e392e38d4702356cefb1766ec92297afcc Mon Sep 17 00:00:00 2001 From: Melissa Alvarez Date: Mon, 12 Feb 2024 11:54:58 -0700 Subject: [PATCH 17/83] [ML] Data Frame Analytics functional tests: ensure job state is stopped before attempting to create custom url (#176622) ## Summary Fixes https://github.com/elastic/kibana/issues/164224 Extends timeout to wait for job to be in 'stopped' state before attempting to create custom url. Flaky test run https://buildkite.com/elastic/kibana-flaky-test-suite-runner/builds/5120 ### 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 - [ ] [Flaky Test Runner](https://ci-stats.kibana.dev/trigger_flaky_test_runner/1) was used on any tests changed - [ ] 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) Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../functional/apps/ml/data_frame_analytics/custom_urls.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/x-pack/test/functional/apps/ml/data_frame_analytics/custom_urls.ts b/x-pack/test/functional/apps/ml/data_frame_analytics/custom_urls.ts index 17f07c0d26297..ab1b46796dad4 100644 --- a/x-pack/test/functional/apps/ml/data_frame_analytics/custom_urls.ts +++ b/x-pack/test/functional/apps/ml/data_frame_analytics/custom_urls.ts @@ -38,8 +38,7 @@ export default function ({ getService }: FtrProviderContext) { const ml = getService('ml'); const browser = getService('browser'); - // FLAKY: https://github.com/elastic/kibana/issues/164224 - describe.skip('custom urls', function () { + describe('custom urls', function () { const dfaJobId = `fq_regression_${Date.now()}`; const generateDestinationIndex = (analyticsId: string) => `user-${analyticsId}`; let testDashboardId: string | null = null; @@ -74,7 +73,7 @@ export default function ({ getService }: FtrProviderContext) { await ml.testResources.createDataViewIfNeeded('ft_farequote', '@timestamp'); await ml.testResources.setKibanaTimeZoneToUTC(); await ml.securityUI.loginAsMlPowerUser(); - await ml.api.createAndRunDFAJob(dfaJobConfig); + await ml.api.createAndRunDFAJob(dfaJobConfig, 3 * 60 * 1000); }); after(async () => { From c1f39beb379ba0d4d6adc307a48ddbca5a447b72 Mon Sep 17 00:00:00 2001 From: Jiawei Wu <74562234+JiaweiWu@users.noreply.github.com> Date: Mon, 12 Feb 2024 11:00:00 -0800 Subject: [PATCH 18/83] [RAM] Add new API to allow for bulk untrack alerts using query DSL (#176264) ## Summary Resolves: https://github.com/elastic/kibana/issues/174640 This PR adds a new API: `/internal/alerting/alerts/_bulk_untrack_by_query` that allows active alerts to be untracked using ES query DSL. This PR also allows alerts to be bulk untracked by query using the alerts table. ![image](https://github.com/elastic/kibana/assets/74562234/242934d1-a034-401f-80b7-2c59473974b8) ### To Test: 1. Create 2 rule that generate alerts 2. Navigate to the alerts table, create a filter using the alerts table search bar to filter 1 of the alerts 3. Select the resulting alert, click "select all alerts" 4. Select "Selected 1 alert" and click "Mark as Untracked" 5. Assert the alert has been untracked 6. Let the rule run again 7. Assert a new active alert should be created 8. Retry steps 1-7 but with a recovered alert. Assert the recover alert does not get untracked ### 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 --------- Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../rule/apis/bulk_untrack_by_query/index.ts | 12 + .../bulk_untrack_by_query/schemas/latest.ts | 7 + .../apis/bulk_untrack_by_query/schemas/v1.ts | 13 + .../bulk_untrack_by_query/types/latest.ts | 8 + .../apis/bulk_untrack_by_query/types/v1.ts | 10 + .../server/alerts_service/alerts_service.ts | 4 +- .../lib/set_alerts_to_untracked.test.ts | 170 ++++++++++++ .../lib/set_alerts_to_untracked.ts | 260 +++++++++++------- .../bulk_untrack/bulk_untrack_alerts.test.ts | 2 + .../bulk_untrack/bulk_untrack_alerts.ts | 14 +- .../schemas/bulk_untrack_body_schema.ts | 7 +- .../plugins/alerting/server/routes/index.ts | 6 +- .../bulk_untrack_alerts_route.test.ts | 62 +++++ ..._route.ts => bulk_untrack_alerts_route.ts} | 9 +- .../routes/rule/apis/bulk_untrack/index.ts | 2 +- .../apis/bulk_untrack/transforms/index.ts | 5 +- .../latest.ts | 2 +- .../v1.ts | 7 +- ...bulk_untrack_alerts_by_query_route.test.ts | 72 +++++ .../bulk_untrack_alerts_by_query_route.ts | 48 ++++ .../rule/apis/bulk_untrack_by_query/index.ts | 8 + .../bulk_untrack_by_query/transforms/index.ts | 10 + .../latest.ts | 8 + .../v1.ts | 16 ++ .../hooks/use_bulk_actions.test.tsx | 19 +- .../alerts_table/hooks/use_bulk_actions.ts | 23 +- .../use_bulk_untrack_alerts_by_query.tsx | 62 +++++ .../tests/alerting/bulk_untrack_by_query.ts | 175 ++++++++++++ .../group1/tests/alerting/index.ts | 1 + 29 files changed, 910 insertions(+), 132 deletions(-) create mode 100644 x-pack/plugins/alerting/common/routes/rule/apis/bulk_untrack_by_query/index.ts create mode 100644 x-pack/plugins/alerting/common/routes/rule/apis/bulk_untrack_by_query/schemas/latest.ts create mode 100644 x-pack/plugins/alerting/common/routes/rule/apis/bulk_untrack_by_query/schemas/v1.ts create mode 100644 x-pack/plugins/alerting/common/routes/rule/apis/bulk_untrack_by_query/types/latest.ts create mode 100644 x-pack/plugins/alerting/common/routes/rule/apis/bulk_untrack_by_query/types/v1.ts create mode 100644 x-pack/plugins/alerting/server/routes/rule/apis/bulk_untrack/bulk_untrack_alerts_route.test.ts rename x-pack/plugins/alerting/server/routes/rule/apis/bulk_untrack/{bulk_untrack_alert_route.ts => bulk_untrack_alerts_route.ts} (84%) rename x-pack/plugins/alerting/server/routes/rule/apis/bulk_untrack/transforms/{transform_request_body_to_application => transform_bulk_untrack_alerts_body}/latest.ts (81%) rename x-pack/plugins/alerting/server/routes/rule/apis/bulk_untrack/transforms/{transform_request_body_to_application => transform_bulk_untrack_alerts_body}/v1.ts (56%) create mode 100644 x-pack/plugins/alerting/server/routes/rule/apis/bulk_untrack_by_query/bulk_untrack_alerts_by_query_route.test.ts create mode 100644 x-pack/plugins/alerting/server/routes/rule/apis/bulk_untrack_by_query/bulk_untrack_alerts_by_query_route.ts create mode 100644 x-pack/plugins/alerting/server/routes/rule/apis/bulk_untrack_by_query/index.ts create mode 100644 x-pack/plugins/alerting/server/routes/rule/apis/bulk_untrack_by_query/transforms/index.ts create mode 100644 x-pack/plugins/alerting/server/routes/rule/apis/bulk_untrack_by_query/transforms/transform_bulk_untrack_alerts_by_query_body/latest.ts create mode 100644 x-pack/plugins/alerting/server/routes/rule/apis/bulk_untrack_by_query/transforms/transform_bulk_untrack_alerts_by_query_body/v1.ts create mode 100644 x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/hooks/use_bulk_untrack_alerts_by_query.tsx create mode 100644 x-pack/test/alerting_api_integration/security_and_spaces/group1/tests/alerting/bulk_untrack_by_query.ts diff --git a/x-pack/plugins/alerting/common/routes/rule/apis/bulk_untrack_by_query/index.ts b/x-pack/plugins/alerting/common/routes/rule/apis/bulk_untrack_by_query/index.ts new file mode 100644 index 0000000000000..2ee7980b3dab5 --- /dev/null +++ b/x-pack/plugins/alerting/common/routes/rule/apis/bulk_untrack_by_query/index.ts @@ -0,0 +1,12 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export { bulkUntrackByQueryBodySchema } from './schemas/latest'; +export { bulkUntrackByQueryBodySchema as bulkUntrackByQueryBodySchemaV1 } from './schemas/v1'; + +export type { BulkUntrackByQueryRequestBody } from './types/latest'; +export type { BulkUntrackByQueryRequestBody as BulkUntrackByQueryRequestBodyV1 } from './types/v1'; diff --git a/x-pack/plugins/alerting/common/routes/rule/apis/bulk_untrack_by_query/schemas/latest.ts b/x-pack/plugins/alerting/common/routes/rule/apis/bulk_untrack_by_query/schemas/latest.ts new file mode 100644 index 0000000000000..614c13b141edb --- /dev/null +++ b/x-pack/plugins/alerting/common/routes/rule/apis/bulk_untrack_by_query/schemas/latest.ts @@ -0,0 +1,7 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +export { bulkUntrackByQueryBodySchema } from './v1'; diff --git a/x-pack/plugins/alerting/common/routes/rule/apis/bulk_untrack_by_query/schemas/v1.ts b/x-pack/plugins/alerting/common/routes/rule/apis/bulk_untrack_by_query/schemas/v1.ts new file mode 100644 index 0000000000000..62cc67360b162 --- /dev/null +++ b/x-pack/plugins/alerting/common/routes/rule/apis/bulk_untrack_by_query/schemas/v1.ts @@ -0,0 +1,13 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { schema } from '@kbn/config-schema'; + +export const bulkUntrackByQueryBodySchema = schema.object({ + query: schema.arrayOf(schema.any()), + feature_ids: schema.arrayOf(schema.string()), +}); diff --git a/x-pack/plugins/alerting/common/routes/rule/apis/bulk_untrack_by_query/types/latest.ts b/x-pack/plugins/alerting/common/routes/rule/apis/bulk_untrack_by_query/types/latest.ts new file mode 100644 index 0000000000000..341ff0c24955e --- /dev/null +++ b/x-pack/plugins/alerting/common/routes/rule/apis/bulk_untrack_by_query/types/latest.ts @@ -0,0 +1,8 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export type { BulkUntrackByQueryRequestBody } from './v1'; diff --git a/x-pack/plugins/alerting/common/routes/rule/apis/bulk_untrack_by_query/types/v1.ts b/x-pack/plugins/alerting/common/routes/rule/apis/bulk_untrack_by_query/types/v1.ts new file mode 100644 index 0000000000000..8e6e7b329c112 --- /dev/null +++ b/x-pack/plugins/alerting/common/routes/rule/apis/bulk_untrack_by_query/types/v1.ts @@ -0,0 +1,10 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import type { TypeOf } from '@kbn/config-schema'; +import { bulkUntrackByQueryBodySchemaV1 } from '..'; + +export type BulkUntrackByQueryRequestBody = TypeOf; diff --git a/x-pack/plugins/alerting/server/alerts_service/alerts_service.ts b/x-pack/plugins/alerting/server/alerts_service/alerts_service.ts index 45d48345d5ce9..10161b4e09635 100644 --- a/x-pack/plugins/alerting/server/alerts_service/alerts_service.ts +++ b/x-pack/plugins/alerting/server/alerts_service/alerts_service.ts @@ -45,7 +45,7 @@ import { import type { LegacyAlertsClientParams, AlertRuleData } from '../alerts_client'; import { AlertsClient } from '../alerts_client'; import { IAlertsClient } from '../alerts_client/types'; -import { setAlertsToUntracked, SetAlertsToUntrackedOpts } from './lib/set_alerts_to_untracked'; +import { setAlertsToUntracked, SetAlertsToUntrackedParams } from './lib/set_alerts_to_untracked'; export const TOTAL_FIELDS_LIMIT = 2500; const LEGACY_ALERT_CONTEXT = 'legacy-alert'; @@ -466,7 +466,7 @@ export class AlertsService implements IAlertsService { } } - public async setAlertsToUntracked(opts: SetAlertsToUntrackedOpts) { + public async setAlertsToUntracked(opts: SetAlertsToUntrackedParams) { return setAlertsToUntracked({ logger: this.options.logger, esClient: await this.options.elasticsearchClientPromise, diff --git a/x-pack/plugins/alerting/server/alerts_service/lib/set_alerts_to_untracked.test.ts b/x-pack/plugins/alerting/server/alerts_service/lib/set_alerts_to_untracked.test.ts index f69ee02464d4d..691fc8548c098 100644 --- a/x-pack/plugins/alerting/server/alerts_service/lib/set_alerts_to_untracked.test.ts +++ b/x-pack/plugins/alerting/server/alerts_service/lib/set_alerts_to_untracked.test.ts @@ -9,11 +9,18 @@ import { elasticsearchServiceMock, loggingSystemMock, } from '@kbn/core/server/mocks'; +import { ALERT_RULE_UUID, ALERT_UUID } from '@kbn/rule-data-utils'; import { setAlertsToUntracked } from './set_alerts_to_untracked'; let clusterClient: ElasticsearchClientMock; let logger: ReturnType; +const getAuthorizedRuleTypesMock = jest.fn(); + +const getAlertIndicesAliasMock = jest.fn(); + +const ensureAuthorizedMock = jest.fn(); + describe('setAlertsToUntracked()', () => { beforeEach(() => { jest.useFakeTimers(); @@ -353,4 +360,167 @@ describe('setAlertsToUntracked()', () => { }) ).resolves; }); + + test('should untrack by query', async () => { + getAuthorizedRuleTypesMock.mockResolvedValue([ + { + id: 'test-rule-type', + }, + ]); + getAlertIndicesAliasMock.mockResolvedValue(['test-alert-index']); + + clusterClient.search.mockResponseOnce({ + took: 1, + timed_out: false, + _shards: { + total: 1, + successful: 1, + skipped: 0, + failed: 0, + }, + hits: { + hits: [], + }, + aggregations: { + ruleTypeIds: { + buckets: [ + { + key: 'some rule type', + consumers: { + buckets: [{ key: 'o11y' }], + }, + }, + ], + }, + }, + }); + + clusterClient.search.mockResponseOnce({ + took: 1, + timed_out: false, + _shards: { + total: 1, + successful: 1, + skipped: 0, + failed: 0, + }, + hits: { + hits: [ + { + _index: 'test-alert-index', + _id: 'test-alert-id-1', + _source: { + [ALERT_RULE_UUID]: 'test-alert-rule-id-1', + [ALERT_UUID]: 'test-alert-id-1', + }, + }, + { + _index: 'test-alert-index', + _id: 'test-alert-id-2', + _source: { + [ALERT_RULE_UUID]: 'test-alert-rule-id-2', + [ALERT_UUID]: 'test-alert-id-2', + }, + }, + ], + }, + }); + + const result = await setAlertsToUntracked({ + isUsingQuery: true, + query: [ + { + bool: { + must: { + term: { + 'kibana.alert.rule.name': 'test', + }, + }, + }, + }, + ], + featureIds: ['o11y'], + spaceId: 'default', + getAuthorizedRuleTypes: getAuthorizedRuleTypesMock, + getAlertIndicesAlias: getAlertIndicesAliasMock, + ensureAuthorized: ensureAuthorizedMock, + logger, + esClient: clusterClient, + }); + + expect(getAlertIndicesAliasMock).lastCalledWith(['test-rule-type'], 'default'); + + expect(clusterClient.updateByQuery).toHaveBeenCalledWith( + expect.objectContaining({ + body: expect.objectContaining({ + query: { + bool: { + must: [ + { + term: { + 'kibana.alert.status': { + value: 'active', // This has to be active + }, + }, + }, + ], + filter: [ + { + bool: { + must: { + term: { + 'kibana.alert.rule.name': 'test', + }, + }, + }, + }, + ], + }, + }, + }), + }) + ); + + expect(clusterClient.search).toHaveBeenCalledWith( + expect.objectContaining({ + body: expect.objectContaining({ + query: { + bool: { + must: [ + { + term: { + 'kibana.alert.status': { + value: 'untracked', // This has to be untracked + }, + }, + }, + ], + filter: [ + { + bool: { + must: { + term: { + 'kibana.alert.rule.name': 'test', + }, + }, + }, + }, + ], + }, + }, + }), + }) + ); + + expect(result).toEqual([ + { + 'kibana.alert.rule.uuid': 'test-alert-rule-id-1', + 'kibana.alert.uuid': 'test-alert-id-1', + }, + { + 'kibana.alert.rule.uuid': 'test-alert-rule-id-2', + 'kibana.alert.uuid': 'test-alert-id-2', + }, + ]); + }); }); diff --git a/x-pack/plugins/alerting/server/alerts_service/lib/set_alerts_to_untracked.ts b/x-pack/plugins/alerting/server/alerts_service/lib/set_alerts_to_untracked.ts index d0e02fb8b1783..c28a760853ba6 100644 --- a/x-pack/plugins/alerting/server/alerts_service/lib/set_alerts_to_untracked.ts +++ b/x-pack/plugins/alerting/server/alerts_service/lib/set_alerts_to_untracked.ts @@ -20,15 +20,32 @@ import { ALERT_UUID, AlertStatus, } from '@kbn/rule-data-utils'; +import type { QueryDslQueryContainer } from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; +import { AlertingAuthorizationEntity } from '../../authorization/alerting_authorization'; +import type { RulesClientContext } from '../../rules_client'; -export interface SetAlertsToUntrackedOpts { - indices: string[]; +type EnsureAuthorized = (opts: { ruleTypeId: string; consumer: string }) => Promise; + +export interface SetAlertsToUntrackedParams { + indices?: string[]; ruleIds?: string[]; alertUuids?: string[]; - ensureAuthorized?: (opts: { ruleTypeId: string; consumer: string }) => Promise; + query?: QueryDslQueryContainer[]; + spaceId?: RulesClientContext['spaceId']; + featureIds?: string[]; + isUsingQuery?: boolean; + getAuthorizedRuleTypes?: RulesClientContext['authorization']['getAuthorizedRuleTypes']; + getAlertIndicesAlias?: RulesClientContext['getAlertIndicesAlias']; + ensureAuthorized?: EnsureAuthorized; +} + +interface SetAlertsToUntrackedParamsWithDep extends SetAlertsToUntrackedParams { + logger: Logger; + esClient: ElasticsearchClient; } type UntrackedAlertsResult = Array<{ [ALERT_RULE_UUID]: string; [ALERT_UUID]: string }>; + interface ConsumersAndRuleTypesAggregation { ruleTypeIds: { buckets: Array<{ @@ -40,30 +57,10 @@ interface ConsumersAndRuleTypesAggregation { }; } -const getQuery = ({ - ruleIds = [], - alertUuids = [], - alertStatus, -}: { - ruleIds?: string[]; - alertUuids?: string[]; - alertStatus: AlertStatus; -}) => { - const shouldMatchRules: Array<{ term: Record }> = ruleIds.map( - (ruleId) => ({ - term: { - [ALERT_RULE_UUID]: { value: ruleId }, - }, - }) - ); - const shouldMatchAlerts: Array<{ term: Record }> = alertUuids.map( - (alertId) => ({ - term: { - [ALERT_UUID]: { value: alertId }, - }, - }) - ); - +const getUntrackQuery = ( + params: SetAlertsToUntrackedParamsWithDep, + alertStatus: AlertStatus +): QueryDslQueryContainer => { const statusTerms: Array<{ term: Record }> = [ { term: { @@ -72,72 +69,139 @@ const getQuery = ({ }, ]; - return [ - ...statusTerms, - { + if (params.isUsingQuery) { + const { query } = params; + return { bool: { - should: shouldMatchRules, + must: statusTerms, + ...(query ? { filter: query } : {}), }, - }, - { + }; + } else { + const { ruleIds = [], alertUuids = [] } = params; + const shouldMatchRules: Array<{ term: Record }> = ruleIds.map( + (ruleId) => ({ + term: { + [ALERT_RULE_UUID]: { value: ruleId }, + }, + }) + ); + const shouldMatchAlerts: Array<{ term: Record }> = alertUuids.map( + (alertId) => ({ + term: { + [ALERT_UUID]: { value: alertId }, + }, + }) + ); + + return { bool: { - should: shouldMatchAlerts, - // If this is empty, ES will default to minimum_should_match: 0 + must: [ + ...statusTerms, + { + bool: { + should: shouldMatchRules, + }, + }, + { + bool: { + should: shouldMatchAlerts, + // If this is empty, ES will default to minimum_should_match: 0 + }, + }, + ], }, - }, - ]; + }; + } }; -export async function setAlertsToUntracked({ - logger, - esClient, - indices, - ruleIds = [], - alertUuids = [], // OPTIONAL - If no alertUuids are passed, untrack ALL ids by default, - ensureAuthorized, -}: { - logger: Logger; - esClient: ElasticsearchClient; -} & SetAlertsToUntrackedOpts): Promise { - if (isEmpty(ruleIds) && isEmpty(alertUuids)) - throw new Error('Must provide either ruleIds or alertUuids'); +const ensureAuthorizedToUntrack = async (params: SetAlertsToUntrackedParamsWithDep) => { + const { esClient, indices, ensureAuthorized } = params; - if (ensureAuthorized) { - // Fetch all rule type IDs and rule consumers, then run the provided ensureAuthorized check for each of them - const response = await esClient.search({ - index: indices, - allow_no_indices: true, - body: { - size: 0, - query: { - bool: { - must: getQuery({ - ruleIds, - alertUuids, - alertStatus: ALERT_STATUS_ACTIVE, - }), - }, - }, - aggs: { - ruleTypeIds: { - terms: { field: ALERT_RULE_TYPE_ID }, - aggs: { consumers: { terms: { field: ALERT_RULE_CONSUMER } } }, - }, + if (!ensureAuthorized) { + return; + } + // Fetch all rule type IDs and rule consumers, then run the provided ensureAuthorized check for each of them + const response = await esClient.search({ + index: indices, + allow_no_indices: true, + body: { + size: 0, + query: getUntrackQuery(params, ALERT_STATUS_ACTIVE), + aggs: { + ruleTypeIds: { + terms: { field: ALERT_RULE_TYPE_ID }, + aggs: { consumers: { terms: { field: ALERT_RULE_CONSUMER } } }, }, }, - }); - const ruleTypeIdBuckets = response.aggregations?.ruleTypeIds.buckets; - if (!ruleTypeIdBuckets) throw new Error('Unable to fetch ruleTypeIds for authorization'); - for (const { - key: ruleTypeId, - consumers: { buckets: consumerBuckets }, - } of ruleTypeIdBuckets) { - const consumers = consumerBuckets.map((b) => b.key); - for (const consumer of consumers) { - if (consumer === 'siem') throw new Error('Untracking Security alerts is not permitted'); - await ensureAuthorized({ ruleTypeId, consumer }); + }, + }); + const ruleTypeIdBuckets = response.aggregations?.ruleTypeIds.buckets; + if (!ruleTypeIdBuckets) { + throw new Error('Unable to fetch ruleTypeIds for authorization'); + } + for (const { + key: ruleTypeId, + consumers: { buckets: consumerBuckets }, + } of ruleTypeIdBuckets) { + const consumers = consumerBuckets.map((b) => b.key); + for (const consumer of consumers) { + if (consumer === 'siem') { + throw new Error('Untracking Security alerts is not permitted'); } + await ensureAuthorized({ ruleTypeId, consumer }); + } + } +}; + +const getAuthorizedAlertsIndices = async ({ + featureIds, + getAuthorizedRuleTypes, + getAlertIndicesAlias, + spaceId, + logger, +}: SetAlertsToUntrackedParamsWithDep) => { + try { + const authorizedRuleTypes = + (await getAuthorizedRuleTypes?.(AlertingAuthorizationEntity.Alert, new Set(featureIds))) || + []; + const indices = getAlertIndicesAlias?.( + authorizedRuleTypes.map((art: { id: string }) => art.id), + spaceId + ); + return indices; + } catch (error) { + const errMessage = `Failed to get authorized rule types to untrack alerts by query: ${error}`; + logger.error(errMessage); + throw new Error(errMessage); + } +}; + +export async function setAlertsToUntracked( + params: SetAlertsToUntrackedParamsWithDep +): Promise { + const { + logger, + esClient, + ruleIds = [], + alertUuids = [], // OPTIONAL - If no alertUuids are passed, untrack ALL ids by default, + ensureAuthorized, + isUsingQuery, + } = params; + + let indices: string[]; + + if (isUsingQuery) { + indices = (await getAuthorizedAlertsIndices(params)) || []; + } else { + if (isEmpty(ruleIds) && isEmpty(alertUuids)) { + throw new Error('Must provide either ruleIds or alertUuids'); } + indices = params.indices || []; + } + + if (ensureAuthorized) { + await ensureAuthorizedToUntrack(params); } try { @@ -154,22 +218,20 @@ export async function setAlertsToUntracked({ source: getUntrackUpdatePainlessScript(new Date()), lang: 'painless', }, - query: { - bool: { - must: getQuery({ - ruleIds, - alertUuids, - alertStatus: ALERT_STATUS_ACTIVE, - }), - }, - }, + query: getUntrackQuery(params, ALERT_STATUS_ACTIVE), }, refresh: true, }); - if (total === 0 && response.total === 0) + + if (total === 0 && response.total === 0) { throw new Error('No active alerts matched the query'); - if (response.total) total = response.total; - if (response.total === response.updated) break; + } + if (response.total) { + total = response.total; + } + if (response.total === response.updated) { + break; + } logger.warn( `Attempt ${retryCount + 1}: Failed to untrack ${ (response.total ?? 0) - (response.updated ?? 0) @@ -186,15 +248,7 @@ export async function setAlertsToUntracked({ body: { _source: [ALERT_RULE_UUID, ALERT_UUID], size: total, - query: { - bool: { - must: getQuery({ - ruleIds, - alertUuids, - alertStatus: ALERT_STATUS_UNTRACKED, - }), - }, - }, + query: getUntrackQuery(params, ALERT_STATUS_UNTRACKED), }, }); return searchResponse.hits.hits.map((hit) => hit._source) as UntrackedAlertsResult; diff --git a/x-pack/plugins/alerting/server/application/rule/methods/bulk_untrack/bulk_untrack_alerts.test.ts b/x-pack/plugins/alerting/server/application/rule/methods/bulk_untrack/bulk_untrack_alerts.test.ts index f8e8b1288634a..78485744e8103 100644 --- a/x-pack/plugins/alerting/server/application/rule/methods/bulk_untrack/bulk_untrack_alerts.test.ts +++ b/x-pack/plugins/alerting/server/application/rule/methods/bulk_untrack/bulk_untrack_alerts.test.ts @@ -79,6 +79,7 @@ describe('bulkUntrackAlerts()', () => { ]); await rulesClient.bulkUntrackAlerts({ + isUsingQuery: true, indices: [ 'she had them apple bottom jeans (jeans)', 'boots with the fur (with the fur)', @@ -160,6 +161,7 @@ describe('bulkUntrackAlerts()', () => { ]); await rulesClient.bulkUntrackAlerts({ + isUsingQuery: true, indices: ["honestly who cares we're not even testing the index right now"], alertUuids: [mockAlertUuid], }); diff --git a/x-pack/plugins/alerting/server/application/rule/methods/bulk_untrack/bulk_untrack_alerts.ts b/x-pack/plugins/alerting/server/application/rule/methods/bulk_untrack/bulk_untrack_alerts.ts index e3c75f2889198..d3a1badd0ab33 100644 --- a/x-pack/plugins/alerting/server/application/rule/methods/bulk_untrack/bulk_untrack_alerts.ts +++ b/x-pack/plugins/alerting/server/application/rule/methods/bulk_untrack/bulk_untrack_alerts.ts @@ -35,15 +35,17 @@ export async function bulkUntrackAlerts( ); } -async function bulkUntrackAlertsWithOCC( - context: RulesClientContext, - { indices, alertUuids }: BulkUntrackBody -) { +async function bulkUntrackAlertsWithOCC(context: RulesClientContext, params: BulkUntrackBody) { try { if (!context.alertsService) throw new Error('unable to access alertsService'); const result = await context.alertsService.setAlertsToUntracked({ - indices, - alertUuids, + ...params, + featureIds: params.featureIds || [], + spaceId: context.spaceId, + getAlertIndicesAlias: context.getAlertIndicesAlias, + getAuthorizedRuleTypes: context.authorization.getAuthorizedRuleTypes.bind( + context.authorization + ), ensureAuthorized: async ({ ruleTypeId, consumer, diff --git a/x-pack/plugins/alerting/server/application/rule/methods/bulk_untrack/schemas/bulk_untrack_body_schema.ts b/x-pack/plugins/alerting/server/application/rule/methods/bulk_untrack/schemas/bulk_untrack_body_schema.ts index 9c77a6e6b2b3a..f597d9f5b6fa4 100644 --- a/x-pack/plugins/alerting/server/application/rule/methods/bulk_untrack/schemas/bulk_untrack_body_schema.ts +++ b/x-pack/plugins/alerting/server/application/rule/methods/bulk_untrack/schemas/bulk_untrack_body_schema.ts @@ -7,6 +7,9 @@ import { schema } from '@kbn/config-schema'; export const bulkUntrackBodySchema = schema.object({ - indices: schema.arrayOf(schema.string()), - alertUuids: schema.arrayOf(schema.string()), + isUsingQuery: schema.boolean(), + indices: schema.maybe(schema.arrayOf(schema.string())), + alertUuids: schema.maybe(schema.arrayOf(schema.string())), + query: schema.maybe(schema.arrayOf(schema.any())), + featureIds: schema.maybe(schema.arrayOf(schema.string())), }); diff --git a/x-pack/plugins/alerting/server/routes/index.ts b/x-pack/plugins/alerting/server/routes/index.ts index d28854fb3ac6a..74d8179111d3d 100644 --- a/x-pack/plugins/alerting/server/routes/index.ts +++ b/x-pack/plugins/alerting/server/routes/index.ts @@ -48,7 +48,8 @@ import { getFlappingSettingsRoute } from './get_flapping_settings'; import { updateFlappingSettingsRoute } from './update_flapping_settings'; import { getRuleTagsRoute } from './rule/apis/tags/get_rule_tags'; import { getScheduleFrequencyRoute } from './rule/apis/get_schedule_frequency'; -import { bulkUntrackAlertRoute } from './rule/apis/bulk_untrack'; +import { bulkUntrackAlertsRoute } from './rule/apis/bulk_untrack'; +import { bulkUntrackAlertsByQueryRoute } from './rule/apis/bulk_untrack_by_query'; import { createMaintenanceWindowRoute } from './maintenance_window/apis/create/create_maintenance_window_route'; import { getMaintenanceWindowRoute } from './maintenance_window/apis/get/get_maintenance_window_route'; @@ -134,7 +135,8 @@ export function defineRoutes(opts: RouteOptions) { registerFieldsRoute(router, licenseState); bulkGetMaintenanceWindowRoute(router, licenseState); getScheduleFrequencyRoute(router, licenseState); - bulkUntrackAlertRoute(router, licenseState); + bulkUntrackAlertsRoute(router, licenseState); + bulkUntrackAlertsByQueryRoute(router, licenseState); getQueryDelaySettingsRoute(router, licenseState); updateQueryDelaySettingsRoute(router, licenseState); } diff --git a/x-pack/plugins/alerting/server/routes/rule/apis/bulk_untrack/bulk_untrack_alerts_route.test.ts b/x-pack/plugins/alerting/server/routes/rule/apis/bulk_untrack/bulk_untrack_alerts_route.test.ts new file mode 100644 index 0000000000000..ab328900dab9a --- /dev/null +++ b/x-pack/plugins/alerting/server/routes/rule/apis/bulk_untrack/bulk_untrack_alerts_route.test.ts @@ -0,0 +1,62 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { httpServiceMock } from '@kbn/core/server/mocks'; +import { bulkUntrackAlertsRoute } from './bulk_untrack_alerts_route'; +import { licenseStateMock } from '../../../../lib/license_state.mock'; +import { mockHandlerArguments } from '../../../_mock_handler_arguments'; +import { rulesClientMock } from '../../../../rules_client.mock'; + +const rulesClient = rulesClientMock.create(); + +jest.mock('../../../../lib/license_api_access', () => ({ + verifyApiAccess: jest.fn(), +})); +beforeEach(() => { + jest.resetAllMocks(); +}); + +describe('bulkUntrackAlertsRoute', () => { + it('should call bulkUntrack with the proper values', async () => { + const licenseState = licenseStateMock.create(); + const router = httpServiceMock.createRouter(); + + bulkUntrackAlertsRoute(router, licenseState); + + const [config, handler] = router.post.mock.calls[0]; + + expect(config.path).toBe('/internal/alerting/alerts/_bulk_untrack'); + + rulesClient.bulkUntrackAlerts.mockResolvedValueOnce(); + + const requestBody = { + indices: ['test-index'], + alert_uuids: ['id1', 'id2'], + }; + + const [context, req, res] = mockHandlerArguments( + { rulesClient }, + { + body: requestBody, + }, + ['ok'] + ); + + expect(await handler(context, req, res)).toEqual(undefined); + + expect(rulesClient.bulkUntrackAlerts).toHaveBeenCalledTimes(1); + expect(rulesClient.bulkUntrackAlerts.mock.calls[0]).toEqual([ + { + indices: requestBody.indices, + alertUuids: requestBody.alert_uuids, + isUsingQuery: false, + }, + ]); + + expect(res.noContent).toHaveBeenCalled(); + }); +}); diff --git a/x-pack/plugins/alerting/server/routes/rule/apis/bulk_untrack/bulk_untrack_alert_route.ts b/x-pack/plugins/alerting/server/routes/rule/apis/bulk_untrack/bulk_untrack_alerts_route.ts similarity index 84% rename from x-pack/plugins/alerting/server/routes/rule/apis/bulk_untrack/bulk_untrack_alert_route.ts rename to x-pack/plugins/alerting/server/routes/rule/apis/bulk_untrack/bulk_untrack_alerts_route.ts index 791fdcca533cc..3539db62d0649 100644 --- a/x-pack/plugins/alerting/server/routes/rule/apis/bulk_untrack/bulk_untrack_alert_route.ts +++ b/x-pack/plugins/alerting/server/routes/rule/apis/bulk_untrack/bulk_untrack_alerts_route.ts @@ -9,12 +9,12 @@ import { BulkUntrackRequestBodyV1, bulkUntrackBodySchemaV1, } from '../../../../../common/routes/rule/apis/bulk_untrack'; -import { transformRequestBodyToApplicationV1 } from './transforms'; +import { transformBulkUntrackAlertsBodyV1 } from './transforms'; import { ILicenseState, RuleTypeDisabledError } from '../../../../lib'; import { verifyAccessAndContext } from '../../../lib'; import { AlertingRequestHandlerContext, INTERNAL_BASE_ALERTING_API_PATH } from '../../../../types'; -export const bulkUntrackAlertRoute = ( +export const bulkUntrackAlertsRoute = ( router: IRouter, licenseState: ILicenseState ) => { @@ -30,7 +30,10 @@ export const bulkUntrackAlertRoute = ( const rulesClient = (await context.alerting).getRulesClient(); const body: BulkUntrackRequestBodyV1 = req.body; try { - await rulesClient.bulkUntrackAlerts(transformRequestBodyToApplicationV1(body)); + await rulesClient.bulkUntrackAlerts({ + ...transformBulkUntrackAlertsBodyV1(body), + isUsingQuery: false, + }); return res.noContent(); } catch (e) { if (e instanceof RuleTypeDisabledError) { diff --git a/x-pack/plugins/alerting/server/routes/rule/apis/bulk_untrack/index.ts b/x-pack/plugins/alerting/server/routes/rule/apis/bulk_untrack/index.ts index b453f47ecbb6e..3f50182814bb9 100644 --- a/x-pack/plugins/alerting/server/routes/rule/apis/bulk_untrack/index.ts +++ b/x-pack/plugins/alerting/server/routes/rule/apis/bulk_untrack/index.ts @@ -5,4 +5,4 @@ * 2.0. */ -export { bulkUntrackAlertRoute } from './bulk_untrack_alert_route'; +export { bulkUntrackAlertsRoute } from './bulk_untrack_alerts_route'; diff --git a/x-pack/plugins/alerting/server/routes/rule/apis/bulk_untrack/transforms/index.ts b/x-pack/plugins/alerting/server/routes/rule/apis/bulk_untrack/transforms/index.ts index aa4eae3d633cf..0f11ca4a535b5 100644 --- a/x-pack/plugins/alerting/server/routes/rule/apis/bulk_untrack/transforms/index.ts +++ b/x-pack/plugins/alerting/server/routes/rule/apis/bulk_untrack/transforms/index.ts @@ -4,5 +4,6 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -export { transformRequestBodyToApplication } from './transform_request_body_to_application/latest'; -export { transformRequestBodyToApplication as transformRequestBodyToApplicationV1 } from './transform_request_body_to_application/v1'; + +export { transformBulkUntrackAlertsBody } from './transform_bulk_untrack_alerts_body/latest'; +export { transformBulkUntrackAlertsBody as transformBulkUntrackAlertsBodyV1 } from './transform_bulk_untrack_alerts_body/v1'; diff --git a/x-pack/plugins/alerting/server/routes/rule/apis/bulk_untrack/transforms/transform_request_body_to_application/latest.ts b/x-pack/plugins/alerting/server/routes/rule/apis/bulk_untrack/transforms/transform_bulk_untrack_alerts_body/latest.ts similarity index 81% rename from x-pack/plugins/alerting/server/routes/rule/apis/bulk_untrack/transforms/transform_request_body_to_application/latest.ts rename to x-pack/plugins/alerting/server/routes/rule/apis/bulk_untrack/transforms/transform_bulk_untrack_alerts_body/latest.ts index 3dab7ef9587fb..6436c98ae0f1f 100644 --- a/x-pack/plugins/alerting/server/routes/rule/apis/bulk_untrack/transforms/transform_request_body_to_application/latest.ts +++ b/x-pack/plugins/alerting/server/routes/rule/apis/bulk_untrack/transforms/transform_bulk_untrack_alerts_body/latest.ts @@ -5,4 +5,4 @@ * 2.0. */ -export { transformRequestBodyToApplication } from './v1'; +export { transformBulkUntrackAlertsBody } from './v1'; diff --git a/x-pack/plugins/alerting/server/routes/rule/apis/bulk_untrack/transforms/transform_request_body_to_application/v1.ts b/x-pack/plugins/alerting/server/routes/rule/apis/bulk_untrack/transforms/transform_bulk_untrack_alerts_body/v1.ts similarity index 56% rename from x-pack/plugins/alerting/server/routes/rule/apis/bulk_untrack/transforms/transform_request_body_to_application/v1.ts rename to x-pack/plugins/alerting/server/routes/rule/apis/bulk_untrack/transforms/transform_bulk_untrack_alerts_body/v1.ts index 0a0750bf45b1b..2e745967ea665 100644 --- a/x-pack/plugins/alerting/server/routes/rule/apis/bulk_untrack/transforms/transform_request_body_to_application/v1.ts +++ b/x-pack/plugins/alerting/server/routes/rule/apis/bulk_untrack/transforms/transform_bulk_untrack_alerts_body/v1.ts @@ -5,13 +5,12 @@ * 2.0. */ -import { RewriteRequestCase } from '../../../../../lib'; -import { BulkUntrackBody } from '../../../../../../application/rule/methods/bulk_untrack/types'; +import type { BulkUntrackRequestBodyV1 } from '../../../../../../../common/routes/rule/apis/bulk_untrack'; -export const transformRequestBodyToApplication: RewriteRequestCase = ({ +export const transformBulkUntrackAlertsBody = ({ indices, alert_uuids: alertUuids, -}) => ({ +}: BulkUntrackRequestBodyV1) => ({ indices, alertUuids, }); diff --git a/x-pack/plugins/alerting/server/routes/rule/apis/bulk_untrack_by_query/bulk_untrack_alerts_by_query_route.test.ts b/x-pack/plugins/alerting/server/routes/rule/apis/bulk_untrack_by_query/bulk_untrack_alerts_by_query_route.test.ts new file mode 100644 index 0000000000000..3486005d3b588 --- /dev/null +++ b/x-pack/plugins/alerting/server/routes/rule/apis/bulk_untrack_by_query/bulk_untrack_alerts_by_query_route.test.ts @@ -0,0 +1,72 @@ +/* + * 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 { httpServiceMock } from '@kbn/core/server/mocks'; +import { bulkUntrackAlertsByQueryRoute } from './bulk_untrack_alerts_by_query_route'; +import { licenseStateMock } from '../../../../lib/license_state.mock'; +import { mockHandlerArguments } from '../../../_mock_handler_arguments'; +import { rulesClientMock } from '../../../../rules_client.mock'; + +const rulesClient = rulesClientMock.create(); + +jest.mock('../../../../lib/license_api_access', () => ({ + verifyApiAccess: jest.fn(), +})); +beforeEach(() => { + jest.resetAllMocks(); +}); + +describe('bulkUntrackAlertsByQueryRoute', () => { + it('should call bulkUntrack with the proper values', async () => { + const licenseState = licenseStateMock.create(); + const router = httpServiceMock.createRouter(); + + bulkUntrackAlertsByQueryRoute(router, licenseState); + + const [config, handler] = router.post.mock.calls[0]; + + expect(config.path).toBe('/internal/alerting/alerts/_bulk_untrack_by_query'); + + rulesClient.bulkUntrackAlerts.mockResolvedValueOnce(); + + const requestBody = { + query: [ + { + bool: { + must: { + term: { + 'kibana.alert.rule.name': 'test', + }, + }, + }, + }, + ], + feature_ids: ['o11y'], + }; + + const [context, req, res] = mockHandlerArguments( + { rulesClient }, + { + body: requestBody, + }, + ['ok'] + ); + + expect(await handler(context, req, res)).toEqual(undefined); + + expect(rulesClient.bulkUntrackAlerts).toHaveBeenCalledTimes(1); + expect(rulesClient.bulkUntrackAlerts.mock.calls[0]).toEqual([ + { + query: requestBody.query, + featureIds: requestBody.feature_ids, + isUsingQuery: true, + }, + ]); + + expect(res.noContent).toHaveBeenCalled(); + }); +}); diff --git a/x-pack/plugins/alerting/server/routes/rule/apis/bulk_untrack_by_query/bulk_untrack_alerts_by_query_route.ts b/x-pack/plugins/alerting/server/routes/rule/apis/bulk_untrack_by_query/bulk_untrack_alerts_by_query_route.ts new file mode 100644 index 0000000000000..92b43570772c7 --- /dev/null +++ b/x-pack/plugins/alerting/server/routes/rule/apis/bulk_untrack_by_query/bulk_untrack_alerts_by_query_route.ts @@ -0,0 +1,48 @@ +/* + * 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 { IRouter } from '@kbn/core/server'; +import { + BulkUntrackByQueryRequestBodyV1, + bulkUntrackByQueryBodySchemaV1, +} from '../../../../../common/routes/rule/apis/bulk_untrack_by_query'; +import { ILicenseState, RuleTypeDisabledError } from '../../../../lib'; +import { verifyAccessAndContext } from '../../../lib'; +import { AlertingRequestHandlerContext, INTERNAL_BASE_ALERTING_API_PATH } from '../../../../types'; +import { transformBulkUntrackAlertsByQueryBodyV1 } from './transforms'; + +export const bulkUntrackAlertsByQueryRoute = ( + router: IRouter, + licenseState: ILicenseState +) => { + router.post( + { + path: `${INTERNAL_BASE_ALERTING_API_PATH}/alerts/_bulk_untrack_by_query`, + validate: { + body: bulkUntrackByQueryBodySchemaV1, + }, + }, + router.handleLegacyErrors( + verifyAccessAndContext(licenseState, async function (context, req, res) { + const rulesClient = (await context.alerting).getRulesClient(); + const body: BulkUntrackByQueryRequestBodyV1 = req.body; + try { + await rulesClient.bulkUntrackAlerts({ + ...transformBulkUntrackAlertsByQueryBodyV1(body), + isUsingQuery: true, + }); + return res.noContent(); + } catch (e) { + if (e instanceof RuleTypeDisabledError) { + return e.sendResponse(res); + } + throw e; + } + }) + ) + ); +}; diff --git a/x-pack/plugins/alerting/server/routes/rule/apis/bulk_untrack_by_query/index.ts b/x-pack/plugins/alerting/server/routes/rule/apis/bulk_untrack_by_query/index.ts new file mode 100644 index 0000000000000..b22cfb075457d --- /dev/null +++ b/x-pack/plugins/alerting/server/routes/rule/apis/bulk_untrack_by_query/index.ts @@ -0,0 +1,8 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export { bulkUntrackAlertsByQueryRoute } from './bulk_untrack_alerts_by_query_route'; diff --git a/x-pack/plugins/alerting/server/routes/rule/apis/bulk_untrack_by_query/transforms/index.ts b/x-pack/plugins/alerting/server/routes/rule/apis/bulk_untrack_by_query/transforms/index.ts new file mode 100644 index 0000000000000..71d3212f4a3bc --- /dev/null +++ b/x-pack/plugins/alerting/server/routes/rule/apis/bulk_untrack_by_query/transforms/index.ts @@ -0,0 +1,10 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export { transformBulkUntrackAlertsByQueryBody } from './transform_bulk_untrack_alerts_by_query_body/latest'; + +export { transformBulkUntrackAlertsByQueryBody as transformBulkUntrackAlertsByQueryBodyV1 } from './transform_bulk_untrack_alerts_by_query_body/v1'; diff --git a/x-pack/plugins/alerting/server/routes/rule/apis/bulk_untrack_by_query/transforms/transform_bulk_untrack_alerts_by_query_body/latest.ts b/x-pack/plugins/alerting/server/routes/rule/apis/bulk_untrack_by_query/transforms/transform_bulk_untrack_alerts_by_query_body/latest.ts new file mode 100644 index 0000000000000..73fdbd68378d6 --- /dev/null +++ b/x-pack/plugins/alerting/server/routes/rule/apis/bulk_untrack_by_query/transforms/transform_bulk_untrack_alerts_by_query_body/latest.ts @@ -0,0 +1,8 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export { transformBulkUntrackAlertsByQueryBody } from './v1'; diff --git a/x-pack/plugins/alerting/server/routes/rule/apis/bulk_untrack_by_query/transforms/transform_bulk_untrack_alerts_by_query_body/v1.ts b/x-pack/plugins/alerting/server/routes/rule/apis/bulk_untrack_by_query/transforms/transform_bulk_untrack_alerts_by_query_body/v1.ts new file mode 100644 index 0000000000000..87c58c48c4c6d --- /dev/null +++ b/x-pack/plugins/alerting/server/routes/rule/apis/bulk_untrack_by_query/transforms/transform_bulk_untrack_alerts_by_query_body/v1.ts @@ -0,0 +1,16 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { BulkUntrackByQueryRequestBodyV1 } from '../../../../../../../common/routes/rule/apis/bulk_untrack_by_query'; + +export const transformBulkUntrackAlertsByQueryBody = ({ + query, + feature_ids: featureIds, +}: BulkUntrackByQueryRequestBodyV1) => ({ + query, + featureIds, +}); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/hooks/use_bulk_actions.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/hooks/use_bulk_actions.test.tsx index 9fa320531a959..84b9180733e83 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/hooks/use_bulk_actions.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/hooks/use_bulk_actions.test.tsx @@ -308,7 +308,22 @@ describe('bulk action hooks', () => { }, })); const { result } = renderHook( - () => useBulkUntrackActions({ setIsBulkActionsLoading, refresh, clearSelection }), + () => + useBulkUntrackActions({ + setIsBulkActionsLoading, + refresh, + clearSelection, + isAllSelected: true, + query: { + bool: { + must: { + term: { + test: 'test', + }, + }, + }, + }, + }), { wrapper: appMockRender.AppWrapper, } @@ -361,7 +376,7 @@ describe('bulk action hooks', () => { }, Object { "data-test-subj": "mark-as-untracked", - "disableOnQuery": true, + "disableOnQuery": false, "disabledLabel": "Mark as untracked", "key": "mark-as-untracked", "label": "Mark as untracked", diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/hooks/use_bulk_actions.ts b/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/hooks/use_bulk_actions.ts index 9b692b3c1b16a..aec60b89e5e1e 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/hooks/use_bulk_actions.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/hooks/use_bulk_actions.ts @@ -32,6 +32,7 @@ import { } from './translations'; import { TimelineItem } from '../bulk_actions/components/toolbar'; import { useBulkUntrackAlerts } from './use_bulk_untrack_alerts'; +import { useBulkUntrackAlertsByQuery } from './use_bulk_untrack_alerts_by_query'; interface BulkActionsProps { query: Pick; @@ -54,8 +55,10 @@ export interface UseBulkActions { type UseBulkAddToCaseActionsProps = Pick & Pick; -type UseBulkUntrackActionsProps = Pick & - Pick; +type UseBulkUntrackActionsProps = Pick & + Pick & { + isAllSelected: boolean; + }; const filterAlertsAlreadyAttachedToCase = (alerts: TimelineItem[], caseId: string) => alerts.filter( @@ -181,6 +184,9 @@ export const useBulkUntrackActions = ({ setIsBulkActionsLoading, refresh, clearSelection, + query, + featureIds = [], + isAllSelected, }: UseBulkUntrackActionsProps) => { const onSuccess = useCallback(() => { refresh(); @@ -189,6 +195,8 @@ export const useBulkUntrackActions = ({ const { application } = useKibana().services; const { mutateAsync: untrackAlerts } = useBulkUntrackAlerts(); + const { mutateAsync: untrackAlertsByQuery } = useBulkUntrackAlertsByQuery(); + // Check if at least one Observability feature is enabled if (!application?.capabilities) return []; const hasApmPermission = application.capabilities.apm?.['alerting:show']; @@ -212,7 +220,7 @@ export const useBulkUntrackActions = ({ { label: MARK_AS_UNTRACKED, key: 'mark-as-untracked', - disableOnQuery: true, + disableOnQuery: false, disabledLabel: MARK_AS_UNTRACKED, 'data-test-subj': 'mark-as-untracked', onClick: async (alerts?: TimelineItem[]) => { @@ -221,7 +229,11 @@ export const useBulkUntrackActions = ({ const indices = alerts.map((alert) => alert._index ?? ''); try { setIsBulkActionsLoading(true); - await untrackAlerts({ indices, alertUuids }); + if (isAllSelected) { + await untrackAlertsByQuery({ query, featureIds }); + } else { + await untrackAlerts({ indices, alertUuids }); + } onSuccess(); } finally { setIsBulkActionsLoading(false); @@ -255,6 +267,9 @@ export function useBulkActions({ setIsBulkActionsLoading, refresh, clearSelection, + query, + featureIds, + isAllSelected: bulkActionsState.isAllSelected, }); const initialItems = [ diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/hooks/use_bulk_untrack_alerts_by_query.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/hooks/use_bulk_untrack_alerts_by_query.tsx new file mode 100644 index 0000000000000..3ef0efd7faad8 --- /dev/null +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/hooks/use_bulk_untrack_alerts_by_query.tsx @@ -0,0 +1,62 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { i18n } from '@kbn/i18n'; +import { useMutation } from '@tanstack/react-query'; +import { QueryDslQueryContainer } from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; +import { INTERNAL_BASE_ALERTING_API_PATH } from '@kbn/alerting-plugin/common'; +import { ValidFeatureId } from '@kbn/rule-data-utils'; +import { AlertsTableQueryContext } from '../contexts/alerts_table_context'; +import { useKibana } from '../../../../common'; + +export const useBulkUntrackAlertsByQuery = () => { + const { + http, + notifications: { toasts }, + } = useKibana().services; + + const untrackAlertsByQuery = useMutation< + string, + string, + { query: Pick; featureIds: ValidFeatureId[] } + >( + ['untrackAlerts'], + ({ query, featureIds }) => { + try { + const body = JSON.stringify({ + query: Array.isArray(query) ? query : [query], + feature_ids: featureIds, + }); + return http.post(`${INTERNAL_BASE_ALERTING_API_PATH}/alerts/_bulk_untrack_by_query`, { + body, + }); + } catch (e) { + throw new Error(`Unable to parse bulk untrack by query params: ${e}`); + } + }, + { + context: AlertsTableQueryContext, + onError: () => { + toasts.addDanger( + i18n.translate('xpack.triggersActionsUI.alertsTable.untrackByQuery.failedMessage', { + defaultMessage: 'Failed to untrack alerts by query', + }) + ); + }, + + onSuccess: () => { + toasts.addSuccess( + i18n.translate('xpack.triggersActionsUI.alertsTable.untrackByQuery.successMessage', { + defaultMessage: 'Untracked alerts', + }) + ); + }, + } + ); + + return untrackAlertsByQuery; +}; diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/group1/tests/alerting/bulk_untrack_by_query.ts b/x-pack/test/alerting_api_integration/security_and_spaces/group1/tests/alerting/bulk_untrack_by_query.ts new file mode 100644 index 0000000000000..a1c799cce9529 --- /dev/null +++ b/x-pack/test/alerting_api_integration/security_and_spaces/group1/tests/alerting/bulk_untrack_by_query.ts @@ -0,0 +1,175 @@ +/* + * 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 expect from '@kbn/expect'; +import { ES_TEST_INDEX_NAME } from '@kbn/alerting-api-integration-helpers'; +import { ALERT_STATUS, ALERT_RULE_NAME } from '@kbn/rule-data-utils'; +import { getUrlPrefix, ObjectRemover, getTestRuleData, getEventLog } from '../../../../common/lib'; +import { FtrProviderContext } from '../../../../common/ftr_provider_context'; +import { UserAtSpaceScenarios } from '../../../scenarios'; + +const alertAsDataIndex = '.internal.alerts-observability.test.alerts.alerts-default-000001'; + +// eslint-disable-next-line import/no-default-export +export default function bulkUntrackByQueryTests({ getService }: FtrProviderContext) { + const supertest = getService('supertest'); + const supertestWithoutAuth = getService('supertestWithoutAuth'); + const retry = getService('retry'); + const es = getService('es'); + + describe('bulk untrack by query', () => { + const objectRemover = new ObjectRemover(supertest); + + afterEach(async () => { + await es.deleteByQuery({ + index: alertAsDataIndex, + query: { + match_all: {}, + }, + conflicts: 'proceed', + }); + objectRemover.removeAll(); + }); + + for (const scenario of UserAtSpaceScenarios) { + describe(scenario.id, async () => { + it('should bulk mark alerts as untracked by query', async () => { + const { body: createdRule1 } = await supertest + .post(`${getUrlPrefix(scenario.space.id)}/api/alerting/rule`) + .set('kbn-xsrf', 'foo') + .send( + getTestRuleData({ + name: 'rule1', + rule_type_id: 'test.always-firing-alert-as-data', + schedule: { interval: '24h' }, + throttle: undefined, + notify_when: undefined, + params: { + index: ES_TEST_INDEX_NAME, + reference: 'test', + }, + }) + ) + .expect(200); + objectRemover.add(scenario.space.id, createdRule1.id, 'rule', 'alerting'); + + const { body: createdRule2 } = await supertest + .post(`${getUrlPrefix(scenario.space.id)}/api/alerting/rule`) + .set('kbn-xsrf', 'foo') + .send( + getTestRuleData({ + name: 'rule2', + rule_type_id: 'test.always-firing-alert-as-data', + schedule: { interval: '24h' }, + throttle: undefined, + notify_when: undefined, + params: { + index: ES_TEST_INDEX_NAME, + reference: 'test', + }, + }) + ) + .expect(200); + objectRemover.add(scenario.space.id, createdRule2.id, 'rule', 'alerting'); + + await retry.try(async () => { + return await getEventLog({ + getService, + spaceId: scenario.space.id, + type: 'alert', + id: createdRule1.id, + provider: 'alerting', + actions: new Map([['active-instance', { equal: 2 }]]), + }); + }); + + await retry.try(async () => { + return await getEventLog({ + getService, + spaceId: scenario.space.id, + type: 'alert', + id: createdRule2.id, + provider: 'alerting', + actions: new Map([['active-instance', { equal: 2 }]]), + }); + }); + + await retry.try(async () => { + const { + hits: { hits: activeAlerts }, + } = await es.search({ + index: alertAsDataIndex, + body: { query: { match_all: {} } }, + }); + + activeAlerts.forEach((activeAlert: any) => { + expect(activeAlert._source[ALERT_STATUS]).eql('active'); + }); + }); + + const response = await supertestWithoutAuth + .post( + `${getUrlPrefix(scenario.space.id)}/internal/alerting/alerts/_bulk_untrack_by_query` + ) + .set('kbn-xsrf', 'foo') + .auth(scenario.user.username, scenario.user.password) + .send({ + query: [ + { + bool: { + must: { + term: { + 'kibana.alert.rule.name': 'rule1', + }, + }, + }, + }, + ], + feature_ids: ['alertsFixture'], + }); + + switch (scenario.id) { + case 'no_kibana_privileges at space1': + case 'space_1_all at space1': + case 'space_1_all at space2': + case 'global_read at space1': + case 'space_1_all_alerts_none_actions at space1': + case 'space_1_all_with_restricted_fixture at space1': + expect(response.statusCode).to.eql(403); + break; + case 'superuser at space1': + expect(response.statusCode).to.eql(204); + await retry.try(async () => { + const untrackedAlert = []; + + const { + hits: { hits }, + } = await es.search({ + index: alertAsDataIndex, + body: { query: { match_all: {} } }, + }); + + hits.forEach((alert: any) => { + if ( + alert._source[ALERT_RULE_NAME] === 'rule1' && + alert._source[ALERT_STATUS] === 'untracked' + ) { + untrackedAlert.push(alert); + } + }); + + expect(untrackedAlert.length).eql(2); + }); + break; + default: + throw new Error(`Scenario untested: ${JSON.stringify(scenario)}`); + } + }); + }); + } + }); +} diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/group1/tests/alerting/index.ts b/x-pack/test/alerting_api_integration/security_and_spaces/group1/tests/alerting/index.ts index 8c0fb30872ac3..149990d5dcf89 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/group1/tests/alerting/index.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/group1/tests/alerting/index.ts @@ -33,6 +33,7 @@ export default function alertingTests({ loadTestFile, getService }: FtrProviderC loadTestFile(require.resolve('./rule_types')); loadTestFile(require.resolve('./retain_api_key')); loadTestFile(require.resolve('./bulk_untrack')); + loadTestFile(require.resolve('./bulk_untrack_by_query')); }); }); } From ef759f6d367b6accf7c3b8a26416fdc7afa43f8b Mon Sep 17 00:00:00 2001 From: jennypavlova Date: Mon, 12 Feb 2024 20:05:02 +0100 Subject: [PATCH 19/83] [Infra] Make k8s section collapsible (#176734) Closes #176733 ## Summary This PR makes the Kubernetes Overview section on the Asset details page collapsible ## Testing - Open the asset details page - Check the metrics section (the Kubernetes Overview section should be collapsible) https://github.com/elastic/kibana/assets/14139027/61fe91e4-1cd0-4dd8-b81f-402a04e47fcc --- .../asset_details/tabs/overview/metrics/metrics_section.tsx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/x-pack/plugins/infra/public/components/asset_details/tabs/overview/metrics/metrics_section.tsx b/x-pack/plugins/infra/public/components/asset_details/tabs/overview/metrics/metrics_section.tsx index dd6ac4b485a80..ec50deb11f2cf 100644 --- a/x-pack/plugins/infra/public/components/asset_details/tabs/overview/metrics/metrics_section.tsx +++ b/x-pack/plugins/infra/public/components/asset_details/tabs/overview/metrics/metrics_section.tsx @@ -58,7 +58,11 @@ export const MetricsSection = ({ assetName, metricsDataView, logsDataView, dateR filterFieldName={model.fields.name} />
-
+
Date: Mon, 12 Feb 2024 20:09:40 +0100 Subject: [PATCH 20/83] Fix PIT issue (#176699) --- .../security_solution/server/lib/telemetry/receiver.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/x-pack/plugins/security_solution/server/lib/telemetry/receiver.ts b/x-pack/plugins/security_solution/server/lib/telemetry/receiver.ts index b3dc89d613d67..6571822e57461 100644 --- a/x-pack/plugins/security_solution/server/lib/telemetry/receiver.ts +++ b/x-pack/plugins/security_solution/server/lib/telemetry/receiver.ts @@ -450,7 +450,6 @@ export class TelemetryReceiver implements ITelemetryReceiver { pit: { id: pitId }, search_after: searchAfter, size: telemetryConfiguration.telemetry_max_buffer_size, - expand_wildcards: ['open' as const, 'hidden' as const], }; let response = null; @@ -461,7 +460,7 @@ export class TelemetryReceiver implements ITelemetryReceiver { if (numOfHits > 0) { const lastHit = response?.hits.hits[numOfHits - 1]; - searchAfter = lastHit?.sort; + query.search_after = lastHit?.sort; } else { fetchMore = false; } @@ -807,6 +806,7 @@ export class TelemetryReceiver implements ITelemetryReceiver { await this.esClient.openPointInTime({ index: `${indexPattern}*`, keep_alive: keepAlive, + expand_wildcards: ['open' as const, 'hidden' as const], }) ).id; From d0dfc33679447a8353eb070245bf7cedb2482cd7 Mon Sep 17 00:00:00 2001 From: Alexi Doak <109488926+doakalexi@users.noreply.github.com> Date: Mon, 12 Feb 2024 11:16:09 -0800 Subject: [PATCH 21/83] [ResponseOps] Include alert creation delay in the event log (#176348) Resolves https://github.com/elastic/kibana/issues/175941 ## Summary Adds a new field, `number_of_delayed_alerts`, to the event log for a rule run. It's a count for all the delayed alerts, I opted to go this route instead of counting the number of times each alert was delayed. Pls let me know if you would like to go another way or would like to add any other metrics :) ### To verify -Go to Dev Tools - Create a rule with the alert delay ``` POST kbn:/api/alerting/rule { "params": { "searchType": "esQuery", "timeWindowSize": 5, "timeWindowUnit": "m", "threshold": [ -1 ], "thresholdComparator": ">", "size": 100, "esQuery": """{ "query":{ "match_all" : {} } }""", "aggType": "count", "groupBy": "all", "termSize": 5, "excludeHitsFromPreviousRun": false, "sourceFields": [], "index": [ ".kibana-event-log*" ], "timeField": "@timestamp" }, "consumer": "stackAlerts", "schedule": { "interval": "1m" }, "tags": [], "name": "test", "rule_type_id": ".es-query", "actions": [ { "group": "query matched", "id": "${ACTION_ID}", "params": { "level": "info", "message": """Elasticsearch query rule '{{rule.name}}' is active: - Value: {{context.value}} - Conditions Met: {{context.conditions}} over {{rule.params.timeWindowSize}}{{rule.params.timeWindowUnit}} - Timestamp: {{context.date}} - Link: {{context.link}}""" }, "frequency": { "notify_when": "onActionGroupChange", "throttle": null, "summary": false } } ], "alert_delay": { "active": 3 } } ``` - Let the rule run and then run the following to look at the event log - Verify that there is a new field `number_of_delayed_alerts` and that the counts what you would would expect for a rule running with the alert_delay --- .../alerts_client/legacy_alerts_client.ts | 3 + .../alerting/server/alerts_client/types.ts | 1 + .../alerting_event_logger.test.ts | 5 + .../alerting_event_logger.ts | 1 + .../lib/get_alerts_for_notification.test.ts | 140 ++++++++++-------- .../server/lib/get_alerts_for_notification.ts | 3 + .../server/lib/last_run_status.test.ts | 1 + .../server/lib/rule_execution_status.test.ts | 1 + .../server/lib/rule_run_metrics_store.mock.ts | 2 + .../server/lib/rule_run_metrics_store.test.ts | 7 + .../server/lib/rule_run_metrics_store.ts | 8 + .../server/task_runner/task_runner.test.ts | 16 +- .../server/task_runner/task_runner.ts | 1 + .../task_runner_alerts_client.test.ts | 3 +- .../task_runner/task_runner_cancel.test.ts | 3 +- .../plugins/event_log/generated/mappings.json | 3 + x-pack/plugins/event_log/generated/schemas.ts | 1 + x-pack/plugins/event_log/scripts/mappings.js | 3 + .../tests/alerting/group1/event_log.ts | 6 + .../alerts_as_data_alert_delay.ts | 11 ++ 20 files changed, 152 insertions(+), 67 deletions(-) diff --git a/x-pack/plugins/alerting/server/alerts_client/legacy_alerts_client.ts b/x-pack/plugins/alerting/server/alerts_client/legacy_alerts_client.ts index a5cfc642a19ee..8c9bf91d17ed8 100644 --- a/x-pack/plugins/alerting/server/alerts_client/legacy_alerts_client.ts +++ b/x-pack/plugins/alerting/server/alerts_client/legacy_alerts_client.ts @@ -141,6 +141,7 @@ export class LegacyAlertsClient< flappingSettings, maintenanceWindowIds, alertDelay, + ruleRunMetricsStore, }: ProcessAlertsOpts) { const { newAlerts: processedAlertsNew, @@ -176,6 +177,7 @@ export class LegacyAlertsClient< processedAlertsRecoveredCurrent, this.startedAtString ); + ruleRunMetricsStore.setNumberOfDelayedAlerts(alerts.delayedAlertsCount); alerts.currentRecoveredAlerts = merge(alerts.currentRecoveredAlerts, earlyRecoveredAlerts); this.processedAlerts.new = alerts.newAlerts; @@ -213,6 +215,7 @@ export class LegacyAlertsClient< flappingSettings, maintenanceWindowIds, alertDelay, + ruleRunMetricsStore, }); this.logAlerts({ diff --git a/x-pack/plugins/alerting/server/alerts_client/types.ts b/x-pack/plugins/alerting/server/alerts_client/types.ts index 1de1c06f5e826..043fcbe5c3d8f 100644 --- a/x-pack/plugins/alerting/server/alerts_client/types.ts +++ b/x-pack/plugins/alerting/server/alerts_client/types.ts @@ -120,6 +120,7 @@ export interface ProcessAlertsOpts { notifyOnActionGroupChange: boolean; maintenanceWindowIds: string[]; alertDelay: number; + ruleRunMetricsStore: RuleRunMetricsStore; } export interface LogAlertsOpts { diff --git a/x-pack/plugins/alerting/server/lib/alerting_event_logger/alerting_event_logger.test.ts b/x-pack/plugins/alerting/server/lib/alerting_event_logger/alerting_event_logger.test.ts index de71e4e67f590..62d2a2f14162d 100644 --- a/x-pack/plugins/alerting/server/lib/alerting_event_logger/alerting_event_logger.test.ts +++ b/x-pack/plugins/alerting/server/lib/alerting_event_logger/alerting_event_logger.test.ts @@ -726,6 +726,7 @@ describe('AlertingEventLogger', () => { numberOfNewAlerts: 4, numberOfRecoveredAlerts: 5, numSearches: 6, + numberOfDelayedAlerts: 7, esSearchDurationMs: 3300, totalSearchDurationMs: 10333, hasReachedAlertLimit: false, @@ -753,6 +754,7 @@ describe('AlertingEventLogger', () => { new: 4, recovered: 5, }, + number_of_delayed_alerts: 7, number_of_searches: 6, es_search_duration_ms: 3300, total_search_duration_ms: 10333, @@ -825,6 +827,7 @@ describe('AlertingEventLogger', () => { numberOfNewAlerts: 4, numberOfRecoveredAlerts: 5, numSearches: 6, + numberOfDelayedAlerts: 7, esSearchDurationMs: 3300, totalSearchDurationMs: 10333, hasReachedAlertLimit: false, @@ -862,6 +865,7 @@ describe('AlertingEventLogger', () => { new: 4, recovered: 5, }, + number_of_delayed_alerts: 7, number_of_searches: 6, es_search_duration_ms: 3300, total_search_duration_ms: 10333, @@ -910,6 +914,7 @@ describe('AlertingEventLogger', () => { new: 0, recovered: 0, }, + number_of_delayed_alerts: 0, number_of_searches: 0, es_search_duration_ms: 0, total_search_duration_ms: 0, diff --git a/x-pack/plugins/alerting/server/lib/alerting_event_logger/alerting_event_logger.ts b/x-pack/plugins/alerting/server/lib/alerting_event_logger/alerting_event_logger.ts index 680279f5dbac7..60ddb03c7c68a 100644 --- a/x-pack/plugins/alerting/server/lib/alerting_event_logger/alerting_event_logger.ts +++ b/x-pack/plugins/alerting/server/lib/alerting_event_logger/alerting_event_logger.ts @@ -440,6 +440,7 @@ export function updateEvent(event: IEvent, opts: UpdateEventOpts) { new: metrics.numberOfNewAlerts ? metrics.numberOfNewAlerts : 0, recovered: metrics.numberOfRecoveredAlerts ? metrics.numberOfRecoveredAlerts : 0, }, + number_of_delayed_alerts: metrics.numberOfDelayedAlerts ? metrics.numberOfDelayedAlerts : 0, number_of_searches: metrics.numSearches ? metrics.numSearches : 0, es_search_duration_ms: metrics.esSearchDurationMs ? metrics.esSearchDurationMs : 0, total_search_duration_ms: metrics.totalSearchDurationMs ? metrics.totalSearchDurationMs : 0, diff --git a/x-pack/plugins/alerting/server/lib/get_alerts_for_notification.test.ts b/x-pack/plugins/alerting/server/lib/get_alerts_for_notification.test.ts index 4656f4377f130..2528a27f19f9e 100644 --- a/x-pack/plugins/alerting/server/lib/get_alerts_for_notification.test.ts +++ b/x-pack/plugins/alerting/server/lib/get_alerts_for_notification.test.ts @@ -23,9 +23,11 @@ describe('getAlertsForNotification', () => { 'default', 0, { + // new alerts '1': alert1, }, { + // active alerts '1': alert1, '2': alert2, }, @@ -94,11 +96,13 @@ describe('getAlertsForNotification', () => { {}, {}, { + // recovered alerts '1': alert1, '2': alert2, '3': alert3, }, { + // current recovered alerts '1': alert1, '2': alert2, '3': alert3, @@ -228,11 +232,13 @@ describe('getAlertsForNotification', () => { {}, {}, { + // recovered alerts '1': alert1, '2': alert2, '3': alert3, }, { + // current recovered alerts '1': alert1, '2': alert2, '3': alert3, @@ -360,11 +366,13 @@ describe('getAlertsForNotification', () => { {}, {}, { + // recovered alerts '1': alert1, '2': alert2, '3': alert3, }, { + // current recovered alerts '1': alert1, '2': alert2, '3': alert3, @@ -459,21 +467,24 @@ describe('getAlertsForNotification', () => { }); const alert2 = new Alert('2', { meta: { uuid: 'uuid-2' } }); - const { newAlerts, activeAlerts, currentActiveAlerts } = getAlertsForNotification( - DEFAULT_FLAPPING_SETTINGS, - true, - 'default', - 0, - { - '1': alert1, - }, - { - '1': alert1, - '2': alert2, - }, - {}, - {} - ); + const { newAlerts, activeAlerts, currentActiveAlerts, delayedAlertsCount } = + getAlertsForNotification( + DEFAULT_FLAPPING_SETTINGS, + true, + 'default', + 0, + { + // new alerts + '1': alert1, + }, + { + // active alerts + '1': alert1, + '2': alert2, + }, + {}, + {} + ); expect(newAlerts).toMatchInlineSnapshot(` Object { "1": Object { @@ -536,28 +547,32 @@ describe('getAlertsForNotification', () => { }, } `); + expect(delayedAlertsCount).toBe(0); }); test('should reset activeCount for all recovered alerts', () => { const alert1 = new Alert('1', { meta: { activeCount: 3 } }); const alert3 = new Alert('3'); - const { recoveredAlerts, currentRecoveredAlerts } = getAlertsForNotification( - DEFAULT_FLAPPING_SETTINGS, - true, - 'default', - 0, - {}, - {}, - { - '1': alert1, - '3': alert3, - }, - { - '1': alert1, - '3': alert3, - } - ); + const { recoveredAlerts, currentRecoveredAlerts, delayedAlertsCount } = + getAlertsForNotification( + DEFAULT_FLAPPING_SETTINGS, + true, + 'default', + 0, + {}, + {}, + { + // recovered alerts + '1': alert1, + '3': alert3, + }, + { + // current recovered alerts + '1': alert1, + '3': alert3, + } + ); expect(alertsWithAnyUUID(recoveredAlerts)).toMatchInlineSnapshot(` Object { @@ -603,6 +618,7 @@ describe('getAlertsForNotification', () => { }, } `); + expect(delayedAlertsCount).toBe(0); }); test('should remove the alert from newAlerts and should not return the alert in currentActiveAlerts if the activeCount is less than the rule alertDelay', () => { @@ -611,21 +627,24 @@ describe('getAlertsForNotification', () => { }); const alert2 = new Alert('2', { meta: { uuid: 'uuid-2' } }); - const { newAlerts, activeAlerts, currentActiveAlerts } = getAlertsForNotification( - DEFAULT_FLAPPING_SETTINGS, - true, - 'default', - 5, - { - '1': alert1, - }, - { - '1': alert1, - '2': alert2, - }, - {}, - {} - ); + const { newAlerts, activeAlerts, currentActiveAlerts, delayedAlertsCount } = + getAlertsForNotification( + DEFAULT_FLAPPING_SETTINGS, + true, + 'default', + 5, + { + // new alerts + '1': alert1, + }, + { + // active alerts + '1': alert1, + '2': alert2, + }, + {}, + {} + ); expect(newAlerts).toMatchInlineSnapshot(`Object {}`); expect(activeAlerts).toMatchInlineSnapshot(` Object { @@ -652,23 +671,26 @@ describe('getAlertsForNotification', () => { } `); expect(currentActiveAlerts).toMatchInlineSnapshot(`Object {}`); + expect(delayedAlertsCount).toBe(2); }); test('should update active alert to look like a new alert if the activeCount is equal to the rule alertDelay', () => { const alert2 = new Alert('2', { meta: { uuid: 'uuid-2' } }); - const { newAlerts, activeAlerts, currentActiveAlerts } = getAlertsForNotification( - DEFAULT_FLAPPING_SETTINGS, - true, - 'default', - 1, - {}, - { - '2': alert2, - }, - {}, - {} - ); + const { newAlerts, activeAlerts, currentActiveAlerts, delayedAlertsCount } = + getAlertsForNotification( + DEFAULT_FLAPPING_SETTINGS, + true, + 'default', + 1, + {}, + { + // active alerts + '2': alert2, + }, + {}, + {} + ); expect(newAlerts['2'].getState().duration).toBe('0'); expect(newAlerts['2'].getState().start).toBeTruthy(); @@ -677,5 +699,7 @@ describe('getAlertsForNotification', () => { expect(currentActiveAlerts['2'].getState().duration).toBe('0'); expect(currentActiveAlerts['2'].getState().start).toBeTruthy(); + + expect(delayedAlertsCount).toBe(0); }); }); diff --git a/x-pack/plugins/alerting/server/lib/get_alerts_for_notification.ts b/x-pack/plugins/alerting/server/lib/get_alerts_for_notification.ts index 593d92b35383b..c5c7ac017b2c1 100644 --- a/x-pack/plugins/alerting/server/lib/get_alerts_for_notification.ts +++ b/x-pack/plugins/alerting/server/lib/get_alerts_for_notification.ts @@ -27,6 +27,7 @@ export function getAlertsForNotification< startedAt?: string | null ) { const currentActiveAlerts: Record> = {}; + let delayedAlertsCount = 0; for (const id of keys(activeAlerts)) { const alert = activeAlerts[id]; @@ -37,6 +38,7 @@ export function getAlertsForNotification< if (alert.getActiveCount() < alertDelay) { // remove from new alerts delete newAlerts[id]; + delayedAlertsCount += 1; } else { currentActiveAlerts[id] = alert; // if the active count is equal to the alertDelay it is considered a new alert @@ -100,5 +102,6 @@ export function getAlertsForNotification< currentActiveAlerts, recoveredAlerts, currentRecoveredAlerts, + delayedAlertsCount, }; } diff --git a/x-pack/plugins/alerting/server/lib/last_run_status.test.ts b/x-pack/plugins/alerting/server/lib/last_run_status.test.ts index c4b1ac0acc3a7..97c47d5294a30 100644 --- a/x-pack/plugins/alerting/server/lib/last_run_status.test.ts +++ b/x-pack/plugins/alerting/server/lib/last_run_status.test.ts @@ -24,6 +24,7 @@ const getMetrics = ({ numberOfNewAlerts: 12, numberOfRecoveredAlerts: 11, numberOfTriggeredActions: 5, + numberOfDelayedAlerts: 3, totalSearchDurationMs: 2, hasReachedAlertLimit, hasReachedQueuedActionsLimit, diff --git a/x-pack/plugins/alerting/server/lib/rule_execution_status.test.ts b/x-pack/plugins/alerting/server/lib/rule_execution_status.test.ts index 34c831db01a75..9eb66bbf7dbb3 100644 --- a/x-pack/plugins/alerting/server/lib/rule_execution_status.test.ts +++ b/x-pack/plugins/alerting/server/lib/rule_execution_status.test.ts @@ -28,6 +28,7 @@ const executionMetrics = { numberOfActiveAlerts: 2, numberOfNewAlerts: 3, numberOfRecoveredAlerts: 13, + numberOfDelayedAlerts: 7, hasReachedAlertLimit: false, triggeredActionsStatus: ActionsCompletion.COMPLETE, hasReachedQueuedActionsLimit: false, diff --git a/x-pack/plugins/alerting/server/lib/rule_run_metrics_store.mock.ts b/x-pack/plugins/alerting/server/lib/rule_run_metrics_store.mock.ts index 5f04ec74127b7..3a78242116b16 100644 --- a/x-pack/plugins/alerting/server/lib/rule_run_metrics_store.mock.ts +++ b/x-pack/plugins/alerting/server/lib/rule_run_metrics_store.mock.ts @@ -16,6 +16,7 @@ const createRuleRunMetricsStoreMock = () => { getNumberOfActiveAlerts: jest.fn(), getNumberOfRecoveredAlerts: jest.fn(), getNumberOfNewAlerts: jest.fn(), + getNumberOfDelayedAlerts: jest.fn(), getStatusByConnectorType: jest.fn(), getMetrics: jest.fn(), getHasReachedAlertLimit: jest.fn(), @@ -28,6 +29,7 @@ const createRuleRunMetricsStoreMock = () => { setNumberOfActiveAlerts: jest.fn(), setNumberOfRecoveredAlerts: jest.fn(), setNumberOfNewAlerts: jest.fn(), + setNumberOfDelayedAlerts: jest.fn(), setTriggeredActionsStatusByConnectorType: jest.fn(), setHasReachedAlertLimit: jest.fn(), hasReachedTheExecutableActionsLimit: jest.fn(), diff --git a/x-pack/plugins/alerting/server/lib/rule_run_metrics_store.test.ts b/x-pack/plugins/alerting/server/lib/rule_run_metrics_store.test.ts index e2b7cc61550bd..86b30041dba83 100644 --- a/x-pack/plugins/alerting/server/lib/rule_run_metrics_store.test.ts +++ b/x-pack/plugins/alerting/server/lib/rule_run_metrics_store.test.ts @@ -23,6 +23,7 @@ describe('RuleRunMetricsStore', () => { expect(ruleRunMetricsStore.getNumberOfActiveAlerts()).toBe(0); expect(ruleRunMetricsStore.getNumberOfRecoveredAlerts()).toBe(0); expect(ruleRunMetricsStore.getNumberOfNewAlerts()).toBe(0); + expect(ruleRunMetricsStore.getNumberOfDelayedAlerts()).toBe(0); expect(ruleRunMetricsStore.getStatusByConnectorType('any')).toBe(undefined); expect(ruleRunMetricsStore.getHasReachedAlertLimit()).toBe(false); expect(ruleRunMetricsStore.getHasReachedQueuedActionsLimit()).toBe(false); @@ -68,6 +69,11 @@ describe('RuleRunMetricsStore', () => { expect(ruleRunMetricsStore.getNumberOfNewAlerts()).toBe(12); }); + test('sets and returns getNumberOfDelayedAlerts', () => { + ruleRunMetricsStore.setNumberOfDelayedAlerts(7); + expect(ruleRunMetricsStore.getNumberOfDelayedAlerts()).toBe(7); + }); + test('sets and returns triggeredActionsStatusByConnectorType', () => { ruleRunMetricsStore.setTriggeredActionsStatusByConnectorType({ actionTypeId: testConnectorId, @@ -111,6 +117,7 @@ describe('RuleRunMetricsStore', () => { numberOfNewAlerts: 12, numberOfRecoveredAlerts: 11, numberOfTriggeredActions: 5, + numberOfDelayedAlerts: 7, totalSearchDurationMs: 2, hasReachedAlertLimit: true, hasReachedQueuedActionsLimit: true, diff --git a/x-pack/plugins/alerting/server/lib/rule_run_metrics_store.ts b/x-pack/plugins/alerting/server/lib/rule_run_metrics_store.ts index 80b72e0069bb6..e088586136eab 100644 --- a/x-pack/plugins/alerting/server/lib/rule_run_metrics_store.ts +++ b/x-pack/plugins/alerting/server/lib/rule_run_metrics_store.ts @@ -19,6 +19,7 @@ interface State { numberOfActiveAlerts: number; numberOfRecoveredAlerts: number; numberOfNewAlerts: number; + numberOfDelayedAlerts: number; hasReachedAlertLimit: boolean; connectorTypes: { [key: string]: { @@ -43,6 +44,7 @@ export class RuleRunMetricsStore { numberOfActiveAlerts: 0, numberOfRecoveredAlerts: 0, numberOfNewAlerts: 0, + numberOfDelayedAlerts: 0, hasReachedAlertLimit: false, connectorTypes: {}, hasReachedQueuedActionsLimit: false, @@ -79,6 +81,9 @@ export class RuleRunMetricsStore { public getNumberOfNewAlerts = () => { return this.state.numberOfNewAlerts; }; + public getNumberOfDelayedAlerts = () => { + return this.state.numberOfDelayedAlerts; + }; public getStatusByConnectorType = (actionTypeId: string) => { return this.state.connectorTypes[actionTypeId]; }; @@ -128,6 +133,9 @@ export class RuleRunMetricsStore { public setNumberOfNewAlerts = (numberOfNewAlerts: number) => { this.state.numberOfNewAlerts = numberOfNewAlerts; }; + public setNumberOfDelayedAlerts = (numberOfDelayedAlerts: number) => { + this.state.numberOfDelayedAlerts = numberOfDelayedAlerts; + }; public setTriggeredActionsStatusByConnectorType = ({ actionTypeId, status, diff --git a/x-pack/plugins/alerting/server/task_runner/task_runner.test.ts b/x-pack/plugins/alerting/server/task_runner/task_runner.test.ts index e4afa351d4f14..3994883ec9277 100644 --- a/x-pack/plugins/alerting/server/task_runner/task_runner.test.ts +++ b/x-pack/plugins/alerting/server/task_runner/task_runner.test.ts @@ -307,7 +307,7 @@ describe('Task Runner', () => { ); expect(logger.debug).nthCalledWith( 4, - 'ruleRunMetrics for test:1: {"numSearches":3,"totalSearchDurationMs":23423,"esSearchDurationMs":33,"numberOfTriggeredActions":0,"numberOfGeneratedActions":0,"numberOfActiveAlerts":0,"numberOfRecoveredAlerts":0,"numberOfNewAlerts":0,"hasReachedAlertLimit":false,"hasReachedQueuedActionsLimit":false,"triggeredActionsStatus":"complete"}' + 'ruleRunMetrics for test:1: {"numSearches":3,"totalSearchDurationMs":23423,"esSearchDurationMs":33,"numberOfTriggeredActions":0,"numberOfGeneratedActions":0,"numberOfActiveAlerts":0,"numberOfRecoveredAlerts":0,"numberOfNewAlerts":0,"numberOfDelayedAlerts":0,"hasReachedAlertLimit":false,"hasReachedQueuedActionsLimit":false,"triggeredActionsStatus":"complete"}' ); testAlertingEventLogCalls({ status: 'ok' }); @@ -389,7 +389,7 @@ describe('Task Runner', () => { ); expect(logger.debug).nthCalledWith( 5, - 'ruleRunMetrics for test:1: {"numSearches":3,"totalSearchDurationMs":23423,"esSearchDurationMs":33,"numberOfTriggeredActions":1,"numberOfGeneratedActions":1,"numberOfActiveAlerts":1,"numberOfRecoveredAlerts":0,"numberOfNewAlerts":1,"hasReachedAlertLimit":false,"hasReachedQueuedActionsLimit":false,"triggeredActionsStatus":"complete"}' + 'ruleRunMetrics for test:1: {"numSearches":3,"totalSearchDurationMs":23423,"esSearchDurationMs":33,"numberOfTriggeredActions":1,"numberOfGeneratedActions":1,"numberOfActiveAlerts":1,"numberOfRecoveredAlerts":0,"numberOfNewAlerts":1,"numberOfDelayedAlerts":0,"hasReachedAlertLimit":false,"hasReachedQueuedActionsLimit":false,"triggeredActionsStatus":"complete"}' ); testAlertingEventLogCalls({ @@ -477,7 +477,7 @@ describe('Task Runner', () => { ); expect(logger.debug).nthCalledWith( 6, - 'ruleRunMetrics for test:1: {"numSearches":3,"totalSearchDurationMs":23423,"esSearchDurationMs":33,"numberOfTriggeredActions":0,"numberOfGeneratedActions":0,"numberOfActiveAlerts":1,"numberOfRecoveredAlerts":0,"numberOfNewAlerts":1,"hasReachedAlertLimit":false,"hasReachedQueuedActionsLimit":false,"triggeredActionsStatus":"complete"}' + 'ruleRunMetrics for test:1: {"numSearches":3,"totalSearchDurationMs":23423,"esSearchDurationMs":33,"numberOfTriggeredActions":0,"numberOfGeneratedActions":0,"numberOfActiveAlerts":1,"numberOfRecoveredAlerts":0,"numberOfNewAlerts":1,"numberOfDelayedAlerts":0,"hasReachedAlertLimit":false,"hasReachedQueuedActionsLimit":false,"triggeredActionsStatus":"complete"}' ); testAlertingEventLogCalls({ @@ -931,7 +931,7 @@ describe('Task Runner', () => { ); expect(logger.debug).nthCalledWith( 6, - 'ruleRunMetrics for test:1: {"numSearches":3,"totalSearchDurationMs":23423,"esSearchDurationMs":33,"numberOfTriggeredActions":1,"numberOfGeneratedActions":1,"numberOfActiveAlerts":2,"numberOfRecoveredAlerts":0,"numberOfNewAlerts":2,"hasReachedAlertLimit":false,"hasReachedQueuedActionsLimit":false,"triggeredActionsStatus":"complete"}' + 'ruleRunMetrics for test:1: {"numSearches":3,"totalSearchDurationMs":23423,"esSearchDurationMs":33,"numberOfTriggeredActions":1,"numberOfGeneratedActions":1,"numberOfActiveAlerts":2,"numberOfRecoveredAlerts":0,"numberOfNewAlerts":2,"numberOfDelayedAlerts":0,"hasReachedAlertLimit":false,"hasReachedQueuedActionsLimit":false,"triggeredActionsStatus":"complete"}' ); expect(mockUsageCounter.incrementCounter).not.toHaveBeenCalled(); } @@ -1376,7 +1376,7 @@ describe('Task Runner', () => { ); expect(logger.debug).nthCalledWith( 6, - 'ruleRunMetrics for test:1: {"numSearches":3,"totalSearchDurationMs":23423,"esSearchDurationMs":33,"numberOfTriggeredActions":2,"numberOfGeneratedActions":2,"numberOfActiveAlerts":1,"numberOfRecoveredAlerts":1,"numberOfNewAlerts":0,"hasReachedAlertLimit":false,"hasReachedQueuedActionsLimit":false,"triggeredActionsStatus":"complete"}' + 'ruleRunMetrics for test:1: {"numSearches":3,"totalSearchDurationMs":23423,"esSearchDurationMs":33,"numberOfTriggeredActions":2,"numberOfGeneratedActions":2,"numberOfActiveAlerts":1,"numberOfRecoveredAlerts":1,"numberOfNewAlerts":0,"numberOfDelayedAlerts":0,"hasReachedAlertLimit":false,"hasReachedQueuedActionsLimit":false,"triggeredActionsStatus":"complete"}' ); testAlertingEventLogCalls({ @@ -1503,7 +1503,7 @@ describe('Task Runner', () => { ); expect(logger.debug).nthCalledWith( 6, - `ruleRunMetrics for test:1: {"numSearches":3,"totalSearchDurationMs":23423,"esSearchDurationMs":33,"numberOfTriggeredActions":2,"numberOfGeneratedActions":2,"numberOfActiveAlerts":1,"numberOfRecoveredAlerts":1,"numberOfNewAlerts":0,"hasReachedAlertLimit":false,"hasReachedQueuedActionsLimit":false,"triggeredActionsStatus":"complete"}` + `ruleRunMetrics for test:1: {"numSearches":3,"totalSearchDurationMs":23423,"esSearchDurationMs":33,"numberOfTriggeredActions":2,"numberOfGeneratedActions":2,"numberOfActiveAlerts":1,"numberOfRecoveredAlerts":1,"numberOfNewAlerts":0,"numberOfDelayedAlerts":0,"hasReachedAlertLimit":false,"hasReachedQueuedActionsLimit":false,"triggeredActionsStatus":"complete"}` ); testAlertingEventLogCalls({ @@ -2642,7 +2642,7 @@ describe('Task Runner', () => { ); expect(logger.debug).nthCalledWith( 4, - 'ruleRunMetrics for test:1: {"numSearches":3,"totalSearchDurationMs":23423,"esSearchDurationMs":33,"numberOfTriggeredActions":0,"numberOfGeneratedActions":0,"numberOfActiveAlerts":0,"numberOfRecoveredAlerts":0,"numberOfNewAlerts":0,"hasReachedAlertLimit":false,"hasReachedQueuedActionsLimit":false,"triggeredActionsStatus":"complete"}' + 'ruleRunMetrics for test:1: {"numSearches":3,"totalSearchDurationMs":23423,"esSearchDurationMs":33,"numberOfTriggeredActions":0,"numberOfGeneratedActions":0,"numberOfActiveAlerts":0,"numberOfRecoveredAlerts":0,"numberOfNewAlerts":0,"numberOfDelayedAlerts":0,"hasReachedAlertLimit":false,"hasReachedQueuedActionsLimit":false,"triggeredActionsStatus":"complete"}' ); testAlertingEventLogCalls({ @@ -3422,6 +3422,7 @@ describe('Task Runner', () => { numberOfNewAlerts: newAlerts, numberOfRecoveredAlerts: recoveredAlerts, numberOfTriggeredActions: triggeredActions, + numberOfDelayedAlerts: 0, totalSearchDurationMs: 23423, hasReachedAlertLimit, triggeredActionsStatus: 'partial', @@ -3458,6 +3459,7 @@ describe('Task Runner', () => { numberOfNewAlerts: newAlerts, numberOfRecoveredAlerts: recoveredAlerts, numberOfTriggeredActions: triggeredActions, + numberOfDelayedAlerts: 0, totalSearchDurationMs: 23423, hasReachedAlertLimit, triggeredActionsStatus: 'complete', diff --git a/x-pack/plugins/alerting/server/task_runner/task_runner.ts b/x-pack/plugins/alerting/server/task_runner/task_runner.ts index 2733521eab88f..aea121e7037ce 100644 --- a/x-pack/plugins/alerting/server/task_runner/task_runner.ts +++ b/x-pack/plugins/alerting/server/task_runner/task_runner.ts @@ -589,6 +589,7 @@ export class TaskRunner< some(actions, (action) => action.frequency?.notifyWhen === RuleNotifyWhen.CHANGE), maintenanceWindowIds: maintenanceWindowsWithoutScopedQueryIds, alertDelay: alertDelay?.active ?? 0, + ruleRunMetricsStore, }); }); diff --git a/x-pack/plugins/alerting/server/task_runner/task_runner_alerts_client.test.ts b/x-pack/plugins/alerting/server/task_runner/task_runner_alerts_client.test.ts index bd47acbbdb8c1..4274c320126de 100644 --- a/x-pack/plugins/alerting/server/task_runner/task_runner_alerts_client.test.ts +++ b/x-pack/plugins/alerting/server/task_runner/task_runner_alerts_client.test.ts @@ -462,7 +462,7 @@ describe('Task Runner', () => { ); expect(logger.debug).nthCalledWith( debugCall++, - 'ruleRunMetrics for test:1: {"numSearches":3,"totalSearchDurationMs":23423,"esSearchDurationMs":33,"numberOfTriggeredActions":0,"numberOfGeneratedActions":0,"numberOfActiveAlerts":0,"numberOfRecoveredAlerts":0,"numberOfNewAlerts":0,"hasReachedAlertLimit":false,"hasReachedQueuedActionsLimit":false,"triggeredActionsStatus":"complete"}' + 'ruleRunMetrics for test:1: {"numSearches":3,"totalSearchDurationMs":23423,"esSearchDurationMs":33,"numberOfTriggeredActions":0,"numberOfGeneratedActions":0,"numberOfActiveAlerts":0,"numberOfRecoveredAlerts":0,"numberOfNewAlerts":0,"numberOfDelayedAlerts":0,"hasReachedAlertLimit":false,"hasReachedQueuedActionsLimit":false,"triggeredActionsStatus":"complete"}' ); expect( taskRunnerFactoryInitializerParams.internalSavedObjectsRepository.update @@ -808,6 +808,7 @@ describe('Task Runner', () => { statusChangeThreshold: 4, }, maintenanceWindowIds: [], + ruleRunMetricsStore, }); expect(alertsClientToUse.logAlerts).toHaveBeenCalledWith({ diff --git a/x-pack/plugins/alerting/server/task_runner/task_runner_cancel.test.ts b/x-pack/plugins/alerting/server/task_runner/task_runner_cancel.test.ts index 0ce2f758ade39..8d7a8857a0e9a 100644 --- a/x-pack/plugins/alerting/server/task_runner/task_runner_cancel.test.ts +++ b/x-pack/plugins/alerting/server/task_runner/task_runner_cancel.test.ts @@ -473,7 +473,7 @@ describe('Task Runner Cancel', () => { ); expect(logger.debug).nthCalledWith( 8, - 'ruleRunMetrics for test:1: {"numSearches":3,"totalSearchDurationMs":23423,"esSearchDurationMs":33,"numberOfTriggeredActions":1,"numberOfGeneratedActions":1,"numberOfActiveAlerts":1,"numberOfRecoveredAlerts":0,"numberOfNewAlerts":1,"hasReachedAlertLimit":false,"hasReachedQueuedActionsLimit":false,"triggeredActionsStatus":"complete"}' + 'ruleRunMetrics for test:1: {"numSearches":3,"totalSearchDurationMs":23423,"esSearchDurationMs":33,"numberOfTriggeredActions":1,"numberOfGeneratedActions":1,"numberOfActiveAlerts":1,"numberOfRecoveredAlerts":0,"numberOfNewAlerts":1,"numberOfDelayedAlerts":0,"hasReachedAlertLimit":false,"hasReachedQueuedActionsLimit":false,"triggeredActionsStatus":"complete"}' ); } @@ -517,6 +517,7 @@ describe('Task Runner Cancel', () => { numberOfNewAlerts: newAlerts, numberOfRecoveredAlerts: recoveredAlerts, numberOfTriggeredActions: triggeredActions, + numberOfDelayedAlerts: 0, totalSearchDurationMs: 23423, hasReachedAlertLimit, triggeredActionsStatus: 'complete', diff --git a/x-pack/plugins/event_log/generated/mappings.json b/x-pack/plugins/event_log/generated/mappings.json index 836228fad02f7..561217eeae803 100644 --- a/x-pack/plugins/event_log/generated/mappings.json +++ b/x-pack/plugins/event_log/generated/mappings.json @@ -359,6 +359,9 @@ } } }, + "number_of_delayed_alerts": { + "type": "long" + }, "number_of_searches": { "type": "long" }, diff --git a/x-pack/plugins/event_log/generated/schemas.ts b/x-pack/plugins/event_log/generated/schemas.ts index ea407540f7dbc..b8be2221ec1ed 100644 --- a/x-pack/plugins/event_log/generated/schemas.ts +++ b/x-pack/plugins/event_log/generated/schemas.ts @@ -162,6 +162,7 @@ export const EventSchema = schema.maybe( recovered: ecsStringOrNumber(), }) ), + number_of_delayed_alerts: ecsStringOrNumber(), number_of_searches: ecsStringOrNumber(), total_indexing_duration_ms: ecsStringOrNumber(), es_search_duration_ms: ecsStringOrNumber(), diff --git a/x-pack/plugins/event_log/scripts/mappings.js b/x-pack/plugins/event_log/scripts/mappings.js index c0c3aa9f2b152..6d0ec3635ab41 100644 --- a/x-pack/plugins/event_log/scripts/mappings.js +++ b/x-pack/plugins/event_log/scripts/mappings.js @@ -134,6 +134,9 @@ exports.EcsCustomPropertyMappings = { }, }, }, + number_of_delayed_alerts: { + type: 'long', + }, number_of_searches: { type: 'long', }, diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group1/event_log.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group1/event_log.ts index da3752e098de2..79e5b659e341d 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group1/event_log.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group1/event_log.ts @@ -1855,6 +1855,7 @@ export default function eventLogTests({ getService }: FtrProviderContext) { const NEW_PATH = 'kibana.alert.rule.execution.metrics.alert_counts.new'; const RECOVERED_PATH = 'kibana.alert.rule.execution.metrics.alert_counts.recovered'; const ACTION_PATH = 'kibana.alert.rule.execution.metrics.number_of_triggered_actions'; + const DELAYED_PATH = 'kibana.alert.rule.execution.metrics.number_of_delayed_alerts'; const { body: createdAction } = await supertest .post(`${getUrlPrefix(space.id)}/api/actions/connector`) @@ -1933,6 +1934,7 @@ export default function eventLogTests({ getService }: FtrProviderContext) { expect(get(event, NEW_PATH)).to.be(0); expect(get(event, RECOVERED_PATH)).to.be(0); expect(get(event, ACTION_PATH)).to.be(0); + expect(get(event, DELAYED_PATH)).to.be(1); }); // third executions creates the delayed active alert and triggers actions @@ -1940,24 +1942,28 @@ export default function eventLogTests({ getService }: FtrProviderContext) { expect(get(executeEvents[2], NEW_PATH)).to.be(1); expect(get(executeEvents[2], RECOVERED_PATH)).to.be(0); expect(get(executeEvents[2], ACTION_PATH)).to.be(1); + expect(get(executeEvents[2], DELAYED_PATH)).to.be(0); // fourth execution expect(get(executeEvents[3], ACTIVE_PATH)).to.be(1); expect(get(executeEvents[3], NEW_PATH)).to.be(0); expect(get(executeEvents[3], RECOVERED_PATH)).to.be(0); expect(get(executeEvents[3], ACTION_PATH)).to.be(0); + expect(get(executeEvents[3], DELAYED_PATH)).to.be(0); // fifth recovered execution expect(get(executeEvents[4], ACTIVE_PATH)).to.be(0); expect(get(executeEvents[4], NEW_PATH)).to.be(0); expect(get(executeEvents[4], RECOVERED_PATH)).to.be(1); expect(get(executeEvents[4], ACTION_PATH)).to.be(0); + expect(get(executeEvents[4], DELAYED_PATH)).to.be(0); // sixth execution does not create the active alert expect(get(executeEvents[5], ACTIVE_PATH)).to.be(0); expect(get(executeEvents[5], NEW_PATH)).to.be(0); expect(get(executeEvents[5], RECOVERED_PATH)).to.be(0); expect(get(executeEvents[5], ACTION_PATH)).to.be(0); + expect(get(executeEvents[5], DELAYED_PATH)).to.be(1); }); }); } diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group4/alerts_as_data/alerts_as_data_alert_delay.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group4/alerts_as_data/alerts_as_data_alert_delay.ts index f7e2876e9775b..c900a08311add 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group4/alerts_as_data/alerts_as_data_alert_delay.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group4/alerts_as_data/alerts_as_data_alert_delay.ts @@ -53,6 +53,7 @@ export default function createAlertsAsDataAlertDelayInstallResourcesTest({ const RECOVERED_PATH = 'kibana.alert.rule.execution.metrics.alert_counts.recovered'; const ACTION_PATH = 'kibana.alert.rule.execution.metrics.number_of_triggered_actions'; const UUID_PATH = 'kibana.alert.rule.execution.uuid'; + const DELAYED_PATH = 'kibana.alert.rule.execution.metrics.number_of_delayed_alerts'; const es = getService('es'); const retry = getService('retry'); @@ -152,6 +153,7 @@ export default function createAlertsAsDataAlertDelayInstallResourcesTest({ expect(get(executeEvent, NEW_PATH)).to.be(0); expect(get(executeEvent, RECOVERED_PATH)).to.be(0); expect(get(executeEvent, ACTION_PATH)).to.be(0); + expect(get(executeEvent, DELAYED_PATH)).to.be(1); // Query for alerts const alertDocsRun1 = await queryForAlertDocs(); @@ -178,6 +180,7 @@ export default function createAlertsAsDataAlertDelayInstallResourcesTest({ expect(get(executeEvent, NEW_PATH)).to.be(0); expect(get(executeEvent, RECOVERED_PATH)).to.be(0); expect(get(executeEvent, ACTION_PATH)).to.be(0); + expect(get(executeEvent, DELAYED_PATH)).to.be(1); // Query for alerts const alertDocsRun2 = await queryForAlertDocs(); @@ -205,6 +208,7 @@ export default function createAlertsAsDataAlertDelayInstallResourcesTest({ expect(get(executeEvent, NEW_PATH)).to.be(1); expect(get(executeEvent, RECOVERED_PATH)).to.be(0); expect(get(executeEvent, ACTION_PATH)).to.be(1); + expect(get(executeEvent, DELAYED_PATH)).to.be(0); // Query for alerts const alertDocsRun3 = await queryForAlertDocs(); @@ -260,6 +264,7 @@ export default function createAlertsAsDataAlertDelayInstallResourcesTest({ expect(get(executeEvent, NEW_PATH)).to.be(0); expect(get(executeEvent, RECOVERED_PATH)).to.be(0); expect(get(executeEvent, ACTION_PATH)).to.be(0); + expect(get(executeEvent, DELAYED_PATH)).to.be(0); // Query for alerts const alertDocsRun4 = await queryForAlertDocs(); @@ -311,6 +316,7 @@ export default function createAlertsAsDataAlertDelayInstallResourcesTest({ expect(get(executeEvent, NEW_PATH)).to.be(0); expect(get(executeEvent, RECOVERED_PATH)).to.be(1); expect(get(executeEvent, ACTION_PATH)).to.be(0); + expect(get(executeEvent, DELAYED_PATH)).to.be(0); // Query for alerts const alertDocsRun5 = await queryForAlertDocs(); @@ -366,6 +372,7 @@ export default function createAlertsAsDataAlertDelayInstallResourcesTest({ expect(get(executeEvent, NEW_PATH)).to.be(0); expect(get(executeEvent, RECOVERED_PATH)).to.be(0); expect(get(executeEvent, ACTION_PATH)).to.be(0); + expect(get(executeEvent, DELAYED_PATH)).to.be(1); // Query for alerts const alertDocsRun6 = await queryForAlertDocs(); @@ -439,6 +446,7 @@ export default function createAlertsAsDataAlertDelayInstallResourcesTest({ expect(get(executeEvent, NEW_PATH)).to.be(0); expect(get(executeEvent, RECOVERED_PATH)).to.be(0); expect(get(executeEvent, ACTION_PATH)).to.be(0); + expect(get(executeEvent, DELAYED_PATH)).to.be(2); // Query for alerts const alertDocsRun1 = await queryForAlertDocs(alwaysFiringAlertsAsDataIndex); @@ -465,6 +473,7 @@ export default function createAlertsAsDataAlertDelayInstallResourcesTest({ expect(get(executeEvent, NEW_PATH)).to.be(0); expect(get(executeEvent, RECOVERED_PATH)).to.be(0); expect(get(executeEvent, ACTION_PATH)).to.be(0); + expect(get(executeEvent, DELAYED_PATH)).to.be(2); // Query for alerts const alertDocsRun2 = await queryForAlertDocs(alwaysFiringAlertsAsDataIndex); @@ -493,6 +502,7 @@ export default function createAlertsAsDataAlertDelayInstallResourcesTest({ expect(get(executeEvent, NEW_PATH)).to.be(2); expect(get(executeEvent, RECOVERED_PATH)).to.be(0); expect(get(executeEvent, ACTION_PATH)).to.be(2); + expect(get(executeEvent, DELAYED_PATH)).to.be(0); // Query for alerts const alertDocsRun3 = await queryForAlertDocs(alwaysFiringAlertsAsDataIndex); @@ -555,6 +565,7 @@ export default function createAlertsAsDataAlertDelayInstallResourcesTest({ expect(get(executeEvent, NEW_PATH)).to.be(0); expect(get(executeEvent, RECOVERED_PATH)).to.be(0); expect(get(executeEvent, ACTION_PATH)).to.be(0); + expect(get(executeEvent, DELAYED_PATH)).to.be(0); // Query for alerts const alertDocsRun4 = await queryForAlertDocs(alwaysFiringAlertsAsDataIndex); From 06ecb5aee9771dacd4f2f8121dbe313435e364c4 Mon Sep 17 00:00:00 2001 From: Paulo Henrique Date: Mon, 12 Feb 2024 11:39:29 -0800 Subject: [PATCH 22/83] [Cloud Security] [Findings] Set misconfiguration tab as default and Fix non matching groups data (#176735) ## Summary This PR addresses the following issues in the Findings -> Misconfigurations page: - Updated Misconfigurations to be the Default selected tab in the Findings page on the initial load (when LocalStorage is not set yet) https://github.com/elastic/kibana/assets/19270322/dc3f071e-f64f-4cf4-8d63-fb481ea98d6b - Fixed an issue with the Non-Matching group that wasn't combining the must_not filter from the grouping with the Benchmark Rules. https://github.com/elastic/kibana/assets/19270322/b428070d-f237-40f9-8249-28fdb0fcddf4 --- .../configurations/latest_findings/use_latest_findings.ts | 2 +- .../public/pages/findings/findings.tsx | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/x-pack/plugins/cloud_security_posture/public/pages/configurations/latest_findings/use_latest_findings.ts b/x-pack/plugins/cloud_security_posture/public/pages/configurations/latest_findings/use_latest_findings.ts index 5a516920df1bd..69638f92e5b64 100644 --- a/x-pack/plugins/cloud_security_posture/public/pages/configurations/latest_findings/use_latest_findings.ts +++ b/x-pack/plugins/cloud_security_posture/public/pages/configurations/latest_findings/use_latest_findings.ts @@ -74,7 +74,7 @@ export const getFindingsQuery = ( }, }, ], - must_not: mutedRulesFilterQuery, + must_not: [...(query?.bool?.must_not ?? []), ...mutedRulesFilterQuery], }, }, ...(pageParam ? { from: pageParam } : {}), diff --git a/x-pack/plugins/cloud_security_posture/public/pages/findings/findings.tsx b/x-pack/plugins/cloud_security_posture/public/pages/findings/findings.tsx index 67e46d82020cc..f8d91d5250234 100644 --- a/x-pack/plugins/cloud_security_posture/public/pages/findings/findings.tsx +++ b/x-pack/plugins/cloud_security_posture/public/pages/findings/findings.tsx @@ -47,9 +47,11 @@ const FindingsTabRedirecter = ({ lastTabSelected }: { lastTabSelected?: Findings ); } - // otherwise stay on the vulnerabilities tab, since it's the first one. + // otherwise stay on the misconfigurations tab, since it's the first one. return ( - + ); }; From 6827db46d5f922be50cf35a09de045e8b813275b Mon Sep 17 00:00:00 2001 From: Devon Thomson Date: Mon, 12 Feb 2024 14:55:16 -0500 Subject: [PATCH 23/83] [Embeddable rebuild] Allow Dashboard to provide references (#176455) Adds the ability for the Dashboard to provide references for its React Embeddable children to inject, and adds the ability for the React Embeddable children to provide extracted references back to the Dashboard. --- examples/embeddable_examples/kibana.jsonc | 9 +- examples/embeddable_examples/public/plugin.ts | 20 +- .../react_embeddables/field_list/constants.ts | 11 + .../field_list/create_field_list_action.tsx | 35 +++ .../field_list_react_embeddable.tsx | 221 ++++++++++++++++++ .../react_embeddables/field_list/types.ts | 19 ++ examples/embeddable_examples/tsconfig.json | 10 +- .../interfaces/serialized_state.ts | 4 +- .../presentation_containers/tsconfig.json | 2 +- .../dashboard_container_references.ts | 68 +++--- .../component/grid/dashboard_grid_item.tsx | 21 +- .../embeddable/api/run_save_functions.tsx | 29 ++- .../embeddable/create/create_dashboard.ts | 7 + .../embeddable/dashboard_container.tsx | 34 +-- .../dashboard_content_management_service.ts | 3 +- .../lib/load_dashboard_state.ts | 9 +- .../lib/save_dashboard_state.ts | 13 +- .../dashboard_content_management/types.ts | 9 + 18 files changed, 443 insertions(+), 81 deletions(-) create mode 100644 examples/embeddable_examples/public/react_embeddables/field_list/constants.ts create mode 100644 examples/embeddable_examples/public/react_embeddables/field_list/create_field_list_action.tsx create mode 100644 examples/embeddable_examples/public/react_embeddables/field_list/field_list_react_embeddable.tsx create mode 100644 examples/embeddable_examples/public/react_embeddables/field_list/types.ts diff --git a/examples/embeddable_examples/kibana.jsonc b/examples/embeddable_examples/kibana.jsonc index 339533a18ca4f..0788268aedf3f 100644 --- a/examples/embeddable_examples/kibana.jsonc +++ b/examples/embeddable_examples/kibana.jsonc @@ -8,12 +8,15 @@ "server": true, "browser": true, "requiredPlugins": [ + "dataViews", "embeddable", "uiActions", "dashboard", + "data", + "charts", + "fieldFormats" ], - "extraPublicDirs": [ - "public/hello_world" - ] + "requiredBundles": ["presentationUtil"], + "extraPublicDirs": ["public/hello_world"] } } diff --git a/examples/embeddable_examples/public/plugin.ts b/examples/embeddable_examples/public/plugin.ts index c94eef3107972..83c5b911dc8e4 100644 --- a/examples/embeddable_examples/public/plugin.ts +++ b/examples/embeddable_examples/public/plugin.ts @@ -6,9 +6,13 @@ * Side Public License, v 1. */ +import { DataViewsPublicPluginStart } from '@kbn/data-views-plugin/public'; import { EmbeddableSetup, EmbeddableStart } from '@kbn/embeddable-plugin/public'; import { Plugin, CoreSetup, CoreStart } from '@kbn/core/public'; import { UiActionsStart } from '@kbn/ui-actions-plugin/public'; +import { DataPublicPluginStart } from '@kbn/data-plugin/public'; +import { ChartsPluginStart } from '@kbn/charts-plugin/public'; +import { FieldFormatsStart } from '@kbn/field-formats-plugin/public'; import { HelloWorldEmbeddableFactory, HELLO_WORLD_EMBEDDABLE, @@ -33,6 +37,8 @@ import { } from './filter_debugger'; import { registerMarkdownEditorEmbeddable } from './react_embeddables/eui_markdown/eui_markdown_react_embeddable'; import { registerCreateEuiMarkdownAction } from './react_embeddables/eui_markdown/create_eui_markdown_action'; +import { registerFieldListFactory } from './react_embeddables/field_list/field_list_react_embeddable'; +import { registerCreateFieldListAction } from './react_embeddables/field_list/create_field_list_action'; export interface EmbeddableExamplesSetupDependencies { embeddable: EmbeddableSetup; @@ -40,7 +46,12 @@ export interface EmbeddableExamplesSetupDependencies { } export interface EmbeddableExamplesStartDependencies { + dataViews: DataViewsPublicPluginStart; embeddable: EmbeddableStart; + uiActions: UiActionsStart; + data: DataPublicPluginStart; + charts: ChartsPluginStart; + fieldFormats: FieldFormatsStart; } interface ExampleEmbeddableFactories { @@ -70,9 +81,6 @@ export class EmbeddableExamplesPlugin core: CoreSetup, deps: EmbeddableExamplesSetupDependencies ) { - registerMarkdownEditorEmbeddable(); - registerCreateEuiMarkdownAction(deps.uiActions); - this.exampleEmbeddableFactories.getHelloWorldEmbeddableFactory = deps.embeddable.registerEmbeddableFactory( HELLO_WORLD_EMBEDDABLE, @@ -104,6 +112,12 @@ export class EmbeddableExamplesPlugin core: CoreStart, deps: EmbeddableExamplesStartDependencies ): EmbeddableExamplesStart { + registerFieldListFactory(core, deps); + registerCreateFieldListAction(deps.uiActions); + + registerMarkdownEditorEmbeddable(); + registerCreateEuiMarkdownAction(deps.uiActions); + return { createSampleData: async () => {}, factories: this.exampleEmbeddableFactories as ExampleEmbeddableFactories, diff --git a/examples/embeddable_examples/public/react_embeddables/field_list/constants.ts b/examples/embeddable_examples/public/react_embeddables/field_list/constants.ts new file mode 100644 index 0000000000000..213d76e0fe874 --- /dev/null +++ b/examples/embeddable_examples/public/react_embeddables/field_list/constants.ts @@ -0,0 +1,11 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +export const FIELD_LIST_ID = 'field_list'; +export const ADD_FIELD_LIST_ACTION_ID = 'create_field_list'; +export const FIELD_LIST_DATA_VIEW_REF_NAME = 'field_list_data_view_id'; diff --git a/examples/embeddable_examples/public/react_embeddables/field_list/create_field_list_action.tsx b/examples/embeddable_examples/public/react_embeddables/field_list/create_field_list_action.tsx new file mode 100644 index 0000000000000..aef3d121cd9d2 --- /dev/null +++ b/examples/embeddable_examples/public/react_embeddables/field_list/create_field_list_action.tsx @@ -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 { i18n } from '@kbn/i18n'; +import { apiIsPresentationContainer } from '@kbn/presentation-containers'; +import { EmbeddableApiContext } from '@kbn/presentation-publishing'; +import { IncompatibleActionError } from '@kbn/ui-actions-plugin/public'; +import { UiActionsPublicStart } from '@kbn/ui-actions-plugin/public/plugin'; +import { ADD_FIELD_LIST_ACTION_ID, FIELD_LIST_ID } from './constants'; + +export const registerCreateFieldListAction = (uiActions: UiActionsPublicStart) => { + uiActions.registerAction({ + id: ADD_FIELD_LIST_ACTION_ID, + getIconType: () => 'indexOpen', + isCompatible: async ({ embeddable }) => { + return apiIsPresentationContainer(embeddable); + }, + execute: async ({ embeddable }) => { + if (!apiIsPresentationContainer(embeddable)) throw new IncompatibleActionError(); + embeddable.addNewPanel({ + panelType: FIELD_LIST_ID, + }); + }, + getDisplayName: () => + i18n.translate('embeddableExamples.unifiedFieldList.displayName', { + defaultMessage: 'Field list', + }), + }); + uiActions.attachAction('ADD_PANEL_TRIGGER', ADD_FIELD_LIST_ACTION_ID); +}; diff --git a/examples/embeddable_examples/public/react_embeddables/field_list/field_list_react_embeddable.tsx b/examples/embeddable_examples/public/react_embeddables/field_list/field_list_react_embeddable.tsx new file mode 100644 index 0000000000000..38122c7393c84 --- /dev/null +++ b/examples/embeddable_examples/public/react_embeddables/field_list/field_list_react_embeddable.tsx @@ -0,0 +1,221 @@ +/* + * 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 { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; +import { css } from '@emotion/react'; +import { ChartsPluginStart } from '@kbn/charts-plugin/public'; +import { Reference } from '@kbn/content-management-utils'; +import { CoreStart } from '@kbn/core-lifecycle-browser'; +import { DataPublicPluginStart } from '@kbn/data-plugin/public'; +import { + DataViewsPublicPluginStart, + DATA_VIEW_SAVED_OBJECT_TYPE, + type DataView, +} from '@kbn/data-views-plugin/public'; +import { + initializeReactEmbeddableTitles, + initializeReactEmbeddableUuid, + ReactEmbeddableFactory, + RegisterReactEmbeddable, + registerReactEmbeddableFactory, + useReactEmbeddableApiHandle, + useReactEmbeddableUnsavedChanges, +} from '@kbn/embeddable-plugin/public'; +import { FieldFormatsStart } from '@kbn/field-formats-plugin/public'; +import { i18n } from '@kbn/i18n'; +import { useBatchedPublishingSubjects } from '@kbn/presentation-publishing'; +import { LazyDataViewPicker, withSuspense } from '@kbn/presentation-util-plugin/public'; +import { euiThemeVars } from '@kbn/ui-theme'; +import { + UnifiedFieldListSidebarContainer, + type UnifiedFieldListSidebarContainerProps, +} from '@kbn/unified-field-list'; +import { cloneDeep } from 'lodash'; +import React, { useEffect, useState } from 'react'; +import { BehaviorSubject } from 'rxjs'; +import { FIELD_LIST_DATA_VIEW_REF_NAME, FIELD_LIST_ID } from './constants'; +import { FieldListApi, FieldListSerializedStateState } from './types'; + +const DataViewPicker = withSuspense(LazyDataViewPicker, null); + +const getCreationOptions: UnifiedFieldListSidebarContainerProps['getCreationOptions'] = () => { + return { + originatingApp: '', + localStorageKeyPrefix: 'examples', + timeRangeUpdatesType: 'timefilter', + compressed: true, + showSidebarToggleButton: false, + disablePopularFields: true, + }; +}; + +export const registerFieldListFactory = ( + core: CoreStart, + { + dataViews, + data, + charts, + fieldFormats, + }: { + dataViews: DataViewsPublicPluginStart; + data: DataPublicPluginStart; + charts: ChartsPluginStart; + fieldFormats: FieldFormatsStart; + } +) => { + const fieldListEmbeddableFactory: ReactEmbeddableFactory< + FieldListSerializedStateState, + FieldListApi + > = { + deserializeState: (state) => { + const serializedState = cloneDeep(state.rawState) as FieldListSerializedStateState; + // inject the reference + const dataViewIdRef = state.references?.find( + (ref) => ref.name === FIELD_LIST_DATA_VIEW_REF_NAME + ); + if (dataViewIdRef && serializedState) { + serializedState.dataViewId = dataViewIdRef?.id; + } + return serializedState; + }, + getComponent: async (initialState, maybeId) => { + const uuid = initializeReactEmbeddableUuid(maybeId); + const { titlesApi, titleComparators, serializeTitles } = + initializeReactEmbeddableTitles(initialState); + + const allDataViews = await dataViews.getIdsWithTitle(); + + const selectedDataViewId$ = new BehaviorSubject( + initialState.dataViewId ?? (await dataViews.getDefaultDataView())?.id + ); + const selectedFieldNames$ = new BehaviorSubject( + initialState.selectedFieldNames + ); + + return RegisterReactEmbeddable((apiRef) => { + const { unsavedChanges, resetUnsavedChanges } = useReactEmbeddableUnsavedChanges( + uuid, + fieldListEmbeddableFactory, + { + dataViewId: [selectedDataViewId$, (value) => selectedDataViewId$.next(value)], + selectedFieldNames: [ + selectedFieldNames$, + (value) => selectedFieldNames$.next(value), + (a, b) => { + return (a?.slice().sort().join(',') ?? '') === (b?.slice().sort().join(',') ?? ''); + }, + ], + ...titleComparators, + } + ); + + useReactEmbeddableApiHandle( + { + ...titlesApi, + unsavedChanges, + resetUnsavedChanges, + serializeState: async () => { + const dataViewId = selectedDataViewId$.getValue(); + const references: Reference[] = dataViewId + ? [ + { + type: DATA_VIEW_SAVED_OBJECT_TYPE, + name: FIELD_LIST_DATA_VIEW_REF_NAME, + id: dataViewId, + }, + ] + : []; + return { + rawState: { + ...serializeTitles(), + // here we skip serializing the dataViewId, because the reference contains that information. + selectedFieldNames: selectedFieldNames$.getValue(), + }, + references, + }; + }, + }, + apiRef, + uuid + ); + + const [selectedDataViewId, selectedFieldNames] = useBatchedPublishingSubjects( + selectedDataViewId$, + selectedFieldNames$ + ); + + const [selectedDataView, setSelectedDataView] = useState(undefined); + + useEffect(() => { + if (!selectedDataViewId) return; + let mounted = true; + (async () => { + const dataView = await dataViews.get(selectedDataViewId); + if (!mounted) return; + setSelectedDataView(dataView); + })(); + return () => { + mounted = false; + }; + }, [selectedDataViewId]); + + return ( + + + { + selectedDataViewId$.next(nextSelection); + }} + trigger={{ + label: + selectedDataView?.getName() ?? + i18n.translate('embeddableExamples.unifiedFieldList.selectDataViewMessage', { + defaultMessage: 'Please select a data view', + }), + }} + /> + + + {selectedDataView ? ( + + selectedFieldNames$.next([ + ...(selectedFieldNames$.getValue() ?? []), + field.name, + ]) + } + onRemoveFieldFromWorkspace={(field) => { + selectedFieldNames$.next( + (selectedFieldNames$.getValue() ?? []).filter((name) => name !== field.name) + ); + }} + /> + ) : null} + + + ); + }); + }, + }; + + registerReactEmbeddableFactory(FIELD_LIST_ID, fieldListEmbeddableFactory); +}; diff --git a/examples/embeddable_examples/public/react_embeddables/field_list/types.ts b/examples/embeddable_examples/public/react_embeddables/field_list/types.ts new file mode 100644 index 0000000000000..9e51d89be58a5 --- /dev/null +++ b/examples/embeddable_examples/public/react_embeddables/field_list/types.ts @@ -0,0 +1,19 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { + DefaultEmbeddableApi, + SerializedReactEmbeddableTitles, +} from '@kbn/embeddable-plugin/public'; + +export type FieldListSerializedStateState = SerializedReactEmbeddableTitles & { + dataViewId?: string; + selectedFieldNames?: string[]; +}; + +export type FieldListApi = DefaultEmbeddableApi; diff --git a/examples/embeddable_examples/tsconfig.json b/examples/embeddable_examples/tsconfig.json index 35f799c8c4e3a..65704c797e1e9 100644 --- a/examples/embeddable_examples/tsconfig.json +++ b/examples/embeddable_examples/tsconfig.json @@ -21,6 +21,14 @@ "@kbn/ui-theme", "@kbn/i18n", "@kbn/es-query", - "@kbn/presentation-containers" + "@kbn/presentation-containers", + "@kbn/data-views-plugin", + "@kbn/data-plugin", + "@kbn/charts-plugin", + "@kbn/field-formats-plugin", + "@kbn/content-management-utils", + "@kbn/core-lifecycle-browser", + "@kbn/presentation-util-plugin", + "@kbn/unified-field-list" ] } diff --git a/packages/presentation/presentation_containers/interfaces/serialized_state.ts b/packages/presentation/presentation_containers/interfaces/serialized_state.ts index 6b24471d34c75..87d51580ca6dd 100644 --- a/packages/presentation/presentation_containers/interfaces/serialized_state.ts +++ b/packages/presentation/presentation_containers/interfaces/serialized_state.ts @@ -6,14 +6,14 @@ * Side Public License, v 1. */ -import type { SavedObjectReference } from '@kbn/core-saved-objects-api-server'; +import { Reference } from '@kbn/content-management-utils'; /** * A package containing the serialized Embeddable state, with references extracted. When saving Embeddables using any * strategy, this is the format that should be used. */ export interface SerializedPanelState { - references?: SavedObjectReference[]; + references?: Reference[]; rawState: RawStateType; version?: string; } diff --git a/packages/presentation/presentation_containers/tsconfig.json b/packages/presentation/presentation_containers/tsconfig.json index 7892712228da2..8e25a7b80c6e2 100644 --- a/packages/presentation/presentation_containers/tsconfig.json +++ b/packages/presentation/presentation_containers/tsconfig.json @@ -9,6 +9,6 @@ "kbn_references": [ "@kbn/presentation-publishing", "@kbn/core-mount-utils-browser", - "@kbn/core-saved-objects-api-server", + "@kbn/content-management-utils", ] } diff --git a/src/plugins/dashboard/common/dashboard_container/persistable_state/dashboard_container_references.ts b/src/plugins/dashboard/common/dashboard_container/persistable_state/dashboard_container_references.ts index 7b4d682085352..987fce0f0455b 100644 --- a/src/plugins/dashboard/common/dashboard_container/persistable_state/dashboard_container_references.ts +++ b/src/plugins/dashboard/common/dashboard_container/persistable_state/dashboard_container_references.ts @@ -6,18 +6,32 @@ * Side Public License, v 1. */ +import { Reference } from '@kbn/content-management-utils'; +import { CONTROL_GROUP_TYPE, PersistableControlGroupInput } from '@kbn/controls-plugin/common'; import { EmbeddableInput, - EmbeddableStateWithType, EmbeddablePersistableStateService, + EmbeddableStateWithType, } from '@kbn/embeddable-plugin/common'; -import { Reference } from '@kbn/content-management-utils'; -import { CONTROL_GROUP_TYPE, PersistableControlGroupInput } from '@kbn/controls-plugin/common'; - -import { DashboardPanelState } from '../types'; import { ParsedDashboardAttributesWithType } from '../../types'; -const getPanelStatePrefix = (state: DashboardPanelState) => `${state.explicitInput.id}:`; +export const getReferencesForPanelId = (id: string, references: Reference[]): Reference[] => { + const prefix = `${id}:`; + const filteredReferences = references + .filter((reference) => reference.name.indexOf(prefix) === 0) + .map((reference) => ({ ...reference, name: reference.name.replace(prefix, '') })); + return filteredReferences; +}; + +export const prefixReferencesFromPanel = (id: string, references: Reference[]): Reference[] => { + const prefix = `${id}:`; + return references + .filter((reference) => reference.type !== 'tag') // panel references should never contain tags. If they do, they must be removed + .map((reference) => ({ + ...reference, + name: `${prefix}${reference.name}`, + })); +}; const controlGroupReferencePrefix = 'controlGroup_'; const controlGroupId = 'dashboard_control_group'; @@ -35,16 +49,15 @@ export const createInject = ( for (const [key, panel] of Object.entries(workingState.panels)) { workingState.panels[key] = { ...panel }; - // Find the references for this panel - const prefix = getPanelStatePrefix(panel); - - const filteredReferences = references - .filter((reference) => reference.name.indexOf(prefix) === 0) - .map((reference) => ({ ...reference, name: reference.name.replace(prefix, '') })); - + const filteredReferences = getReferencesForPanelId(key, references); const panelReferences = filteredReferences.length === 0 ? references : filteredReferences; - // Inject dashboard references back in + /** + * Inject saved object ID back into the explicit input. + * + * TODO move this logic into the persistable state service inject method for each panel type + * that could be by value or by reference + */ if (panel.panelRefName !== undefined) { const matchingReference = panelReferences.find( (reference) => reference.name === panel.panelRefName @@ -116,15 +129,18 @@ export const createExtract = ( workingState.panels = { ...workingState.panels }; // Run every panel through the state service to get the nested references - for (const [key, panel] of Object.entries(workingState.panels)) { - const prefix = getPanelStatePrefix(panel); - - // If the panel is a saved object, then we will make the reference for that saved object and change the explicit input + for (const [id, panel] of Object.entries(workingState.panels)) { + /** + * Extract saved object ID reference from the explicit input. + * + * TODO move this logic into the persistable state service extract method for each panel type + * that could be by value or by reference. + */ if (panel.explicitInput.savedObjectId) { - panel.panelRefName = `panel_${key}`; + panel.panelRefName = `panel_${id}`; references.push({ - name: `${prefix}panel_${key}`, + name: `${id}:panel_${id}`, type: panel.type, id: panel.explicitInput.savedObjectId as string, }); @@ -137,18 +153,10 @@ export const createExtract = ( type: panel.type, }); - // We're going to prefix the names of the references so that we don't end up with dupes (from visualizations for instance) - const prefixedReferences = panelReferences - .filter((reference) => reference.type !== 'tag') // panel references should never contain tags. If they do, they must be removed - .map((reference) => ({ - ...reference, - name: `${prefix}${reference.name}`, - })); - - references.push(...prefixedReferences); + references.push(...prefixReferencesFromPanel(id, panelReferences)); const { type, ...restOfState } = panelState; - workingState.panels[key].explicitInput = restOfState as EmbeddableInput; + workingState.panels[id].explicitInput = restOfState as EmbeddableInput; } } diff --git a/src/plugins/dashboard/public/dashboard_container/component/grid/dashboard_grid_item.tsx b/src/plugins/dashboard/public/dashboard_container/component/grid/dashboard_grid_item.tsx index 164555b92a176..948b9f7bc6332 100644 --- a/src/plugins/dashboard/public/dashboard_container/component/grid/dashboard_grid_item.tsx +++ b/src/plugins/dashboard/public/dashboard_container/component/grid/dashboard_grid_item.tsx @@ -6,20 +6,19 @@ * Side Public License, v 1. */ -import React, { useState, useRef, useEffect, useLayoutEffect, useMemo } from 'react'; import { EuiLoadingChart } from '@elastic/eui'; -import classNames from 'classnames'; - -import { PhaseEvent } from '@kbn/presentation-publishing'; +import { css } from '@emotion/react'; import { - ReactEmbeddableRenderer, EmbeddablePanel, reactEmbeddableRegistryHasKey, + ReactEmbeddableRenderer, ViewMode, } from '@kbn/embeddable-plugin/public'; - -import { css } from '@emotion/react'; +import { PhaseEvent } from '@kbn/presentation-publishing'; +import classNames from 'classnames'; +import React, { useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react'; import { DashboardPanelState } from '../../../../common'; +import { getReferencesForPanelId } from '../../../../common/dashboard_container/persistable_state/dashboard_container_references'; import { pluginServices } from '../../../services/plugin_services'; import { useDashboardContainer } from '../../embeddable/dashboard_container'; @@ -101,17 +100,19 @@ export const Item = React.forwardRef( : css``; const renderedEmbeddable = useMemo(() => { + const references = getReferencesForPanelId(id, container.savedObjectReferences); + // render React embeddable if (reactEmbeddableRegistryHasKey(type)) { return ( ); } + // render legacy embeddable return ( ( ); }, [container, id, index, onPanelStatusChange, type, panel]); - // render legacy embeddable - return (
=> { +): Promise<{ panels: DashboardContainerInput['panels']; references: Reference[] }> => { + const references: Reference[] = []; const reactEmbeddableSavePromises: Array< Promise<{ serializedState: SerializedPanelState; uuid: string }> > = []; @@ -51,8 +56,9 @@ const serializeAllPanelState = async ( const saveResults = await Promise.all(reactEmbeddableSavePromises); for (const { serializedState, uuid } of saveResults) { panels[uuid].explicitInput = { ...serializedState.rawState, id: uuid }; + references.push(...prefixReferencesFromPanel(uuid, serializedState.references ?? [])); } - return panels; + return { panels, references }; }; export function runSaveAs(this: DashboardContainer) { @@ -112,7 +118,7 @@ export function runSaveAs(this: DashboardContainer) { // do not save if title is duplicate and is unconfirmed return {}; } - const nextPanels = await serializeAllPanelState(this); + const { panels: nextPanels, references } = await serializeAllPanelState(this); const dashboardStateToSave: DashboardContainerInput = { ...currentState, panels: nextPanels, @@ -127,6 +133,7 @@ export function runSaveAs(this: DashboardContainer) { const beforeAddTime = window.performance.now(); const saveResult = await saveDashboardState({ + panelReferences: references, currentState: stateToSave, saveOptions, lastSavedId, @@ -145,12 +152,13 @@ export function runSaveAs(this: DashboardContainer) { batch(() => { this.dispatch.setStateFromSaveModal(stateFromSaveModal); this.dispatch.setLastSavedInput(dashboardStateToSave); - this.lastSavedState.next(); if (this.controlGroup && persistableControlGroupInput) { this.controlGroup.dispatch.setLastSavedInput(persistableControlGroupInput); } }); } + this.savedObjectReferences = saveResult.references ?? []; + this.lastSavedState.next(); resolve(saveResult); return saveResult; }; @@ -186,7 +194,7 @@ export async function runQuickSave(this: DashboardContainer) { if (managed) return; - const nextPanels = await serializeAllPanelState(this); + const { panels: nextPanels, references } = await serializeAllPanelState(this); const dashboardStateToSave: DashboardContainerInput = { ...currentState, panels: nextPanels }; let stateToSave: SavedDashboardInput = dashboardStateToSave; let persistableControlGroupInput: PersistableControlGroupInput | undefined; @@ -196,11 +204,13 @@ export async function runQuickSave(this: DashboardContainer) { } const saveResult = await saveDashboardState({ - lastSavedId, + panelReferences: references, currentState: stateToSave, saveOptions: {}, + lastSavedId, }); + this.savedObjectReferences = saveResult.references ?? []; this.dispatch.setLastSavedInput(dashboardStateToSave); this.lastSavedState.next(); if (this.controlGroup && persistableControlGroupInput) { @@ -279,6 +289,7 @@ export async function runClone(this: DashboardContainer) { title: newTitle, }, }); + this.savedObjectReferences = saveResult.references ?? []; resolve(saveResult); return saveResult.id ? { diff --git a/src/plugins/dashboard/public/dashboard_container/embeddable/create/create_dashboard.ts b/src/plugins/dashboard/public/dashboard_container/embeddable/create/create_dashboard.ts index d7d34fa42f078..e23c7cdb09846 100644 --- a/src/plugins/dashboard/public/dashboard_container/embeddable/create/create_dashboard.ts +++ b/src/plugins/dashboard/public/dashboard_container/embeddable/create/create_dashboard.ts @@ -239,6 +239,13 @@ export const initializeDashboard = async ({ description: initialDashboardInput.title, }; + // -------------------------------------------------------------------------------------- + // Track references + // -------------------------------------------------------------------------------------- + untilDashboardReady().then((dashboard) => { + dashboard.savedObjectReferences = loadDashboardReturn?.references; + }); + // -------------------------------------------------------------------------------------- // Set up unified search integration. // -------------------------------------------------------------------------------------- diff --git a/src/plugins/dashboard/public/dashboard_container/embeddable/dashboard_container.tsx b/src/plugins/dashboard/public/dashboard_container/embeddable/dashboard_container.tsx index e58e8597500dd..46885ecc7c0a5 100644 --- a/src/plugins/dashboard/public/dashboard_container/embeddable/dashboard_container.tsx +++ b/src/plugins/dashboard/public/dashboard_container/embeddable/dashboard_container.tsx @@ -6,14 +6,8 @@ * Side Public License, v 1. */ -import { v4 } from 'uuid'; -import { omit } from 'lodash'; -import React, { createContext, useContext } from 'react'; -import ReactDOM from 'react-dom'; -import { batch } from 'react-redux'; -import { BehaviorSubject, Subject, Subscription } from 'rxjs'; -import { map, distinctUntilChanged } from 'rxjs/operators'; -import deepEqual from 'fast-deep-equal'; +import { METRIC_TYPE } from '@kbn/analytics'; +import { Reference } from '@kbn/content-management-utils'; import type { ControlGroupContainer } from '@kbn/controls-plugin/public'; import type { KibanaExecutionContext, OverlayRef } from '@kbn/core/public'; import { RefreshInterval } from '@kbn/data-plugin/public'; @@ -21,9 +15,9 @@ import type { DataView } from '@kbn/data-views-plugin/public'; import { reportPerformanceMetricEvent } from '@kbn/ebt-tools'; import { Container, + DefaultEmbeddableApi, EmbeddableFactoryNotFoundError, isExplicitInputWithAttributes, - DefaultEmbeddableApi, PanelNotFoundError, ReactEmbeddableParentContext, reactEmbeddableRegistryHasKey, @@ -33,17 +27,25 @@ import { type EmbeddableOutput, type IEmbeddable, } from '@kbn/embeddable-plugin/public'; -import { METRIC_TYPE } from '@kbn/analytics'; -import { I18nProvider } from '@kbn/i18n-react'; import type { Filter, Query, TimeRange } from '@kbn/es-query'; +import { I18nProvider } from '@kbn/i18n-react'; import { KibanaThemeProvider } from '@kbn/kibana-react-plugin/public'; import { PanelPackage } from '@kbn/presentation-containers'; import { ReduxEmbeddableTools, ReduxToolsPackage } from '@kbn/presentation-util-plugin/public'; import { LocatorPublic } from '@kbn/share-plugin/common'; import { ExitFullScreenButtonKibanaProvider } from '@kbn/shared-ux-button-exit-full-screen'; - +import deepEqual from 'fast-deep-equal'; +import { omit } from 'lodash'; +import React, { createContext, useContext } from 'react'; +import ReactDOM from 'react-dom'; +import { batch } from 'react-redux'; +import { BehaviorSubject, Subject, Subscription } from 'rxjs'; +import { distinctUntilChanged, map } from 'rxjs/operators'; +import { v4 } from 'uuid'; import { DashboardLocatorParams, DASHBOARD_CONTAINER_TYPE } from '../..'; import { DashboardContainerInput, DashboardPanelState } from '../../../common'; +import { getReferencesForPanelId } from '../../../common/dashboard_container/persistable_state/dashboard_container_references'; +import { dashboardReplacePanelActionStrings } from '../../dashboard_actions/_dashboard_actions_strings'; import { DASHBOARD_APP_ID, DASHBOARD_LOADED_EVENT, @@ -55,6 +57,7 @@ import { DashboardAnalyticsService } from '../../services/analytics/types'; import { DashboardCapabilitiesService } from '../../services/dashboard_capabilities/types'; import { pluginServices } from '../../services/plugin_services'; import { placePanel } from '../component/panel_placement'; +import { panelPlacementStrategies } from '../component/panel_placement/place_new_panel_strategies'; import { DashboardViewport } from '../component/viewport/dashboard_viewport'; import { DashboardExternallyAccessibleApi } from '../external_api/dashboard_api'; import { dashboardContainerReducers } from '../state/dashboard_container_reducers'; @@ -80,8 +83,6 @@ import { dashboardTypeDisplayLowercase, dashboardTypeDisplayName, } from './dashboard_container_factory'; -import { dashboardReplacePanelActionStrings } from '../../dashboard_actions/_dashboard_actions_strings'; -import { panelPlacementStrategies } from '../component/panel_placement/place_new_panel_strategies'; export interface InheritedChildInput { filters: Filter[]; @@ -160,6 +161,7 @@ export class DashboardContainer // new embeddable framework public reactEmbeddableChildren: BehaviorSubject<{ [key: string]: DefaultEmbeddableApi }> = new BehaviorSubject<{ [key: string]: DefaultEmbeddableApi }>({}); + public savedObjectReferences: Reference[] = []; constructor( initialInput: DashboardContainerInput, @@ -736,8 +738,8 @@ export class DashboardContainer } = this.getState(); const panel: DashboardPanelState | undefined = panels[childId]; - // TODO Embeddable refactor. References here - return { rawState: panel?.explicitInput, version: panel?.version, references: [] }; + const references = getReferencesForPanelId(childId, this.savedObjectReferences); + return { rawState: panel?.explicitInput, version: panel?.version, references }; }; public removePanel(id: string) { diff --git a/src/plugins/dashboard/public/services/dashboard_content_management/dashboard_content_management_service.ts b/src/plugins/dashboard/public/services/dashboard_content_management/dashboard_content_management_service.ts index 0cac2dff75e32..2b2835e2a2420 100644 --- a/src/plugins/dashboard/public/services/dashboard_content_management/dashboard_content_management_service.ts +++ b/src/plugins/dashboard/public/services/dashboard_content_management/dashboard_content_management_service.ts @@ -56,7 +56,7 @@ export const dashboardContentManagementServiceFactory: DashboardContentManagemen contentManagement, savedObjectsTagging, }), - saveDashboardState: ({ currentState, saveOptions, lastSavedId }) => + saveDashboardState: ({ currentState, saveOptions, lastSavedId, panelReferences }) => saveDashboardState({ data, embeddable, @@ -64,6 +64,7 @@ export const dashboardContentManagementServiceFactory: DashboardContentManagemen lastSavedId, currentState, notifications, + panelReferences, dashboardBackup, contentManagement, initializerContext, diff --git a/src/plugins/dashboard/public/services/dashboard_content_management/lib/load_dashboard_state.ts b/src/plugins/dashboard/public/services/dashboard_content_management/lib/load_dashboard_state.ts index 5b748c740cea4..cabd1542efbb2 100644 --- a/src/plugins/dashboard/public/services/dashboard_content_management/lib/load_dashboard_state.ts +++ b/src/plugins/dashboard/public/services/dashboard_content_management/lib/load_dashboard_state.ts @@ -57,7 +57,12 @@ export const loadDashboardState = async ({ * This is a newly created dashboard, so there is no saved object state to load. */ if (!savedObjectId) { - return { dashboardInput: newDashboardState, dashboardFound: true, newDashboardCreated: true }; + return { + dashboardInput: newDashboardState, + dashboardFound: true, + newDashboardCreated: true, + references: [], + }; } /** @@ -97,6 +102,7 @@ export const loadDashboardState = async ({ dashboardInput: newDashboardState, dashboardFound: false, dashboardId: savedObjectId, + references: [], }; } @@ -192,6 +198,7 @@ export const loadDashboardState = async ({ return { managed, + references, resolveMeta, dashboardInput, anyMigrationRun, diff --git a/src/plugins/dashboard/public/services/dashboard_content_management/lib/save_dashboard_state.ts b/src/plugins/dashboard/public/services/dashboard_content_management/lib/save_dashboard_state.ts index ec7e1fd2c097b..a2b0c3f8609b2 100644 --- a/src/plugins/dashboard/public/services/dashboard_content_management/lib/save_dashboard_state.ts +++ b/src/plugins/dashboard/public/services/dashboard_content_management/lib/save_dashboard_state.ts @@ -73,6 +73,7 @@ export const saveDashboardState = async ({ lastSavedId, saveOptions, currentState, + panelReferences, dashboardBackup, contentManagement, savedObjectsTagging, @@ -180,6 +181,8 @@ export const saveDashboardState = async ({ ? savedObjectsTagging.updateTagsReferences(dashboardReferences, tags) : dashboardReferences; + const allReferences = [...references, ...(panelReferences ?? [])]; + /** * Save the saved object using the content management */ @@ -191,7 +194,11 @@ export const saveDashboardState = async ({ >({ contentTypeId: DASHBOARD_CONTENT_ID, data: attributes, - options: { id: idToSaveTo, references, overwrite: true }, + options: { + id: idToSaveTo, + references: allReferences, + overwrite: true, + }, }); const newId = result.item.id; @@ -207,12 +214,12 @@ export const saveDashboardState = async ({ */ if (newId !== lastSavedId) { dashboardBackup.clearState(lastSavedId); - return { redirectRequired: true, id: newId }; + return { redirectRequired: true, id: newId, references: allReferences }; } else { dashboardContentManagementCache.deleteDashboard(newId); // something changed in an existing dashboard, so delete it from the cache so that it can be re-fetched } } - return { id: newId }; + return { id: newId, references: allReferences }; } catch (error) { toasts.addDanger({ title: dashboardSaveToastStrings.getFailureString(currentState.title, error.message), diff --git a/src/plugins/dashboard/public/services/dashboard_content_management/types.ts b/src/plugins/dashboard/public/services/dashboard_content_management/types.ts index 7e79e01df56ad..900d8e6d09972 100644 --- a/src/plugins/dashboard/public/services/dashboard_content_management/types.ts +++ b/src/plugins/dashboard/public/services/dashboard_content_management/types.ts @@ -6,6 +6,7 @@ * Side Public License, v 1. */ +import { Reference } from '@kbn/content-management-utils'; import { PersistableControlGroupInput } from '@kbn/controls-plugin/common'; import { SavedObjectSaveOpts } from '@kbn/saved-objects-plugin/public'; @@ -74,6 +75,12 @@ export interface LoadDashboardReturn { resolveMeta?: DashboardResolveMeta; dashboardInput: SavedDashboardInput; anyMigrationRun?: boolean; + + /** + * Raw references returned directly from the Dashboard saved object. These + * should be provided to the React Embeddable children on deserialize. + */ + references: Reference[]; } /** @@ -84,12 +91,14 @@ export type SavedDashboardSaveOpts = SavedObjectSaveOpts & { saveAsCopy?: boolea export interface SaveDashboardProps { currentState: SavedDashboardInput; saveOptions: SavedDashboardSaveOpts; + panelReferences?: Reference[]; lastSavedId?: string; } export interface SaveDashboardReturn { id?: string; error?: string; + references?: Reference[]; redirectRequired?: boolean; } From e81568ca919767a938334d65ff2eea4c1f7a2561 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 12 Feb 2024 13:28:55 -0700 Subject: [PATCH 24/83] Update dependency @elastic/charts to v63.1.0 (main) (#176527) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [![Mend Renovate](https://app.renovatebot.com/images/banner.svg)](https://renovatebot.com) This PR contains the following updates: | Package | Change | Age | Adoption | Passing | Confidence | |---|---|---|---|---|---| | [@elastic/charts](https://togithub.com/elastic/elastic-charts) | [`63.0.0` -> `63.1.0`](https://renovatebot.com/diffs/npm/@elastic%2fcharts/63.0.0/63.1.0) | [![age](https://developer.mend.io/api/mc/badges/age/npm/@elastic%2fcharts/63.1.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![adoption](https://developer.mend.io/api/mc/badges/adoption/npm/@elastic%2fcharts/63.1.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![passing](https://developer.mend.io/api/mc/badges/compatibility/npm/@elastic%2fcharts/63.0.0/63.1.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@elastic%2fcharts/63.0.0/63.1.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) | --- ### Release Notes
elastic/elastic-charts (@​elastic/charts) ### [`v63.1.0`](https://togithub.com/elastic/elastic-charts/blob/HEAD/CHANGELOG.md#6310-2024-01-29) [Compare Source](https://togithub.com/elastic/elastic-charts/compare/v63.0.0...v63.1.0) ##### Bug Fixes - **deps:** update dependency [@​elastic/eui](https://togithub.com/elastic/eui) to ^92.1.1 ([#​2315](https://togithub.com/elastic/elastic-charts/issues/2315)) ([f4e4fae](https://togithub.com/elastic/elastic-charts/commit/f4e4fae42e5dcc1b882f0c5126c66d3c96cd2fcc)) - **deps:** update dependency [@​playwright/test](https://togithub.com/playwright/test) to ^1.41.1 ([#​2316](https://togithub.com/elastic/elastic-charts/issues/2316)) ([e2ab527](https://togithub.com/elastic/elastic-charts/commit/e2ab52791baef9da628930d3e8d738ce3772c34a)) - **styles:** isolated point style overrides ([#​2278](https://togithub.com/elastic/elastic-charts/issues/2278)) ([3fb1df2](https://togithub.com/elastic/elastic-charts/commit/3fb1df21d08c441c84705bd3d5984fc07caa11be)) ##### Features - **metric:** custom slot to render contents in gap ([#​2303](https://togithub.com/elastic/elastic-charts/issues/2303)) ([3256c8c](https://togithub.com/elastic/elastic-charts/commit/3256c8ca14d180e4d7a483811a37612aca2691ce)) ##### Performance Improvements - **tooltip:** improve placement logic ([#​2310](https://togithub.com/elastic/elastic-charts/issues/2310)) ([cac5f49](https://togithub.com/elastic/elastic-charts/commit/cac5f4908a54374d114d7a11e66d648979013039))
--- ### Configuration 📅 **Schedule**: Branch creation - At any time (no schedule defined), Automerge - At any time (no schedule defined). 🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied. ♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox. 🔕 **Ignore**: Close this PR and you won't be reminded about this update again. --- - [ ] If you want to rebase/retry this PR, check this box --- This PR has been generated by [Mend Renovate](https://www.mend.io/free-developer-tools/renovate/). View repository job log [here](https://developer.mend.io/github/elastic/kibana). Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index a0bc855415b9d..191de4ea372fd 100644 --- a/package.json +++ b/package.json @@ -101,7 +101,7 @@ "@dnd-kit/utilities": "^2.0.0", "@elastic/apm-rum": "^5.16.0", "@elastic/apm-rum-react": "^2.0.2", - "@elastic/charts": "63.0.0", + "@elastic/charts": "63.1.0", "@elastic/datemath": "5.0.3", "@elastic/elasticsearch": "npm:@elastic/elasticsearch-canary@8.9.1-canary.1", "@elastic/ems-client": "8.5.1", diff --git a/yarn.lock b/yarn.lock index fbd3e8ca5979a..973c748f3b781 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1658,10 +1658,10 @@ dependencies: object-hash "^1.3.0" -"@elastic/charts@63.0.0": - version "63.0.0" - resolved "https://registry.yarnpkg.com/@elastic/charts/-/charts-63.0.0.tgz#c7f54cd60a1a59a28b5654886392e05a10fd67b8" - integrity sha512-nvLg/qFJXYuKOdTDYj3iuwJ/X4zhkHdIB91yezd7fo+YvpBRiAUzJfc6Dpy6M5JkmGwx7Dq8zjGt6mO8ngOhog== +"@elastic/charts@63.1.0": + version "63.1.0" + resolved "https://registry.yarnpkg.com/@elastic/charts/-/charts-63.1.0.tgz#6348ffe01d6e77ddd150382b57515134f76293b3" + integrity sha512-UdzsErplc5z2cQxRY7N4kXZXRfb0pdDdsC7V4ag2WIlDiYDpygB3iThb83sG99E9KtOqIkHPE5nyDmWI6GwfOg== dependencies: "@popperjs/core" "^2.11.8" bezier-easing "^2.1.0" From f76b0bbc77583d5190eb0f00c4bb59c313c2b010 Mon Sep 17 00:00:00 2001 From: Paul Tavares <56442535+paul-tavares@users.noreply.github.com> Date: Mon, 12 Feb 2024 15:29:25 -0500 Subject: [PATCH 25/83] [8.13][SecuritySolution][Endpoint] Retrieve (API) agent status for SentinelOne agents and display it in the UI (#176440) ## Summary - New internal API that returns the agent status for one of the supported agent types (currently `endpoint` or `sentinel_one`) - route: `/internal/api/endpoint/agent_status` - Supported query params: `agentType` and `agentIds` - The response will include a record for each agent ID that was requested. Each record contains a `found: boolean` that indicates whether the agent id information was found or not (could be the case when agents are un-enrolled) - NOTE: currently, this API will return only statuses for SentinelOne in 8.13. In the next release, it will be expanded with a framework that enables it to return status for any supported `agentType` - UI changes to show Host/Agent info. for SentinelOne using new api __________ API Request: `GET /internal/api/endpoint/agent_status?agentType=sentinel_one&agentIds=1111&agentIds=2222agentIds=invalid-id` API Response: ```json5 { "data": { "1111": { "agentType": "sentinel_one", "id": "1111", "found": true, "status": "healthy", "isolated": true, // <<<< Note host is isolated "lastSeen": "2024-02-07T19:03:31.287300Z", "pendingActions": { "execute": 0, "upload": 0, "unisolate": 0, "isolate": 0, "get-file": 0, "kill-process": 0, "suspend-process": 0, "running-processes": 0 } }, "2222": { "agentType": "sentinel_one", "id": "2222", "found": true, "status": "healthy", "isolated": false, "lastSeen": "2024-02-07T14:45:02.830739Z", "pendingActions": { "execute": 0, "upload": 0, "unisolate": 0, "isolate": 1, // <<< Note: host is Isolating "get-file": 0, "kill-process": 0, "suspend-process": 0, "running-processes": 0 } } }, { "invalid-id": { "agentType": "sentinel_one", "id": "invalid", "found": false, "status": "unenrolled", "isolated": false, "lastSeen": "", "pendingActions": { "execute": 0, "upload": 0, "unisolate": 0, "isolate": 0, "get-file": 0, "kill-process": 0, "suspend-process": 0, "running-processes": 0 } } } ``` Co-authored-by: Ashokaditya Co-authored-by: Ash <1849116+ashokaditya@users.noreply.github.com> --- .../api/endpoint/actions/common/base.ts | 17 +- .../common/api/endpoint/actions/list_route.ts | 13 +- .../agent/get_agent_status_route.test.ts | 54 +++++ .../endpoint/agent/get_agent_status_route.ts | 41 ++++ .../common/endpoint/constants.ts | 3 + .../common/endpoint/types/agents.ts | 29 +++ .../common/endpoint/types/index.ts | 1 + .../use_responder_action_data.ts | 1 - .../sentinel_one_agent_status.tsx | 55 +++-- .../use_host_isolation_action.tsx | 43 ++-- .../use_sentinelone_host_isolation.tsx | 72 ++++--- .../highlighted_fields_cell.test.tsx | 7 +- .../sentinel_one/header_sentinel_one_info.tsx | 8 +- .../hooks/use_with_show_responder.tsx | 2 - .../routes/actions/response_actions.test.ts | 2 +- .../routes/agent/agent_status_handler.test.ts | 131 ++++++++++++ .../routes/agent/agent_status_handler.ts | 107 ++++++++++ .../server/endpoint/routes/agent/index.ts | 17 ++ .../get_response_actions_client.test.ts | 2 +- .../actions/clients/sentinelone/mock.ts | 177 ----------------- .../actions/clients/sentinelone/mocks.ts | 188 ++++++++++++++++++ .../sentinel_one_actions_client.test.ts | 4 +- .../services/agent/agent_status.test.ts | 184 +++++++++++++++++ .../endpoint/services/agent/agent_status.ts | 138 +++++++++++++ .../security_solution/server/plugin.ts | 2 + .../common/sentinelone/types.ts | 2 - 26 files changed, 1011 insertions(+), 289 deletions(-) create mode 100644 x-pack/plugins/security_solution/common/api/endpoint/agent/get_agent_status_route.test.ts create mode 100644 x-pack/plugins/security_solution/common/api/endpoint/agent/get_agent_status_route.ts create mode 100644 x-pack/plugins/security_solution/common/endpoint/types/agents.ts create mode 100644 x-pack/plugins/security_solution/server/endpoint/routes/agent/agent_status_handler.test.ts create mode 100644 x-pack/plugins/security_solution/server/endpoint/routes/agent/agent_status_handler.ts create mode 100644 x-pack/plugins/security_solution/server/endpoint/routes/agent/index.ts delete mode 100644 x-pack/plugins/security_solution/server/endpoint/services/actions/clients/sentinelone/mock.ts create mode 100644 x-pack/plugins/security_solution/server/endpoint/services/actions/clients/sentinelone/mocks.ts create mode 100644 x-pack/plugins/security_solution/server/endpoint/services/agent/agent_status.test.ts create mode 100644 x-pack/plugins/security_solution/server/endpoint/services/agent/agent_status.ts diff --git a/x-pack/plugins/security_solution/common/api/endpoint/actions/common/base.ts b/x-pack/plugins/security_solution/common/api/endpoint/actions/common/base.ts index 755361902eaa1..a5e5c060e7303 100644 --- a/x-pack/plugins/security_solution/common/api/endpoint/actions/common/base.ts +++ b/x-pack/plugins/security_solution/common/api/endpoint/actions/common/base.ts @@ -9,6 +9,21 @@ import type { TypeOf } from '@kbn/config-schema'; import { schema } from '@kbn/config-schema'; import { RESPONSE_ACTION_AGENT_TYPE } from '../../../../endpoint/service/response_actions/constants'; +export const AgentTypeSchemaLiteral = RESPONSE_ACTION_AGENT_TYPE.map((agentType) => + schema.literal(agentType) +); + +export const agentTypesSchema = { + schema: schema.oneOf( + // @ts-expect-error TS2769: No overload matches this call + AgentTypeSchemaLiteral + ), + options: { + minSize: 1, + maxSize: RESPONSE_ACTION_AGENT_TYPE.length, + }, +}; + export const BaseActionRequestSchema = { /** A list of endpoint IDs whose hosts will be isolated (Fleet Agent IDs will be retrieved for these) */ endpoint_ids: schema.arrayOf(schema.string({ minLength: 1 }), { @@ -46,7 +61,7 @@ export const BaseActionRequestSchema = { agent_type: schema.maybe( schema.oneOf( // @ts-expect-error TS2769: No overload matches this call - RESPONSE_ACTION_AGENT_TYPE.map((agentType) => schema.literal(agentType)), + AgentTypeSchemaLiteral, { defaultValue: 'endpoint' } ) ), diff --git a/x-pack/plugins/security_solution/common/api/endpoint/actions/list_route.ts b/x-pack/plugins/security_solution/common/api/endpoint/actions/list_route.ts index 3fe188198bc4b..720764d9cd03c 100644 --- a/x-pack/plugins/security_solution/common/api/endpoint/actions/list_route.ts +++ b/x-pack/plugins/security_solution/common/api/endpoint/actions/list_route.ts @@ -9,12 +9,12 @@ import type { TypeOf } from '@kbn/config-schema'; import { schema } from '@kbn/config-schema'; import { - RESPONSE_ACTION_AGENT_TYPE, RESPONSE_ACTION_API_COMMANDS_NAMES, RESPONSE_ACTION_STATUS, RESPONSE_ACTION_TYPE, } from '../../../endpoint/service/response_actions/constants'; import { ENDPOINT_DEFAULT_PAGE_SIZE } from '../../../endpoint/constants'; +import { agentTypesSchema } from './common/base'; const commandsSchema = schema.oneOf( // @ts-expect-error TS2769: No overload matches this call @@ -33,17 +33,6 @@ const actionTypesSchema = { options: { minSize: 1, maxSize: RESPONSE_ACTION_TYPE.length }, }; -const agentTypesSchema = { - schema: schema.oneOf( - // @ts-expect-error TS2769: No overload matches this call - RESPONSE_ACTION_AGENT_TYPE.map((agentType) => schema.literal(agentType)) - ), - options: { - minSize: 1, - maxSize: RESPONSE_ACTION_AGENT_TYPE.length, - }, -}; - export const EndpointActionListRequestSchema = { query: schema.object({ agentIds: schema.maybe( diff --git a/x-pack/plugins/security_solution/common/api/endpoint/agent/get_agent_status_route.test.ts b/x-pack/plugins/security_solution/common/api/endpoint/agent/get_agent_status_route.test.ts new file mode 100644 index 0000000000000..f9362aa6847a9 --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/endpoint/agent/get_agent_status_route.test.ts @@ -0,0 +1,54 @@ +/* + * 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 { EndpointAgentStatusRequestSchema } from './get_agent_status_route'; + +describe('Agent status api route schema', () => { + it('should optionally accept `agentType`', () => { + expect(() => + EndpointAgentStatusRequestSchema.query.validate({ + agentIds: '1', + }) + ).not.toThrow(); + }); + + it('should error if unknown `agentType` is used', () => { + expect(() => + EndpointAgentStatusRequestSchema.query.validate({ + agentIds: '1', + agentType: 'foo', + }) + ).toThrow(/\[agentType\]: types that failed validation/); + }); + + it.each([ + ['string with spaces only', { agentIds: ' ' }], + ['empty string', { agentIds: '' }], + ['array with empty strings', { agentIds: [' ', ''] }], + ['agentIds not defined', {}], + ['agentIds is empty array', { agentIds: [] }], + [ + 'more than 50 agentIds', + { agentIds: Array.from({ length: 51 }, () => Math.random().toString(32)) }, + ], + ])('should error if %s are used for `agentIds`', (_, validateOptions) => { + expect(() => EndpointAgentStatusRequestSchema.query.validate(validateOptions)).toThrow( + /\[agentIds\]:/ + ); + }); + + it.each([ + ['single string value', 'one'], + ['array of strings', ['one', 'two']], + ])('should accept %s of `agentIds`', (_, agentIdsValue) => { + expect(() => + EndpointAgentStatusRequestSchema.query.validate({ + agentIds: agentIdsValue, + }) + ).not.toThrow(); + }); +}); diff --git a/x-pack/plugins/security_solution/common/api/endpoint/agent/get_agent_status_route.ts b/x-pack/plugins/security_solution/common/api/endpoint/agent/get_agent_status_route.ts new file mode 100644 index 0000000000000..20eea48571c49 --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/endpoint/agent/get_agent_status_route.ts @@ -0,0 +1,41 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { schema, type TypeOf } from '@kbn/config-schema'; +import { AgentTypeSchemaLiteral } from '..'; + +const AgentStatusAgentIdSchema = schema.string({ + minLength: 1, + validate: (id) => { + if (id.trim() === '') { + return 'actionIds can not be empty strings'; + } + }, +}); + +export const EndpointAgentStatusRequestSchema = { + query: schema.object({ + agentIds: schema.oneOf([ + schema.arrayOf(AgentStatusAgentIdSchema, { minSize: 1, maxSize: 50 }), + AgentStatusAgentIdSchema, + ]), + + agentType: schema.maybe( + schema.oneOf( + // @ts-expect-error TS2769: No overload matches this call + AgentTypeSchemaLiteral, + { + defaultValue: 'endpoint', + } + ) + ), + }), +}; + +export type EndpointAgentStatusRequestQueryParams = TypeOf< + typeof EndpointAgentStatusRequestSchema.query +>; diff --git a/x-pack/plugins/security_solution/common/endpoint/constants.ts b/x-pack/plugins/security_solution/common/endpoint/constants.ts index 892cb717e1a2c..7f41e36382137 100644 --- a/x-pack/plugins/security_solution/common/endpoint/constants.ts +++ b/x-pack/plugins/security_solution/common/endpoint/constants.ts @@ -103,6 +103,9 @@ export const ACTION_AGENT_FILE_INFO_ROUTE = `${BASE_ENDPOINT_ACTION_ROUTE}/{acti export const ACTION_AGENT_FILE_DOWNLOAD_ROUTE = `${BASE_ENDPOINT_ACTION_ROUTE}/{action_id}/file/{file_id}/download`; export const ACTION_STATE_ROUTE = `${BASE_ENDPOINT_ACTION_ROUTE}/state`; +/** Endpoint Agent Routes */ +export const ENDPOINT_AGENT_STATUS_ROUTE = `/internal${BASE_ENDPOINT_ROUTE}/agent_status`; + export const failedFleetActionErrorCode = '424'; export const ENDPOINT_DEFAULT_PAGE = 0; diff --git a/x-pack/plugins/security_solution/common/endpoint/types/agents.ts b/x-pack/plugins/security_solution/common/endpoint/types/agents.ts new file mode 100644 index 0000000000000..5394df26a3bae --- /dev/null +++ b/x-pack/plugins/security_solution/common/endpoint/types/agents.ts @@ -0,0 +1,29 @@ +/* + * 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 { HostStatus } from '.'; +import type { + ResponseActionsApiCommandNames, + ResponseActionAgentType, +} from '../service/response_actions/constants'; +import {} from '../service/response_actions/constants'; + +export interface AgentStatusInfo { + id: string; + agentType: ResponseActionAgentType; + found: boolean; + isolated: boolean; + isPendingUninstall: boolean; + isUninstalled: boolean; + lastSeen: string; // ISO date + pendingActions: Record; + status: HostStatus; +} + +export interface AgentStatusApiResponse { + data: Record; +} diff --git a/x-pack/plugins/security_solution/common/endpoint/types/index.ts b/x-pack/plugins/security_solution/common/endpoint/types/index.ts index 4887d83493f5c..f525c03ce17f7 100644 --- a/x-pack/plugins/security_solution/common/endpoint/types/index.ts +++ b/x-pack/plugins/security_solution/common/endpoint/types/index.ts @@ -13,6 +13,7 @@ export * from './actions'; export * from './os'; export * from './trusted_apps'; export * from './utility_types'; +export * from './agents'; export type { ConditionEntriesMap, ConditionEntry } from './exception_list_items'; /** diff --git a/x-pack/plugins/security_solution/public/detections/components/endpoint_responder/use_responder_action_data.ts b/x-pack/plugins/security_solution/public/detections/components/endpoint_responder/use_responder_action_data.ts index cfa004b7ce6b2..fb20548271191 100644 --- a/x-pack/plugins/security_solution/public/detections/components/endpoint_responder/use_responder_action_data.ts +++ b/x-pack/plugins/security_solution/public/detections/components/endpoint_responder/use_responder_action_data.ts @@ -138,7 +138,6 @@ export const useResponderActionData = ({ capabilities: ['isolation'], hostName: agentInfoFromAlert.host.name, platform: agentInfoFromAlert.host.os.family, - lastCheckin: agentInfoFromAlert.lastCheckin, }); } if (hostInfo) { diff --git a/x-pack/plugins/security_solution/public/detections/components/host_isolation/sentinel_one_agent_status.tsx b/x-pack/plugins/security_solution/public/detections/components/host_isolation/sentinel_one_agent_status.tsx index 3d643dffc51cc..171515aea5122 100644 --- a/x-pack/plugins/security_solution/public/detections/components/host_isolation/sentinel_one_agent_status.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/host_isolation/sentinel_one_agent_status.tsx @@ -8,29 +8,15 @@ import { EuiBadge, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; import React, { useMemo } from 'react'; import styled from 'styled-components'; -import type { SentinelOneAgent } from '@kbn/stack-connectors-plugin/common/sentinelone/types'; -import { HostStatus } from '../../../../common/endpoint/types'; import { getAgentStatusText } from '../../../common/components/endpoint/agent_status_text'; import { HOST_STATUS_TO_BADGE_COLOR } from '../../../management/pages/endpoint_hosts/view/host_constants'; -import { useSentinelOneAgentData } from './use_sentinelone_host_isolation'; +import { useGetSentinelOneAgentStatus } from './use_sentinelone_host_isolation'; import { - ISOLATING_LABEL, ISOLATED_LABEL, + ISOLATING_LABEL, RELEASING_LABEL, } from '../../../common/components/endpoint/endpoint_agent_status'; -const getSentinelOneAgentStatus = (data?: SentinelOneAgent) => { - if (!data) { - return HostStatus.UNENROLLED; - } - - if (!data?.isActive) { - return HostStatus.OFFLINE; - } - - return HostStatus.HEALTHY; -}; - export enum SENTINEL_ONE_NETWORK_STATUS { CONNECTING = 'connecting', CONNECTED = 'connected', @@ -46,25 +32,27 @@ const EuiFlexGroupStyled = styled(EuiFlexGroup)` export const SentinelOneAgentStatus = React.memo( ({ agentId, 'data-test-subj': dataTestSubj }: { agentId: string; 'data-test-subj'?: string }) => { - const { data, isFetched } = useSentinelOneAgentData({ agentId }); + const { data, isLoading, isFetched } = useGetSentinelOneAgentStatus([agentId]); + const agentStatus = data?.[`${agentId}`]; const label = useMemo(() => { - const networkStatus = data?.data?.data?.[0]?.networkStatus; + const currentNetworkStatus = agentStatus?.isolated; + const pendingActions = agentStatus?.pendingActions; - if (networkStatus === SENTINEL_ONE_NETWORK_STATUS.DISCONNECTING) { - return ISOLATING_LABEL; - } + if (pendingActions) { + if (pendingActions.isolate > 0) { + return ISOLATING_LABEL; + } - if (networkStatus === SENTINEL_ONE_NETWORK_STATUS.DISCONNECTED) { - return ISOLATED_LABEL; + if (pendingActions.unisolate > 0) { + return RELEASING_LABEL; + } } - if (networkStatus === SENTINEL_ONE_NETWORK_STATUS.CONNECTING) { - return RELEASING_LABEL; + if (currentNetworkStatus) { + return ISOLATED_LABEL; } - }, [data?.data?.data]); - - const agentStatus = useMemo(() => getSentinelOneAgentStatus(data?.data?.data?.[0]), [data]); + }, [agentStatus?.isolated, agentStatus?.pendingActions]); return ( - {isFetched ? ( - - {getAgentStatusText(agentStatus)} + {isFetched && !isLoading && agentStatus ? ( + + {getAgentStatusText(agentStatus.status)} ) : ( '-' )} - {label && ( + {isFetched && !isLoading && label && ( <>{label} diff --git a/x-pack/plugins/security_solution/public/detections/components/host_isolation/use_host_isolation_action.tsx b/x-pack/plugins/security_solution/public/detections/components/host_isolation/use_host_isolation_action.tsx index c899653a0c836..bf96225f5ca9d 100644 --- a/x-pack/plugins/security_solution/public/detections/components/host_isolation/use_host_isolation_action.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/host_isolation/use_host_isolation_action.tsx @@ -20,7 +20,7 @@ import { ISOLATE_HOST, UNISOLATE_HOST } from './translations'; import { getFieldValue } from './helpers'; import { useUserPrivileges } from '../../../common/components/user_privileges'; import type { AlertTableContextMenuItem } from '../alerts_table/types'; -import { useSentinelOneAgentData } from './use_sentinelone_host_isolation'; +import { useGetSentinelOneAgentStatus } from './use_sentinelone_host_isolation'; interface UseHostIsolationActionProps { closePopover: () => void; @@ -78,23 +78,19 @@ export const useHostIsolationAction = ({ agentId, }); - const { data: sentinelOneResponse } = useSentinelOneAgentData({ agentId: sentinelOneAgentId }); - - const sentinelOneAgentData = useMemo( - () => sentinelOneResponse?.data?.data?.[0], - [sentinelOneResponse] - ); + const { data: sentinelOneAgentData } = useGetSentinelOneAgentStatus([sentinelOneAgentId || '']); + const sentinelOneAgentStatus = sentinelOneAgentData?.[`${sentinelOneAgentId}`]; const isHostIsolated = useMemo(() => { if (sentinelOneManualHostActionsEnabled && isSentinelOneAlert) { - return sentinelOneAgentData?.networkStatus === 'disconnected'; + return sentinelOneAgentStatus?.isolated; } return isIsolated; }, [ isIsolated, isSentinelOneAlert, - sentinelOneAgentData?.networkStatus, + sentinelOneAgentStatus?.isolated, sentinelOneManualHostActionsEnabled, ]); @@ -107,8 +103,8 @@ export const useHostIsolationAction = ({ }); } - if (sentinelOneManualHostActionsEnabled && isSentinelOneAlert && sentinelOneAgentData) { - return sentinelOneAgentData.isActive; + if (sentinelOneManualHostActionsEnabled && isSentinelOneAlert && sentinelOneAgentStatus) { + return sentinelOneAgentStatus.status === 'healthy'; } return false; }, [ @@ -117,7 +113,7 @@ export const useHostIsolationAction = ({ hostOsFamily, isEndpointAlert, isSentinelOneAlert, - sentinelOneAgentData, + sentinelOneAgentStatus, sentinelOneManualHostActionsEnabled, ]); @@ -130,29 +126,34 @@ export const useHostIsolationAction = ({ } }, [closePopover, isHostIsolated, onAddIsolationStatusClick]); - const menuItemDisabled = useMemo(() => { + const isIsolationActionDisabled = useMemo(() => { if (sentinelOneManualHostActionsEnabled && isSentinelOneAlert) { return ( - !sentinelOneAgentData || - sentinelOneAgentData?.isUninstalled || - sentinelOneAgentData?.isPendingUninstall + !sentinelOneAgentStatus || + sentinelOneAgentStatus?.isUninstalled || + sentinelOneAgentStatus?.isPendingUninstall ); } return agentStatus === HostStatus.UNENROLLED; - }, [agentStatus, isSentinelOneAlert, sentinelOneAgentData, sentinelOneManualHostActionsEnabled]); + }, [ + agentStatus, + isSentinelOneAlert, + sentinelOneAgentStatus, + sentinelOneManualHostActionsEnabled, + ]); const menuItems = useMemo( () => [ { key: 'isolate-host-action-item', 'data-test-subj': 'isolate-host-action-item', - disabled: menuItemDisabled, + disabled: isIsolationActionDisabled, onClick: isolateHostHandler, name: isHostIsolated ? UNISOLATE_HOST : ISOLATE_HOST, }, ], - [isHostIsolated, isolateHostHandler, menuItemDisabled] + [isHostIsolated, isolateHostHandler, isIsolationActionDisabled] ); return useMemo(() => { @@ -164,7 +165,7 @@ export const useHostIsolationAction = ({ isSentinelOneAlert && sentinelOneManualHostActionsEnabled && sentinelOneAgentId && - sentinelOneAgentData && + sentinelOneAgentStatus && hasActionsAllPrivileges ) { return menuItems; @@ -191,7 +192,7 @@ export const useHostIsolationAction = ({ isSentinelOneAlert, loadingHostIsolationStatus, menuItems, - sentinelOneAgentData, + sentinelOneAgentStatus, sentinelOneAgentId, sentinelOneManualHostActionsEnabled, ]); diff --git a/x-pack/plugins/security_solution/public/detections/components/host_isolation/use_sentinelone_host_isolation.tsx b/x-pack/plugins/security_solution/public/detections/components/host_isolation/use_sentinelone_host_isolation.tsx index f0fa47edf9c06..a8c9ea91a6d9e 100644 --- a/x-pack/plugins/security_solution/public/detections/components/host_isolation/use_sentinelone_host_isolation.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/host_isolation/use_sentinelone_host_isolation.tsx @@ -6,45 +6,51 @@ */ import { isEmpty } from 'lodash'; -import type { - SentinelOneGetAgentsParams, - SentinelOneGetAgentsResponse, -} from '@kbn/stack-connectors-plugin/common/sentinelone/types'; -import { SENTINELONE_CONNECTOR_ID, SUB_ACTION } from '@kbn/stack-connectors-plugin/public/common'; +import type { SentinelOneGetAgentsResponse } from '@kbn/stack-connectors-plugin/common/sentinelone/types'; +import type { UseQueryOptions, UseQueryResult } from '@tanstack/react-query'; +import { useQuery } from '@tanstack/react-query'; +import type { IHttpFetchError } from '@kbn/core-http-browser'; +import type { ActionTypeExecutorResult } from '@kbn/actions-plugin/common'; +import { ENDPOINT_AGENT_STATUS_ROUTE } from '../../../../common/endpoint/constants'; +import type { AgentStatusApiResponse } from '../../../../common/endpoint/types'; +import { useHttp } from '../../../common/lib/kibana'; import { useIsExperimentalFeatureEnabled } from '../../../common/hooks/use_experimental_features'; -import { useSubAction } from '../../../timelines/components/side_panel/event_details/flyout/use_sub_action'; -import { useLoadConnectors } from '../../../common/components/response_actions/use_load_connectors'; -import { SENTINEL_ONE_NETWORK_STATUS } from './sentinel_one_agent_status'; -/** - * Using SentinelOne connector to pull agent's data from the SentinelOne API. If the agentId is in the transition state - * (isolating/releasing) it will keep pulling the state until it finalizes the action - * @param agentId - */ -export const useSentinelOneAgentData = ({ agentId }: { agentId?: string }) => { +interface ErrorType { + statusCode: number; + message: string; + meta: ActionTypeExecutorResult; +} + +export const useGetSentinelOneAgentStatus = ( + agentIds: string[], + options: UseQueryOptions> = {} +): UseQueryResult> => { const sentinelOneManualHostActionsEnabled = useIsExperimentalFeatureEnabled( 'sentinelOneManualHostActionsEnabled' ); - const { data: connector } = useLoadConnectors({ actionTypeId: SENTINELONE_CONNECTOR_ID }); - return useSubAction({ - connectorId: connector?.[0]?.id, - subAction: SUB_ACTION.GET_AGENTS, - subActionParams: { - uuid: agentId, - }, - disabled: !sentinelOneManualHostActionsEnabled || isEmpty(agentId), - // @ts-expect-error update types - refetchInterval: (lastResponse: { data: SentinelOneGetAgentsResponse }) => { - const networkStatus = lastResponse?.data?.data?.[0] - .networkStatus as SENTINEL_ONE_NETWORK_STATUS; + const http = useHttp(); - return [ - SENTINEL_ONE_NETWORK_STATUS.CONNECTING, - SENTINEL_ONE_NETWORK_STATUS.DISCONNECTING, - ].includes(networkStatus) - ? 5000 - : false; - }, + return useQuery>({ + queryKey: ['get-agent-status', agentIds], + ...options, + enabled: !( + sentinelOneManualHostActionsEnabled && + isEmpty(agentIds.filter((agentId) => agentId.trim().length)) + ), + // TODO: update this to use a function instead of a number + refetchInterval: 2000, + queryFn: () => + http + .get<{ data: AgentStatusApiResponse['data'] }>(ENDPOINT_AGENT_STATUS_ROUTE, { + version: '1', + query: { + agentIds, + // 8.13 sentinel_one support via internal API + agentType: 'sentinel_one', + }, + }) + .then((response) => response.data), }); }; diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/right/components/highlighted_fields_cell.test.tsx b/x-pack/plugins/security_solution/public/flyout/document_details/right/components/highlighted_fields_cell.test.tsx index c27828f637c8a..562de8574146f 100644 --- a/x-pack/plugins/security_solution/public/flyout/document_details/right/components/highlighted_fields_cell.test.tsx +++ b/x-pack/plugins/security_solution/public/flyout/document_details/right/components/highlighted_fields_cell.test.tsx @@ -18,7 +18,7 @@ import { LeftPanelInsightsTab, DocumentDetailsLeftPanelKey } from '../../left'; import { TestProviders } from '../../../../common/mock'; import { ENTITIES_TAB_ID } from '../../left/components/entities_details'; import { useGetEndpointDetails } from '../../../../management/hooks'; -import { useSentinelOneAgentData } from '../../../../detections/components/host_isolation/use_sentinelone_host_isolation'; +import { useGetSentinelOneAgentStatus } from '../../../../detections/components/host_isolation/use_sentinelone_host_isolation'; import { useExpandableFlyoutApi, type ExpandableFlyoutApi } from '@kbn/expandable-flyout'; jest.mock('../../../../management/hooks'); @@ -96,7 +96,10 @@ describe('', () => { }); it('should render sentinelone agent status cell if field is agent.status and origialField is observer.serial_number', () => { - (useSentinelOneAgentData as jest.Mock).mockReturnValue({ isFetched: true }); + (useGetSentinelOneAgentStatus as jest.Mock).mockReturnValue({ + isFetched: true, + isLoading: false, + }); const { getByTestId } = render( ( - ({ agentId, platform, hostName, lastCheckin }) => { + ({ agentId, platform, hostName }) => { + const { data } = useGetSentinelOneAgentStatus([agentId]); + const agentStatus = data?.[agentId]; + const lastCheckin = agentStatus ? agentStatus.lastSeen : ''; + return ( ; capabilities: ImmutableArray; platform: string; - lastCheckin: string; }); export const useWithShowResponder = (): ShowResponseActionsConsole => { @@ -115,7 +114,6 @@ export const useWithShowResponder = (): ShowResponseActionsConsole => { ); diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/actions/response_actions.test.ts b/x-pack/plugins/security_solution/server/endpoint/routes/actions/response_actions.test.ts index 4789ff8046e6d..066e9f950ad44 100644 --- a/x-pack/plugins/security_solution/server/endpoint/routes/actions/response_actions.test.ts +++ b/x-pack/plugins/security_solution/server/endpoint/routes/actions/response_actions.test.ts @@ -73,7 +73,7 @@ import { omit, set } from 'lodash'; import type { ResponseActionAgentType } from '../../../../common/endpoint/service/response_actions/constants'; import { responseActionsClientMock } from '../../services/actions/clients/mocks'; import type { ActionsApiRequestHandlerContext } from '@kbn/actions-plugin/server'; -import { sentinelOneMock } from '../../services/actions/clients/sentinelone/mock'; +import { sentinelOneMock } from '../../services/actions/clients/sentinelone/mocks'; jest.mock('../../services', () => { const realModule = jest.requireActual('../../services'); diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/agent/agent_status_handler.test.ts b/x-pack/plugins/security_solution/server/endpoint/routes/agent/agent_status_handler.test.ts new file mode 100644 index 0000000000000..7ab7cd3b3c8d4 --- /dev/null +++ b/x-pack/plugins/security_solution/server/endpoint/routes/agent/agent_status_handler.test.ts @@ -0,0 +1,131 @@ +/* + * 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 { HttpApiTestSetupMock } from '../../mocks'; +import { createHttpApiTestSetupMock } from '../../mocks'; +import { sentinelOneMock } from '../../services/actions/clients/sentinelone/mocks'; +import { registerAgentStatusRoute } from './agent_status_handler'; +import { ENDPOINT_AGENT_STATUS_ROUTE } from '../../../../common/endpoint/constants'; +import { CustomHttpRequestError } from '../../../utils/custom_http_request_error'; +import type { EndpointAgentStatusRequestQueryParams } from '../../../../common/api/endpoint/agent/get_agent_status_route'; + +describe('Agent Status API route handler', () => { + let apiTestSetup: HttpApiTestSetupMock; + let httpRequestMock: ReturnType< + HttpApiTestSetupMock['createRequestMock'] + >; + let httpHandlerContextMock: HttpApiTestSetupMock< + never, + EndpointAgentStatusRequestQueryParams + >['httpHandlerContextMock']; + let httpResponseMock: HttpApiTestSetupMock< + never, + EndpointAgentStatusRequestQueryParams + >['httpResponseMock']; + + beforeEach(async () => { + apiTestSetup = createHttpApiTestSetupMock(); + ({ httpHandlerContextMock, httpResponseMock } = apiTestSetup); + + httpRequestMock = apiTestSetup.createRequestMock({ + query: { agentType: 'sentinel_one', agentIds: ['one', 'two'] }, + }); + + ( + (await apiTestSetup.httpHandlerContextMock.actions).getActionsClient as jest.Mock + ).mockReturnValue(sentinelOneMock.createConnectorActionsClient()); + + apiTestSetup.endpointAppContextMock.experimentalFeatures = { + ...apiTestSetup.endpointAppContextMock.experimentalFeatures, + responseActionsSentinelOneV1Enabled: true, + }; + + registerAgentStatusRoute(apiTestSetup.routerMock, apiTestSetup.endpointAppContextMock); + }); + + it('should error if the sentinel_one feature flag is turned off', async () => { + apiTestSetup.endpointAppContextMock.experimentalFeatures = { + ...apiTestSetup.endpointAppContextMock.experimentalFeatures, + responseActionsSentinelOneV1Enabled: false, + }; + + await apiTestSetup + .getRegisteredVersionedRoute('get', ENDPOINT_AGENT_STATUS_ROUTE, '1') + .routeHandler(httpHandlerContextMock, httpRequestMock, httpResponseMock); + + expect(httpResponseMock.customError).toHaveBeenCalledWith({ + statusCode: 400, + body: expect.any(CustomHttpRequestError), + }); + }); + + it('should only (v8.13) accept agent type of sentinel_one', async () => { + // @ts-expect-error `query.*` is not mutable + httpRequestMock.query.agentType = 'endpoint'; + await apiTestSetup + .getRegisteredVersionedRoute('get', ENDPOINT_AGENT_STATUS_ROUTE, '1') + .routeHandler(httpHandlerContextMock, httpRequestMock, httpResponseMock); + + expect(httpResponseMock.customError).toHaveBeenCalledWith({ + statusCode: 400, + body: expect.any(CustomHttpRequestError), + }); + }); + + it('should return status code 200 with expected payload', async () => { + await apiTestSetup + .getRegisteredVersionedRoute('get', ENDPOINT_AGENT_STATUS_ROUTE, '1') + .routeHandler(httpHandlerContextMock, httpRequestMock, httpResponseMock); + + expect(httpResponseMock.ok).toHaveBeenCalledWith({ + body: { + data: { + one: { + agentType: 'sentinel_one', + found: false, + id: 'one', + isUninstalled: false, + isPendingUninstall: false, + isolated: false, + lastSeen: '', + pendingActions: { + execute: 0, + 'get-file': 0, + isolate: 0, + 'kill-process': 0, + 'running-processes': 0, + 'suspend-process': 0, + unisolate: 0, + upload: 0, + }, + status: 'unenrolled', + }, + two: { + agentType: 'sentinel_one', + found: false, + id: 'two', + isUninstalled: false, + isPendingUninstall: false, + isolated: false, + lastSeen: '', + pendingActions: { + execute: 0, + 'get-file': 0, + isolate: 0, + 'kill-process': 0, + 'running-processes': 0, + 'suspend-process': 0, + unisolate: 0, + upload: 0, + }, + status: 'unenrolled', + }, + }, + }, + }); + }); +}); diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/agent/agent_status_handler.ts b/x-pack/plugins/security_solution/server/endpoint/routes/agent/agent_status_handler.ts new file mode 100644 index 0000000000000..2bf78bbb73b99 --- /dev/null +++ b/x-pack/plugins/security_solution/server/endpoint/routes/agent/agent_status_handler.ts @@ -0,0 +1,107 @@ +/* + * 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 { RequestHandler } from '@kbn/core/server'; +import { getAgentStatus } from '../../services/agent/agent_status'; +import { errorHandler } from '../error_handler'; +import type { EndpointAgentStatusRequestQueryParams } from '../../../../common/api/endpoint/agent/get_agent_status_route'; +import { EndpointAgentStatusRequestSchema } from '../../../../common/api/endpoint/agent/get_agent_status_route'; +import { ENDPOINT_AGENT_STATUS_ROUTE } from '../../../../common/endpoint/constants'; +import type { + SecuritySolutionPluginRouter, + SecuritySolutionRequestHandlerContext, +} from '../../../types'; +import type { EndpointAppContext } from '../../types'; +import { withEndpointAuthz } from '../with_endpoint_authz'; +import { CustomHttpRequestError } from '../../../utils/custom_http_request_error'; + +export const registerAgentStatusRoute = ( + router: SecuritySolutionPluginRouter, + endpointContext: EndpointAppContext +) => { + router.versioned + .get({ + access: 'internal', + path: ENDPOINT_AGENT_STATUS_ROUTE, + options: { authRequired: true, tags: ['access:securitySolution'] }, + }) + .addVersion( + { + version: '1', + validate: { + request: EndpointAgentStatusRequestSchema, + }, + }, + withEndpointAuthz( + { all: ['canReadSecuritySolution'] }, + endpointContext.logFactory.get('actionStatusRoute'), + getAgentStatusRouteHandler(endpointContext) + ) + ); +}; + +export const getAgentStatusRouteHandler = ( + endpointContext: EndpointAppContext +): RequestHandler< + never, + EndpointAgentStatusRequestQueryParams, + unknown, + SecuritySolutionRequestHandlerContext +> => { + const logger = endpointContext.logFactory.get('agentStatus'); + + return async (context, request, response) => { + const { agentType = 'endpoint', agentIds: _agentIds } = request.query; + const agentIds = Array.isArray(_agentIds) ? _agentIds : [_agentIds]; + + // Note: because our API schemas are defined as module static variables (as opposed to a + // `getter` function), we need to include this additional validation here, since + // `agent_type` is included in the schema independent of the feature flag + if ( + agentType === 'sentinel_one' && + !endpointContext.experimentalFeatures.responseActionsSentinelOneV1Enabled + ) { + return errorHandler( + logger, + response, + new CustomHttpRequestError(`[request query.agent_type]: feature is disabled`, 400) + ); + } + + // TEMPORARY: + // For v8.13 we only support SentinelOne on this API due to time constraints + if (agentType !== 'sentinel_one') { + return errorHandler( + logger, + response, + new CustomHttpRequestError( + `[${agentType}] agent type is not currently supported by this API`, + 400 + ) + ); + } + + logger.debug( + `Retrieving status for: agentType [${agentType}], agentIds: [${agentIds.join(', ')}]` + ); + + try { + return response.ok({ + body: { + data: await getAgentStatus({ + agentType, + agentIds, + logger, + connectorActionsClient: (await context.actions).getActionsClient(), + }), + }, + }); + } catch (e) { + return errorHandler(logger, response, e); + } + }; +}; diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/agent/index.ts b/x-pack/plugins/security_solution/server/endpoint/routes/agent/index.ts new file mode 100644 index 0000000000000..be028ae114218 --- /dev/null +++ b/x-pack/plugins/security_solution/server/endpoint/routes/agent/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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { registerAgentStatusRoute } from './agent_status_handler'; +import type { SecuritySolutionPluginRouter } from '../../../types'; +import type { EndpointAppContext } from '../../types'; + +export const registerAgentRoutes = ( + router: SecuritySolutionPluginRouter, + endpointContext: EndpointAppContext +) => { + registerAgentStatusRoute(router, endpointContext); +}; diff --git a/x-pack/plugins/security_solution/server/endpoint/services/actions/clients/get_response_actions_client.test.ts b/x-pack/plugins/security_solution/server/endpoint/services/actions/clients/get_response_actions_client.test.ts index 224c8eac855ed..904cc9dfad3d7 100644 --- a/x-pack/plugins/security_solution/server/endpoint/services/actions/clients/get_response_actions_client.test.ts +++ b/x-pack/plugins/security_solution/server/endpoint/services/actions/clients/get_response_actions_client.test.ts @@ -11,7 +11,7 @@ import { RESPONSE_ACTION_AGENT_TYPE } from '../../../../../common/endpoint/servi import { getResponseActionsClient } from '../..'; import { ResponseActionsClientImpl } from './lib/base_response_actions_client'; import { UnsupportedResponseActionsAgentTypeError } from './errors'; -import { sentinelOneMock } from './sentinelone/mock'; +import { sentinelOneMock } from './sentinelone/mocks'; describe('getResponseActionsClient()', () => { let options: GetResponseActionsClientConstructorOptions; diff --git a/x-pack/plugins/security_solution/server/endpoint/services/actions/clients/sentinelone/mock.ts b/x-pack/plugins/security_solution/server/endpoint/services/actions/clients/sentinelone/mock.ts deleted file mode 100644 index 48a6ace18adc5..0000000000000 --- a/x-pack/plugins/security_solution/server/endpoint/services/actions/clients/sentinelone/mock.ts +++ /dev/null @@ -1,177 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import type { SentinelOneGetAgentsResponse } from '@kbn/stack-connectors-plugin/common/sentinelone/types'; -import { - SENTINELONE_CONNECTOR_ID, - SUB_ACTION, -} from '@kbn/stack-connectors-plugin/common/sentinelone/constants'; -import type { ActionsClientMock } from '@kbn/actions-plugin/server/actions_client/actions_client.mock'; -import type { ConnectorWithExtraFindData } from '@kbn/actions-plugin/server/application/connector/types'; -import type { ResponseActionsClientOptionsMock } from '../mocks'; -import { responseActionsClientMock } from '../mocks'; - -export interface SentinelOneActionsClientOptionsMock extends ResponseActionsClientOptionsMock { - connectorActions: ActionsClientMock; -} - -const createSentinelOneGetAgentsApiResponseMock = (): SentinelOneGetAgentsResponse => { - return { - pagination: { - nextCursor: 'next-0', - totalItems: 1, - }, - errors: null, - data: [ - { - accountId: '11111111111', - accountName: 'Elastic', - groupUpdatedAt: null, - policyUpdatedAt: null, - activeDirectory: { - computerDistinguishedName: null, - computerMemberOf: [], - lastUserDistinguishedName: null, - lastUserMemberOf: [], - userPrincipalName: null, - mail: null, - }, - activeThreats: 0, - agentVersion: '23.3.2.12', - allowRemoteShell: true, - appsVulnerabilityStatus: 'not_applicable', - cloudProviders: {}, - computerName: 'sentinelone-1460', - consoleMigrationStatus: 'N/A', - coreCount: 1, - cpuCount: 1, - cpuId: 'ARM Cortex-A72', - createdAt: '2023-12-21T20:32:52.290978Z', - detectionState: null, - domain: 'unknown', - encryptedApplications: false, - externalId: '', - externalIp: '108.77.84.191', - firewallEnabled: true, - firstFullModeTime: null, - fullDiskScanLastUpdatedAt: '2023-12-21T20:57:55.690655Z', - groupId: '9999999999999', - groupIp: '108.77.84.x', - groupName: 'Default Group', - id: '1845174760470303882', - inRemoteShellSession: false, - infected: false, - installerType: '.deb', - isActive: true, - isDecommissioned: false, - isPendingUninstall: false, - isUninstalled: false, - isUpToDate: true, - lastActiveDate: '2023-12-26T21:34:28.032981Z', - lastIpToMgmt: '192.168.64.2', - lastLoggedInUserName: '', - licenseKey: '', - locationEnabled: false, - locationType: 'not_supported', - locations: null, - machineType: 'server', - mitigationMode: 'detect', - mitigationModeSuspicious: 'detect', - modelName: 'QEMU QEMU Virtual Machine', - networkInterfaces: [ - { - gatewayIp: '192.168.64.1', - gatewayMacAddress: 'be:d0:74:50:d8:64', - id: '1845174760470303883', - inet: ['192.168.64.2'], - inet6: ['fdf4:f033:b1d4:8c51:5054:ff:febc:6253'], - name: 'enp0s1', - physical: '52:54:00:BC:62:53', - }, - ], - networkQuarantineEnabled: false, - networkStatus: 'connecting', - operationalState: 'na', - operationalStateExpiration: null, - osArch: '64 bit', - osName: 'Linux', - osRevision: 'Ubuntu 22.04.3 LTS 5.15.0-91-generic', - osStartTime: '2023-12-21T20:31:51Z', - osType: 'linux', - osUsername: 'root', - rangerStatus: 'Enabled', - rangerVersion: '23.4.0.9', - registeredAt: '2023-12-21T20:32:52.286752Z', - remoteProfilingState: 'disabled', - remoteProfilingStateExpiration: null, - scanAbortedAt: null, - scanFinishedAt: '2023-12-21T20:57:55.690655Z', - scanStartedAt: '2023-12-21T20:33:31.170460Z', - scanStatus: 'finished', - serialNumber: null, - showAlertIcon: false, - siteId: '88888888888', - siteName: 'Default site', - storageName: null, - storageType: null, - tags: { sentinelone: [] }, - threatRebootRequired: false, - totalMemory: 1966, - updatedAt: '2023-12-26T21:35:35.986596Z', - userActionsNeeded: [], - uuid: 'a2f4603d-c9e2-d7a2-bec2-0d646f3bbc9f', - }, - ], - }; -}; - -const createConnectorActionsClientMock = (): ActionsClientMock => { - const client = responseActionsClientMock.createConnectorActionsClient(); - - (client.getAll as jest.Mock).mockImplementation(async () => { - const result: ConnectorWithExtraFindData[] = [ - // SentinelOne connector - responseActionsClientMock.createConnector({ - actionTypeId: SENTINELONE_CONNECTOR_ID, - id: 's1-connector-instance-id', - }), - ]; - - return result; - }); - - (client.execute as jest.Mock).mockImplementation( - async (options: Parameters[0]) => { - const subAction = options.params.subAction; - - switch (subAction) { - case SUB_ACTION.GET_AGENTS: - return responseActionsClientMock.createConnectorActionExecuteResponse({ - data: createSentinelOneGetAgentsApiResponseMock(), - }); - - default: - return responseActionsClientMock.createConnectorActionExecuteResponse(); - } - } - ); - - return client; -}; - -const createConstructorOptionsMock = (): SentinelOneActionsClientOptionsMock => { - return { - ...responseActionsClientMock.createConstructorOptions(), - connectorActions: createConnectorActionsClientMock(), - }; -}; - -export const sentinelOneMock = { - createGetAgentsResponse: createSentinelOneGetAgentsApiResponseMock, - createConnectorActionsClient: createConnectorActionsClientMock, - createConstructorOptions: createConstructorOptionsMock, -}; diff --git a/x-pack/plugins/security_solution/server/endpoint/services/actions/clients/sentinelone/mocks.ts b/x-pack/plugins/security_solution/server/endpoint/services/actions/clients/sentinelone/mocks.ts new file mode 100644 index 0000000000000..2701120d1e4f0 --- /dev/null +++ b/x-pack/plugins/security_solution/server/endpoint/services/actions/clients/sentinelone/mocks.ts @@ -0,0 +1,188 @@ +/* + * 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 { SentinelOneGetAgentsResponse } from '@kbn/stack-connectors-plugin/common/sentinelone/types'; +import { + SENTINELONE_CONNECTOR_ID, + SUB_ACTION, +} from '@kbn/stack-connectors-plugin/common/sentinelone/constants'; +import type { ActionsClientMock } from '@kbn/actions-plugin/server/actions_client/actions_client.mock'; +import type { ConnectorWithExtraFindData } from '@kbn/actions-plugin/server/application/connector/types'; +import { merge } from 'lodash'; +import type { ResponseActionsClientOptionsMock } from '../mocks'; +import { responseActionsClientMock } from '../mocks'; + +export interface SentinelOneActionsClientOptionsMock extends ResponseActionsClientOptionsMock { + connectorActions: ActionsClientMock; +} + +const createSentinelOneAgentDetailsMock = ( + overrides: Partial = {} +): SentinelOneGetAgentsResponse['data'][number] => { + return merge( + { + accountId: '11111111111', + accountName: 'Elastic', + groupUpdatedAt: null, + policyUpdatedAt: null, + activeDirectory: { + computerDistinguishedName: null, + computerMemberOf: [], + lastUserDistinguishedName: null, + lastUserMemberOf: [], + userPrincipalName: null, + mail: null, + }, + activeThreats: 0, + agentVersion: '23.3.2.12', + allowRemoteShell: true, + appsVulnerabilityStatus: 'not_applicable', + cloudProviders: {}, + computerName: 'sentinelone-1460', + consoleMigrationStatus: 'N/A', + coreCount: 1, + cpuCount: 1, + cpuId: 'ARM Cortex-A72', + createdAt: '2023-12-21T20:32:52.290978Z', + detectionState: null, + domain: 'unknown', + encryptedApplications: false, + externalId: '', + externalIp: '108.77.84.191', + firewallEnabled: true, + firstFullModeTime: null, + fullDiskScanLastUpdatedAt: '2023-12-21T20:57:55.690655Z', + groupId: '9999999999999', + groupIp: '108.77.84.x', + groupName: 'Default Group', + id: '1845174760470303882', + inRemoteShellSession: false, + infected: false, + installerType: '.deb', + isActive: true, + isDecommissioned: false, + isPendingUninstall: false, + isUninstalled: false, + isUpToDate: true, + lastActiveDate: '2023-12-26T21:34:28.032981Z', + lastIpToMgmt: '192.168.64.2', + lastLoggedInUserName: '', + licenseKey: '', + locationEnabled: false, + locationType: 'not_supported', + locations: null, + machineType: 'server', + mitigationMode: 'detect', + mitigationModeSuspicious: 'detect', + modelName: 'QEMU QEMU Virtual Machine', + networkInterfaces: [ + { + gatewayIp: '192.168.64.1', + gatewayMacAddress: 'be:d0:74:50:d8:64', + id: '1845174760470303883', + inet: ['192.168.64.2'], + inet6: ['fdf4:f033:b1d4:8c51:5054:ff:febc:6253'], + name: 'enp0s1', + physical: '52:54:00:BC:62:53', + }, + ], + networkQuarantineEnabled: false, + networkStatus: 'connecting', + operationalState: 'na', + operationalStateExpiration: null, + osArch: '64 bit', + osName: 'Linux', + osRevision: 'Ubuntu 22.04.3 LTS 5.15.0-91-generic', + osStartTime: '2023-12-21T20:31:51Z', + osType: 'linux', + osUsername: 'root', + rangerStatus: 'Enabled', + rangerVersion: '23.4.0.9', + registeredAt: '2023-12-21T20:32:52.286752Z', + remoteProfilingState: 'disabled', + remoteProfilingStateExpiration: null, + scanAbortedAt: null, + scanFinishedAt: '2023-12-21T20:57:55.690655Z', + scanStartedAt: '2023-12-21T20:33:31.170460Z', + scanStatus: 'finished', + serialNumber: null, + showAlertIcon: false, + siteId: '88888888888', + siteName: 'Default site', + storageName: null, + storageType: null, + tags: { sentinelone: [] }, + threatRebootRequired: false, + totalMemory: 1966, + updatedAt: '2023-12-26T21:35:35.986596Z', + userActionsNeeded: [], + uuid: 'a2f4603d-c9e2-d7a2-bec2-0d646f3bbc9f', + }, + overrides + ); +}; + +const createSentinelOneGetAgentsApiResponseMock = ( + data: SentinelOneGetAgentsResponse['data'] = [createSentinelOneAgentDetailsMock()] +): SentinelOneGetAgentsResponse => { + return { + pagination: { + nextCursor: 'next-0', + totalItems: 1, + }, + errors: null, + data, + }; +}; + +const createConnectorActionsClientMock = (): ActionsClientMock => { + const client = responseActionsClientMock.createConnectorActionsClient(); + + (client.getAll as jest.Mock).mockImplementation(async () => { + const result: ConnectorWithExtraFindData[] = [ + // SentinelOne connector + responseActionsClientMock.createConnector({ + actionTypeId: SENTINELONE_CONNECTOR_ID, + id: 's1-connector-instance-id', + }), + ]; + + return result; + }); + + (client.execute as jest.Mock).mockImplementation( + async (options: Parameters[0]) => { + const subAction = options.params.subAction; + + switch (subAction) { + case SUB_ACTION.GET_AGENTS: + return responseActionsClientMock.createConnectorActionExecuteResponse({ + data: createSentinelOneGetAgentsApiResponseMock(), + }); + + default: + return responseActionsClientMock.createConnectorActionExecuteResponse(); + } + } + ); + + return client; +}; + +const createConstructorOptionsMock = (): SentinelOneActionsClientOptionsMock => { + return { + ...responseActionsClientMock.createConstructorOptions(), + connectorActions: createConnectorActionsClientMock(), + }; +}; + +export const sentinelOneMock = { + createGetAgentsResponse: createSentinelOneGetAgentsApiResponseMock, + createSentinelOneAgentDetails: createSentinelOneAgentDetailsMock, + createConnectorActionsClient: createConnectorActionsClientMock, + createConstructorOptions: createConstructorOptionsMock, +}; diff --git a/x-pack/plugins/security_solution/server/endpoint/services/actions/clients/sentinelone/sentinel_one_actions_client.test.ts b/x-pack/plugins/security_solution/server/endpoint/services/actions/clients/sentinelone/sentinel_one_actions_client.test.ts index c506e8615ed04..aec2ffdebe1ee 100644 --- a/x-pack/plugins/security_solution/server/endpoint/services/actions/clients/sentinelone/sentinel_one_actions_client.test.ts +++ b/x-pack/plugins/security_solution/server/endpoint/services/actions/clients/sentinelone/sentinel_one_actions_client.test.ts @@ -11,8 +11,8 @@ import { SentinelOneActionsClient } from './sentinel_one_actions_client'; import { getActionDetailsById as _getActionDetailsById } from '../../action_details_by_id'; import { ResponseActionsClientError, ResponseActionsNotSupportedError } from '../errors'; import type { ActionsClientMock } from '@kbn/actions-plugin/server/actions_client/actions_client.mock'; -import type { SentinelOneActionsClientOptionsMock } from './mock'; -import { sentinelOneMock } from './mock'; +import type { SentinelOneActionsClientOptionsMock } from './mocks'; +import { sentinelOneMock } from './mocks'; import { ENDPOINT_ACTION_RESPONSES_INDEX, ENDPOINT_ACTIONS_INDEX, diff --git a/x-pack/plugins/security_solution/server/endpoint/services/agent/agent_status.test.ts b/x-pack/plugins/security_solution/server/endpoint/services/agent/agent_status.test.ts new file mode 100644 index 0000000000000..8037590d323c2 --- /dev/null +++ b/x-pack/plugins/security_solution/server/endpoint/services/agent/agent_status.test.ts @@ -0,0 +1,184 @@ +/* + * 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 { GetAgentStatusOptions } from './agent_status'; +import { loggingSystemMock } from '@kbn/core-logging-server-mocks'; +import { sentinelOneMock } from '../actions/clients/sentinelone/mocks'; +import { getAgentStatus, SENTINEL_ONE_NETWORK_STATUS } from './agent_status'; +import { responseActionsClientMock } from '../actions/clients/mocks'; + +describe('Endpoint Get Agent Status service', () => { + let agentStatusOptions: GetAgentStatusOptions; + + beforeEach(() => { + agentStatusOptions = { + agentType: 'sentinel_one', + agentIds: ['1', '2'], + logger: loggingSystemMock.create().get('getAgentStatus'), + connectorActionsClient: sentinelOneMock.createConnectorActionsClient(), + }; + }); + + it('should throw error if unable to access stack connectors', async () => { + (agentStatusOptions.connectorActionsClient.getAll as jest.Mock).mockImplementation(async () => { + throw new Error('boom'); + }); + const getStatusResponsePromise = getAgentStatus(agentStatusOptions); + + await expect(getStatusResponsePromise).rejects.toHaveProperty( + 'message', + 'Unable to retrieve list of stack connectors: boom' + ); + await expect(getStatusResponsePromise).rejects.toHaveProperty('statusCode', 400); + }); + + it('should throw error if no SentinelOne connector is registered', async () => { + (agentStatusOptions.connectorActionsClient.getAll as jest.Mock).mockResolvedValue([]); + const getStatusResponsePromise = getAgentStatus(agentStatusOptions); + + await expect(getStatusResponsePromise).rejects.toHaveProperty( + 'message', + 'No SentinelOne stack connector found' + ); + await expect(getStatusResponsePromise).rejects.toHaveProperty('statusCode', 400); + }); + + it('should send api request to SentinelOne', async () => { + await getAgentStatus(agentStatusOptions); + + expect(agentStatusOptions.connectorActionsClient.execute).toHaveBeenCalledWith({ + actionId: 's1-connector-instance-id', + params: { + subAction: 'getAgents', + subActionParams: { + uuids: '1,2', + }, + }, + }); + }); + + it('should throw if api call to SentinelOne failed', async () => { + (agentStatusOptions.connectorActionsClient.execute as jest.Mock).mockResolvedValue( + responseActionsClientMock.createConnectorActionExecuteResponse({ + status: 'error', + serviceMessage: 'boom', + }) + ); + const getStatusResponsePromise = getAgentStatus(agentStatusOptions); + + await expect(getStatusResponsePromise).rejects.toHaveProperty( + 'message', + 'Attempt retrieve agent information from to SentinelOne failed: boom' + ); + await expect(getStatusResponsePromise).rejects.toHaveProperty('statusCode', 500); + }); + + it('should return expected output', async () => { + agentStatusOptions.agentIds = ['aaa', 'bbb', 'ccc', 'invalid']; + (agentStatusOptions.connectorActionsClient.execute as jest.Mock).mockResolvedValue( + responseActionsClientMock.createConnectorActionExecuteResponse({ + data: sentinelOneMock.createGetAgentsResponse([ + sentinelOneMock.createSentinelOneAgentDetails({ + networkStatus: SENTINEL_ONE_NETWORK_STATUS.DISCONNECTED, // Isolated + uuid: 'aaa', + }), + sentinelOneMock.createSentinelOneAgentDetails({ + networkStatus: SENTINEL_ONE_NETWORK_STATUS.DISCONNECTING, // Releasing + uuid: 'bbb', + }), + sentinelOneMock.createSentinelOneAgentDetails({ + networkStatus: SENTINEL_ONE_NETWORK_STATUS.CONNECTING, // isolating + uuid: 'ccc', + }), + ]), + }) + ); + + await expect(getAgentStatus(agentStatusOptions)).resolves.toEqual({ + aaa: { + agentType: 'sentinel_one', + found: true, + id: 'aaa', + isUninstalled: false, + isPendingUninstall: false, + isolated: true, + lastSeen: '2023-12-26T21:35:35.986596Z', + pendingActions: { + execute: 0, + 'get-file': 0, + isolate: 0, + 'kill-process': 0, + 'running-processes': 0, + 'suspend-process': 0, + unisolate: 0, + upload: 0, + }, + status: 'healthy', + }, + bbb: { + agentType: 'sentinel_one', + found: true, + id: 'bbb', + isUninstalled: false, + isPendingUninstall: false, + isolated: false, + lastSeen: '2023-12-26T21:35:35.986596Z', + pendingActions: { + execute: 0, + 'get-file': 0, + isolate: 1, + 'kill-process': 0, + 'running-processes': 0, + 'suspend-process': 0, + unisolate: 0, + upload: 0, + }, + status: 'healthy', + }, + ccc: { + agentType: 'sentinel_one', + found: true, + id: 'ccc', + isUninstalled: false, + isPendingUninstall: false, + isolated: false, + lastSeen: '2023-12-26T21:35:35.986596Z', + pendingActions: { + execute: 0, + 'get-file': 0, + isolate: 0, + 'kill-process': 0, + 'running-processes': 0, + 'suspend-process': 0, + unisolate: 1, + upload: 0, + }, + status: 'healthy', + }, + invalid: { + agentType: 'sentinel_one', + found: false, + id: 'invalid', + isUninstalled: false, + isPendingUninstall: false, + isolated: false, + lastSeen: '', + pendingActions: { + execute: 0, + 'get-file': 0, + isolate: 0, + 'kill-process': 0, + 'running-processes': 0, + 'suspend-process': 0, + unisolate: 0, + upload: 0, + }, + status: 'unenrolled', + }, + }); + }); +}); diff --git a/x-pack/plugins/security_solution/server/endpoint/services/agent/agent_status.ts b/x-pack/plugins/security_solution/server/endpoint/services/agent/agent_status.ts new file mode 100644 index 0000000000000..f91b4238979dc --- /dev/null +++ b/x-pack/plugins/security_solution/server/endpoint/services/agent/agent_status.ts @@ -0,0 +1,138 @@ +/* + * 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 { ActionsClient } from '@kbn/actions-plugin/server'; +import type { ConnectorWithExtraFindData } from '@kbn/actions-plugin/server/application/connector/types'; +import { + SENTINELONE_CONNECTOR_ID, + SUB_ACTION, +} from '@kbn/stack-connectors-plugin/common/sentinelone/constants'; +import type { Logger } from '@kbn/core/server'; +import { keyBy, merge } from 'lodash'; +import type { ActionTypeExecutorResult } from '@kbn/actions-plugin/common'; +import type { SentinelOneGetAgentsResponse } from '@kbn/stack-connectors-plugin/common/sentinelone/types'; +import { stringify } from '../../utils/stringify'; +import type { ResponseActionAgentType } from '../../../../common/endpoint/service/response_actions/constants'; +import type { AgentStatusApiResponse } from '../../../../common/endpoint/types'; +import { HostStatus } from '../../../../common/endpoint/types'; +import { CustomHttpRequestError } from '../../../utils/custom_http_request_error'; + +export interface GetAgentStatusOptions { + // NOTE: only sentinel_one currently supported + agentType: ResponseActionAgentType & 'sentinel_one'; + agentIds: string[]; + connectorActionsClient: ActionsClient; + logger: Logger; +} + +export const getAgentStatus = async ({ + agentType, + agentIds, + connectorActionsClient, + logger, +}: GetAgentStatusOptions): Promise => { + let connectorList: ConnectorWithExtraFindData[] = []; + + try { + connectorList = await connectorActionsClient.getAll(); + } catch (err) { + throw new CustomHttpRequestError( + `Unable to retrieve list of stack connectors: ${err.message}`, + // failure here is likely due to Authz, but because we don't have a good way to determine that, + // the `statusCode` below is set to `400` instead of `401`. + 400, + err + ); + } + const connector = connectorList.find(({ actionTypeId, isDeprecated, isMissingSecrets }) => { + return actionTypeId === SENTINELONE_CONNECTOR_ID && !isDeprecated && !isMissingSecrets; + }); + + if (!connector) { + throw new CustomHttpRequestError(`No SentinelOne stack connector found`, 400, connectorList); + } + + logger.debug(`Using SentinelOne stack connector: ${connector.name} (${connector.id})`); + + const agentDetailsResponse = (await connectorActionsClient.execute({ + actionId: connector.id, + params: { + subAction: SUB_ACTION.GET_AGENTS, + subActionParams: { + uuids: agentIds.filter((agentId) => agentId.trim().length).join(','), + }, + }, + })) as ActionTypeExecutorResult; + + if (agentDetailsResponse.status === 'error') { + logger.error(stringify(agentDetailsResponse)); + + throw new CustomHttpRequestError( + `Attempt retrieve agent information from to SentinelOne failed: ${ + agentDetailsResponse.serviceMessage || agentDetailsResponse.message + }`, + 500, + agentDetailsResponse + ); + } + + const agentDetailsById = keyBy(agentDetailsResponse.data?.data, 'uuid'); + + logger.debug(`Response from SentinelOne API:\n${stringify(agentDetailsById)}`); + + return agentIds.reduce((acc, agentId) => { + const thisAgentDetails = agentDetailsById[agentId]; + const thisAgentStatus = { + agentType, + id: agentId, + found: false, + isolated: false, + isPendingUninstall: false, + isUninstalled: false, + lastSeen: '', + pendingActions: { + execute: 0, + upload: 0, + unisolate: 0, + isolate: 0, + 'get-file': 0, + 'kill-process': 0, + 'suspend-process': 0, + 'running-processes': 0, + }, + status: HostStatus.UNENROLLED, + }; + + if (thisAgentDetails) { + merge(thisAgentStatus, { + found: true, + lastSeen: thisAgentDetails.updatedAt, + isPendingUninstall: thisAgentDetails.isPendingUninstall, + isUninstalled: thisAgentDetails.isUninstalled, + isolated: thisAgentDetails.networkStatus === SENTINEL_ONE_NETWORK_STATUS.DISCONNECTED, + status: !thisAgentDetails.isActive ? HostStatus.OFFLINE : HostStatus.HEALTHY, + pendingActions: { + isolate: + thisAgentDetails.networkStatus === SENTINEL_ONE_NETWORK_STATUS.DISCONNECTING ? 1 : 0, + unisolate: + thisAgentDetails.networkStatus === SENTINEL_ONE_NETWORK_STATUS.CONNECTING ? 1 : 0, + }, + }); + } + + acc[agentId] = thisAgentStatus; + + return acc; + }, {}); +}; + +export enum SENTINEL_ONE_NETWORK_STATUS { + CONNECTING = 'connecting', + CONNECTED = 'connected', + DISCONNECTING = 'disconnecting', + DISCONNECTED = 'disconnected', +} diff --git a/x-pack/plugins/security_solution/server/plugin.ts b/x-pack/plugins/security_solution/server/plugin.ts index 93f49275491ee..1af0d9171153b 100644 --- a/x-pack/plugins/security_solution/server/plugin.ts +++ b/x-pack/plugins/security_solution/server/plugin.ts @@ -19,6 +19,7 @@ import type { ILicense } from '@kbn/licensing-plugin/server'; import { FLEET_ENDPOINT_PACKAGE } from '@kbn/fleet-plugin/common'; import { i18n } from '@kbn/i18n'; +import { registerAgentRoutes } from './endpoint/routes/agent'; import { endpointPackagePoliciesStatsSearchStrategyProvider } from './search_strategy/endpoint_package_policies_stats'; import { turnOffPolicyProtectionsIfNotSupported } from './endpoint/migrations/turn_off_policy_protections'; import { endpointSearchStrategyProvider } from './search_strategy/endpoint'; @@ -363,6 +364,7 @@ export class Plugin implements ISecuritySolutionPlugin { this.endpointContext, plugins.encryptedSavedObjects?.canEncrypt === true ); + registerAgentRoutes(router, this.endpointContext); if (plugins.alerting != null) { const ruleNotificationType = legacyRulesNotificationRuleType({ logger }); diff --git a/x-pack/plugins/stack_connectors/common/sentinelone/types.ts b/x-pack/plugins/stack_connectors/common/sentinelone/types.ts index 65dda9e6028a3..95f0391c7e5f4 100644 --- a/x-pack/plugins/stack_connectors/common/sentinelone/types.ts +++ b/x-pack/plugins/stack_connectors/common/sentinelone/types.ts @@ -29,8 +29,6 @@ export type SentinelOneBaseApiResponse = TypeOf>; export type SentinelOneGetAgentsResponse = TypeOf; -export type SentinelOneAgent = SentinelOneGetAgentsResponse['data'][0]; - export type SentinelOneKillProcessParams = TypeOf; export type SentinelOneExecuteScriptParams = TypeOf; From 8af71e4889682380623376843af7c096522812b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Zaffarano?= Date: Mon, 12 Feb 2024 21:54:56 +0100 Subject: [PATCH 26/83] [Security Solution] Add async telemetry events sender (#174577) --- .../__mocks__/endpoint-alert.json | 196 +++ .../integration_tests/__mocks__/rule.json | 127 ++ .../server/integration_tests/lib/helpers.ts | 109 ++ .../lib/telemetry_helpers.ts | 269 ++++ .../integration_tests/telemetry.test.ts | 351 +++++ .../utils/get_detecton_rules_preview.ts | 6 +- .../utils/get_diagnostics_preview.ts | 6 +- .../telemetry/utils/get_endpoint_preview.ts | 6 +- .../utils/get_security_lists_preview.ts | 6 +- .../query/create_query_alert_type.test.ts | 2 +- .../rule_types/utils/send_telemetry_events.ts | 3 +- .../server/lib/telemetry/__mocks__/index.ts | 35 + .../telemetry/__mocks__/kibana-artifacts.zip | Bin 1788 -> 2109 bytes .../server/lib/telemetry/artifact.test.ts | 46 +- .../server/lib/telemetry/artifact.ts | 101 +- .../server/lib/telemetry/async_sender.test.ts | 1148 +++++++++++++++++ .../server/lib/telemetry/async_sender.ts | 414 ++++++ .../lib/telemetry/async_sender.types.ts | 55 + .../lib/telemetry/collections_helpers.test.ts | 81 ++ .../lib/telemetry/collections_helpers.ts | 65 + .../server/lib/telemetry/configuration.ts | 28 +- .../server/lib/telemetry/helpers.test.ts | 42 - .../server/lib/telemetry/helpers.ts | 27 +- .../server/lib/telemetry/preview_sender.ts | 40 +- .../lib/telemetry/preview_task_metrics.ts | 52 + .../server/lib/telemetry/rxjs_helpers.test.ts | 166 +++ .../server/lib/telemetry/rxjs_helpers.ts | 83 ++ .../server/lib/telemetry/sender.ts | 74 +- .../server/lib/telemetry/sender_helpers.ts | 125 ++ .../server/lib/telemetry/task.test.ts | 14 +- .../server/lib/telemetry/task.ts | 16 +- .../server/lib/telemetry/task_metrics.test.ts | 80 ++ .../server/lib/telemetry/task_metrics.ts | 51 + .../lib/telemetry/task_metrics.types.ts | 25 + .../lib/telemetry/tasks/configuration.test.ts | 8 +- .../lib/telemetry/tasks/configuration.ts | 89 +- .../telemetry/tasks/detection_rule.test.ts | 8 +- .../lib/telemetry/tasks/detection_rule.ts | 53 +- .../lib/telemetry/tasks/diagnostic.test.ts | 11 +- .../server/lib/telemetry/tasks/diagnostic.ts | 38 +- .../lib/telemetry/tasks/endpoint.test.ts | 13 +- .../server/lib/telemetry/tasks/endpoint.ts | 58 +- .../server/lib/telemetry/tasks/filterlists.ts | 47 +- .../tasks/prebuilt_rule_alerts.test.ts | 11 +- .../telemetry/tasks/prebuilt_rule_alerts.ts | 33 +- .../telemetry/tasks/security_lists.test.ts | 8 +- .../lib/telemetry/tasks/security_lists.ts | 37 +- .../lib/telemetry/tasks/timelines.test.ts | 10 +- .../server/lib/telemetry/tasks/timelines.ts | 33 +- .../tasks/timelines_diagnostic.test.ts | 10 +- .../telemetry/tasks/timelines_diagnostic.ts | 37 +- .../server/lib/telemetry/types.ts | 44 +- .../security_solution/server/plugin.ts | 22 +- .../plugins/security_solution/tsconfig.json | 1 + .../task_based/all_types.ts | 8 +- .../task_based/detection_rules.ts | 22 +- 56 files changed, 4130 insertions(+), 320 deletions(-) create mode 100644 x-pack/plugins/security_solution/server/integration_tests/__mocks__/endpoint-alert.json create mode 100644 x-pack/plugins/security_solution/server/integration_tests/__mocks__/rule.json create mode 100644 x-pack/plugins/security_solution/server/integration_tests/lib/helpers.ts create mode 100644 x-pack/plugins/security_solution/server/integration_tests/lib/telemetry_helpers.ts create mode 100644 x-pack/plugins/security_solution/server/integration_tests/telemetry.test.ts create mode 100644 x-pack/plugins/security_solution/server/lib/telemetry/async_sender.test.ts create mode 100644 x-pack/plugins/security_solution/server/lib/telemetry/async_sender.ts create mode 100644 x-pack/plugins/security_solution/server/lib/telemetry/async_sender.types.ts create mode 100644 x-pack/plugins/security_solution/server/lib/telemetry/collections_helpers.test.ts create mode 100644 x-pack/plugins/security_solution/server/lib/telemetry/collections_helpers.ts create mode 100644 x-pack/plugins/security_solution/server/lib/telemetry/preview_task_metrics.ts create mode 100644 x-pack/plugins/security_solution/server/lib/telemetry/rxjs_helpers.test.ts create mode 100644 x-pack/plugins/security_solution/server/lib/telemetry/rxjs_helpers.ts create mode 100644 x-pack/plugins/security_solution/server/lib/telemetry/sender_helpers.ts create mode 100644 x-pack/plugins/security_solution/server/lib/telemetry/task_metrics.test.ts create mode 100644 x-pack/plugins/security_solution/server/lib/telemetry/task_metrics.ts create mode 100644 x-pack/plugins/security_solution/server/lib/telemetry/task_metrics.types.ts diff --git a/x-pack/plugins/security_solution/server/integration_tests/__mocks__/endpoint-alert.json b/x-pack/plugins/security_solution/server/integration_tests/__mocks__/endpoint-alert.json new file mode 100644 index 0000000000000..e5ae0ceec9785 --- /dev/null +++ b/x-pack/plugins/security_solution/server/integration_tests/__mocks__/endpoint-alert.json @@ -0,0 +1,196 @@ +{ + "@timestamp": "2024-01-13T01:49:46.2633272Z", + "Endpoint": { + "policy": { + "applied": { + "artifacts": { + "global": { + "identifiers": [ + { + "name": "diagnostic-configuration-v1", + "sha256": "9a0c8808ce6d9b8043cc3033ec55de4fee958dc871680789f699c16e3079cc05" + }, + { + "name": "diagnostic-endpointpe-v4-blocklist", + "sha256": "9b98d31453367a4a4af2f32763ef9d08faadd0520a4c34104ebd89060bcbc87c" + } + ], + "version": "staging.NOT_FOUND-1705109685512" + }, + "user": { + "identifiers": [ + { + "name": "endpoint-eventfilterlist-windows-v1", + "sha256": "d801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658" + }, + { + "name": "endpoint-exceptionlist-windows-v1", + "sha256": "d801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658" + } + ], + "version": "1.0.0" + } + } + } + } + }, + "agent": { + "build": { + "original": "version: 7.17.16, compiled: Mon Nov 13 15:00:00 2023, branch: HEAD, commit: db581d419ce896071e0fcfbec5ae0666297d37dc" + }, + "id": "69bb7a83-c735-4c27-9646-01f697aa3f68", + "type": "endpoint", + "version": "7.17.16" + }, + "cluster_name": "639afbf700044279b8221d6a5f935f33", + "cluster_uuid": "F6paC1IFSXCcT3N9MiWttw", + "data_stream": { + "dataset": "endpoint.alerts", + "namespace": "default", + "type": "logs" + }, + "ecs": { + "version": "1.11.0" + }, + "elastic": { + "agent": { + "id": "69bb7a83-c735-4c27-9646-01f697aa3f68" + } + }, + "event": { + "action": "creation", + "agent_id_status": "verified", + "category": ["malware", "intrusion_detection", "file"], + "code": "malicious_file", + "created": "2024-01-13T01:49:46.2633272Z", + "dataset": "endpoint.alerts", + "id": "NO5fsT3K5DRjdEjO++++++zo", + "ingested": "2024-01-13T01:50:17Z", + "kind": "alert", + "module": "endpoint", + "outcome": "success", + "risk_score": 99, + "sequence": 2382, + "severity": 99, + "type": ["info", "creation", "allowed"] + }, + "file": { + "Ext": { + "code_signature": [ + { + "exists": false + } + ], + "malware_classification": { + "identifier": "endpointpe-v4-model", + "score": 0.9998635053634644, + "threshold": 0.58, + "version": "4.0.38000" + } + }, + "accessed": "2024-01-13T01:49:46.2633272Z", + "created": "2024-01-13T01:49:46.2633272Z", + "directory": "C:\\Windows\\TEMP\\d9a4d415-0a1f-451d-a878-c0753eb653dd", + "extension": "exe", + "hash": { + "md5": "f410cff9e5d18862b08a448c278817f0", + "sha1": "c4c355c5a23454418009401e3a16d8625f081e23", + "sha256": "5a79448a3c3062794851a986f55d118a9a71ca057bfcb0fda1997142125736c2" + }, + "mtime": "2024-01-13T01:49:46.2633272Z", + "name": "sample.exe", + "path": "C:\\Windows\\TEMP\\d9a4d415-0a1f-451d-a878-c0753eb653dd\\sample.exe", + "size": 288256 + }, + "host": { + "os": { + "Ext": { + "variant": "Windows Server 2019 Datacenter" + }, + "family": "windows", + "full": "Windows Server 2019 Datacenter 1809 (10.0.17763.3406)", + "kernel": "1809 (10.0.17763.3406)", + "name": "Windows", + "platform": "windows", + "type": "windows", + "version": "1809 (10.0.17763.3406)" + } + }, + "license": { + "issued_to": "639afbf700044279b8221d6a5f935f33", + "issuer": "elasticsearch", + "status": "active", + "type": "trial", + "uid": "b7f9aac3-751e-4cbb-84a8-1b853c53d197" + }, + "process": { + "Ext": { + "architecture": "x86_64", + "code_signature": [ + { + "exists": true, + "status": "trusted", + "subject_name": "Microsoft Windows", + "trusted": true + } + ], + "token": { + "integrity_level_name": "system" + } + }, + "args": [ + "powershell.exe", + "-NoProfile", + "-NoLogo", + "-ExecutionPolicy", + "Unrestricted", + "-File", + "C:\\Windows\\TEMP\\metadata-scripts2073839487\\windows-startup-script-ps1.ps1" + ], + "command_line": "powershell.exe -NoProfile -NoLogo -ExecutionPolicy Unrestricted -File C:\\Windows\\TEMP\\metadata-scripts2073839487\\windows-startup-script-ps1.ps1", + "entity_id": "NjliYjdhODMtYzczNS00YzI3LTk2NDYtMDFmNjk3YWEzZjY4LTQwODgtMTMzNDk1ODQwNTUuNDIzODIxNDAw", + "executable": "C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe", + "hash": { + "md5": "7353f60b1739074eb17c5f4dddefe239", + "sha1": "6cbce4a295c163791b60fc23d285e6d84f28ee4c", + "sha256": "de96a6e69944335375dc1ac238336066889d9ffc7d73628ef4fe1b1b160ab32c" + }, + "name": "powershell.exe", + "parent": { + "Ext": { + "architecture": "x86_64", + "code_signature": [ + { + "exists": true, + "status": "trusted", + "subject_name": "Google LLC", + "trusted": true + } + ] + }, + "args": [ + "C:\\Program Files\\Google\\Compute Engine\\metadata_scripts\\GCEMetadataScripts.exe", + "startup" + ], + "command_line": "\"C:\\Program Files\\Google\\Compute Engine\\metadata_scripts\\GCEMetadataScripts.exe\" \"startup\"", + "entity_id": "NjliYjdhODMtYzczNS00YzI3LTk2NDYtMDFmNjk3YWEzZjY4LTM4OTItMTMzNDk1ODQwNTQuOTkwOTU1NjAw", + "executable": "C:\\Program Files\\Google\\Compute Engine\\metadata_scripts\\GCEMetadataScripts.exe", + "hash": { + "md5": "98aea13d8067b8f89ee0dce69a154966", + "sha1": "88ceb122a9a4b33674ecdba95784e82ea5be08f6", + "sha256": "590870dc691dd708c875bfc5385a9482fa6ecce0b34d607fe23a71c55b2693ee" + }, + "name": "GCEMetadataScripts.exe", + "pid": 3892, + "uptime": 132 + }, + "pe": { + "original_file_name": "PowerShell.EXE" + }, + "pid": 4088, + "uptime": 131 + }, + "rule": { + "ruleset": "production" + } +} diff --git a/x-pack/plugins/security_solution/server/integration_tests/__mocks__/rule.json b/x-pack/plugins/security_solution/server/integration_tests/__mocks__/rule.json new file mode 100644 index 0000000000000..2b33be991177b --- /dev/null +++ b/x-pack/plugins/security_solution/server/integration_tests/__mocks__/rule.json @@ -0,0 +1,127 @@ +{ + "alert": { + "name": "Azure Automation Runbook Created or Modified", + "tags": ["Elastic", "Cloud", "Azure", "Continuous Monitoring", "SecOps", "Configuration Audit"], + "alertTypeId": "siem.queryRule", + "consumer": "siem", + "params": { + "author": ["Elastic"], + "description": "test", + "ruleId": "id-test", + "falsePositives": [], + "from": "now-25m", + "immutable": true, + "license": "Elastic License v2", + "outputIndex": ".siem-signals-default", + "maxSignals": 100, + "relatedIntegrations": [ + { + "integration": "activitylogs", + "package": "azure", + "version": "^1.0.0" + } + ], + "requiredFields": [ + { + "ecs": false, + "name": "azure.activitylogs.operation_name", + "type": "keyword" + }, + { + "ecs": true, + "name": "event.dataset", + "type": "keyword" + }, + { + "ecs": true, + "name": "event.outcome", + "type": "keyword" + } + ], + "riskScore": 21, + "riskScoreMapping": [], + "setup": "The Azure Fleet integration, Filebeat module, or similarly structured data is required to be compatible with this rule.", + "severity": "low", + "severityMapping": [], + "threat": [], + "timestampOverride": "event.ingested", + "to": "now", + "references": [ + "https://powerzure.readthedocs.io/en/latest/Functions/operational.html#create-backdoor", + "https://github.com/hausec/PowerZure", + "https://posts.specterops.io/attacking-azure-azure-ad-and-introducing-powerzure-ca70b330511a", + "https://azure.microsoft.com/en-in/blog/azure-automation-runbook-management/" + ], + "note": "", + "version": 101, + "exceptionsList": [ + { + "type": "detection", + "id": "123456", + "list_id": "endpoint_trusted_apps", + "namespace_type": "single" + } + ], + "type": "query", + "language": "kuery", + "index": ["filebeat-*", "logs-azure*"], + "query": "" + }, + "schedule": { + "interval": "5m" + }, + "enabled": false, + "actions": [], + "throttle": null, + "notifyWhen": "onActiveAlert", + "apiKeyOwner": null, + "apiKey": null, + "createdBy": "a@b.co", + "updatedBy": "a@b.co", + "createdAt": "2021-11-25T15:44:44.682Z", + "updatedAt": "2023-01-04T14:20:54.727Z", + "muteAll": false, + "mutedInstanceIds": [], + "executionStatus": { + "status": "pending", + "lastExecutionDate": "2021-11-25T15:44:44.682Z", + "error": null + }, + "meta": { + "versionApiKeyLastmodified": "8.5.0" + }, + "legacyId": "123456", + "mapped_params": { + "risk_score": 21, + "severity": "20-low" + }, + "snoozeSchedule": [], + "monitoring": { + "run": { + "history": [], + "calculated_metrics": { + "success_ratio": 0 + }, + "last_run": { + "timestamp": "2021-11-25T15:44:44.682Z", + "metrics": { + "total_search_duration_ms": null, + "total_indexing_duration_ms": null, + "total_alerts_detected": null, + "total_alerts_created": null, + "gap_duration_s": null, + "duration": 2212 + } + } + } + }, + "revision": 101 + }, + "type": "alert", + "references": [], + "managed": false, + "namespaces": ["default"], + "coreMigrationVersion": "8.8.0", + "typeMigrationVersion": "8.8.0", + "updated_at": "2023-01-04T14:20:54.727Z" +} diff --git a/x-pack/plugins/security_solution/server/integration_tests/lib/helpers.ts b/x-pack/plugins/security_solution/server/integration_tests/lib/helpers.ts new file mode 100644 index 0000000000000..20d98303a62a7 --- /dev/null +++ b/x-pack/plugins/security_solution/server/integration_tests/lib/helpers.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 Fs from 'fs'; +import Util from 'util'; +import deepmerge from 'deepmerge'; +import { createTestServers, createRootWithCorePlugins } from '@kbn/core-test-helpers-kbn-server'; +const asyncUnlink = Util.promisify(Fs.unlink); + +/** + * Eventually runs a callback until it succeeds or times out. + * Inspired in https://kotest.io/docs/assertions/eventually.html + * + * @param cb The callback to run/retry + * @param duration The maximum duration to run the callback, default 10000 millisecs + * @param interval The interval between each run, default 100 millisecs + */ +export async function eventually( + cb: () => Promise, + duration: number = 10000, + interval: number = 200 +) { + let elapsed = 0; + + while (true) { + const startedAt: number = performance.now(); + try { + return await cb(); + } catch (e) { + if (elapsed >= duration) { + throw e; + } + } + await new Promise((resolve) => setTimeout(resolve, interval)); + elapsed += performance.now() - startedAt; + } +} + +export async function setupTestServers(logFilePath: string, settings = {}) { + const { startES } = createTestServers({ + adjustTimeout: (t) => jest.setTimeout(t), + settings: { + es: { + license: 'trial', + }, + }, + }); + + const esServer = await startES(); + + const root = createRootWithCorePlugins( + deepmerge( + { + server: { + port: 9991, + }, + logging: { + appenders: { + file: { + type: 'file', + fileName: logFilePath, + layout: { + type: 'json', + }, + }, + }, + root: { + level: 'warn', + }, + loggers: [ + { + name: 'plugins.taskManager', + level: 'warn', + appenders: ['file'], + }, + { + name: 'plugins.securitySolution.telemetry_events', + level: 'all', + appenders: ['file'], + }, + ], + }, + }, + settings + ), + { oss: false } + ); + + await root.preboot(); + const coreSetup = await root.setup(); + const coreStart = await root.start(); + + return { + esServer, + kibanaServer: { + root, + coreSetup, + coreStart, + stop: async () => root.shutdown(), + }, + }; +} + +export async function removeFile(path: string) { + await asyncUnlink(path).catch(() => void 0); +} diff --git a/x-pack/plugins/security_solution/server/integration_tests/lib/telemetry_helpers.ts b/x-pack/plugins/security_solution/server/integration_tests/lib/telemetry_helpers.ts new file mode 100644 index 0000000000000..3eb8d7aca8883 --- /dev/null +++ b/x-pack/plugins/security_solution/server/integration_tests/lib/telemetry_helpers.ts @@ -0,0 +1,269 @@ +/* + * 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 { + CoreStart, + ElasticsearchClient, + KibanaRequest, + SavedObjectsServiceStart, +} from '@kbn/core/server'; +import type { + ExceptionListItemSchema, + ExceptionListSchema, +} from '@kbn/securitysolution-io-ts-list-types'; +import { asyncForEach } from '@kbn/std'; + +import { + createExceptionList, + createExceptionListItem, + deleteExceptionList, + deleteExceptionListItem, +} from '@kbn/lists-plugin/server/services/exception_lists'; +import { ENDPOINT_ARTIFACT_LISTS } from '@kbn/securitysolution-list-constants'; +import { DETECTION_TYPE, NAMESPACE_TYPE } from '@kbn/lists-plugin/common/constants.mock'; +import { TelemetryEventsSender } from '../../lib/telemetry/sender'; +import type { + SecuritySolutionPluginStart, + SecuritySolutionPluginStartDependencies, +} from '../../plugin_contract'; +import type { SecurityTelemetryTask } from '../../lib/telemetry/task'; +import { Plugin as SecuritySolutionPlugin } from '../../plugin'; +import { AsyncTelemetryEventsSender } from '../../lib/telemetry/async_sender'; +import { DEFAULT_DIAGNOSTIC_INDEX } from '../../lib/telemetry/constants'; +import mockEndpointAlert from '../__mocks__/endpoint-alert.json'; +import mockedRule from '../__mocks__/rule.json'; + +export function getTelemetryTasks( + spy: jest.SpyInstance< + SecuritySolutionPluginStart, + [core: CoreStart, plugins: SecuritySolutionPluginStartDependencies] + > +): SecurityTelemetryTask[] { + const pluginInstances = spy.mock.instances; + if (pluginInstances.length === 0) { + throw new Error('Telemetry sender not started'); + } + const plugin = pluginInstances[0]; + if (plugin instanceof SecuritySolutionPlugin) { + /* eslint dot-notation: "off" */ + const sender = plugin['telemetryEventsSender']; + if (sender instanceof TelemetryEventsSender) { + /* eslint dot-notation: "off" */ + return sender['telemetryTasks'] ?? []; + } else { + throw new Error('Telemetry sender not started'); + } + } else { + throw new Error('Telemetry sender not started'); + } +} + +export function getAsyncTelemetryEventSender( + spy: jest.SpyInstance< + SecuritySolutionPluginStart, + [core: CoreStart, plugins: SecuritySolutionPluginStartDependencies] + > +): AsyncTelemetryEventsSender { + const pluginInstances = spy.mock.instances; + if (pluginInstances.length === 0) { + throw new Error('Telemetry sender not started'); + } + const plugin = pluginInstances[0]; + if (plugin instanceof SecuritySolutionPlugin) { + /* eslint dot-notation: "off" */ + const sender = plugin['asyncTelemetryEventsSender']; + if (sender instanceof AsyncTelemetryEventsSender) { + return sender; + } else { + throw new Error('Telemetry sender not started'); + } + } else { + throw new Error('Telemetry sender not started'); + } +} + +export function getTelemetryTask( + tasks: SecurityTelemetryTask[], + id: string +): SecurityTelemetryTask { + const task = tasks.find((t) => t['config'].type === id); + + expect(task).toBeDefined(); + if (task === undefined) { + throw new Error(`Task ${id} not found`); + } + return task; +} + +export async function createMockedEndpointAlert(esClient: ElasticsearchClient) { + const index = `${DEFAULT_DIAGNOSTIC_INDEX.replace('-*', '')}-001`; + + await esClient.indices.create({ index, body: { settings: { hidden: true } } }); + + if (mockEndpointAlert['event']) { + mockEndpointAlert['event']['ingested'] = new Date().toISOString(); + } + + await esClient.create({ + index, + id: 'diagnostic-test-id', + body: mockEndpointAlert, + refresh: 'wait_for', + }); +} + +export async function createMockedAlert( + esClient: ElasticsearchClient, + so: SavedObjectsServiceStart +) { + const alertIndex = so.getIndexForType('alert'); + const aliasInfo = await esClient.indices.getAlias({ index: alertIndex }); + const alias = Object.keys(aliasInfo); + + await esClient.create({ + index: alias[0], + id: 'test-id', + body: mockedRule, + refresh: 'wait_for', + }); +} + +export async function cleanupMockedEndpointAlerts(esClient: ElasticsearchClient) { + const index = `${DEFAULT_DIAGNOSTIC_INDEX.replace('-*', '')}-001`; + + await esClient.indices.delete({ index }).catch(() => { + // ignore errors + }); +} + +export async function cleanupMockedAlerts( + esClient: ElasticsearchClient, + so: SavedObjectsServiceStart +) { + const alertIndex = so.getIndexForType('alert'); + const aliasInfo = await esClient.indices.getAlias({ index: alertIndex }); + const alias = Object.keys(aliasInfo); + + await esClient + .deleteByQuery({ + index: alias[0], + body: { + query: { + match_all: {}, + }, + }, + }) + .catch(() => { + // ignore errors + }); +} + +export async function createMockedExceptionList(so: SavedObjectsServiceStart) { + const type = DETECTION_TYPE; + const listId = ENDPOINT_ARTIFACT_LISTS.trustedApps.id; + + const immutable = true; + const savedObjectsClient = so.getScopedClient(fakeKibanaRequest); + + const namespaceType = NAMESPACE_TYPE; + const name = 'test'; + const description = 'test'; + const meta = undefined; + const user = ''; + const version = 1; + const tags: string[] = []; + const tieBreaker = ''; + + const exceptionList = await createExceptionList({ + listId, + immutable, + savedObjectsClient, + namespaceType, + name, + description, + meta, + user, + tags, + tieBreaker, + type, + version, + }); + + const exceptionListItem = await createExceptionListItem({ + comments: [], + entries: [ + { + field: 'process.hash.md5', + operator: 'included', + type: 'match', + value: 'ae27a4b4821b13cad2a17a75d219853e', + }, + ], + expireTime: undefined, + itemId: '1', + listId, + savedObjectsClient, + namespaceType, + name: 'item1', + osTypes: ['linux'], + description, + meta, + user, + tags, + tieBreaker, + type: 'simple', + }); + + return { exceptionList, exceptionListItem }; +} + +export async function cleanupMockedExceptionLists( + exceptionsList: ExceptionListSchema[], + exceptionsListItem: ExceptionListItemSchema[], + so: SavedObjectsServiceStart +) { + const savedObjectsClient = so.getScopedClient(fakeKibanaRequest); + await asyncForEach(exceptionsListItem, async (exceptionListItem) => { + await deleteExceptionListItem({ + itemId: exceptionListItem.item_id, + id: exceptionListItem.id, + namespaceType: NAMESPACE_TYPE, + savedObjectsClient, + }); + }); + await asyncForEach(exceptionsList, async (exceptionList) => { + await deleteExceptionList({ + listId: exceptionList.list_id, + id: exceptionList.id, + namespaceType: NAMESPACE_TYPE, + savedObjectsClient, + }); + }); +} + +const fakeKibanaRequest = { + headers: {}, + getBasePath: () => '', + path: '/', + route: { settings: {} }, + url: { + href: '/', + }, + raw: { + req: { + url: '/', + }, + }, +} as unknown as KibanaRequest; + +export function getTelemetryTaskType(task: SecurityTelemetryTask): string { + if (task !== null && typeof task === 'object') { + return task['config']['type']; + } else { + return ''; + } +} diff --git a/x-pack/plugins/security_solution/server/integration_tests/telemetry.test.ts b/x-pack/plugins/security_solution/server/integration_tests/telemetry.test.ts new file mode 100644 index 0000000000000..e2357d6d614ed --- /dev/null +++ b/x-pack/plugins/security_solution/server/integration_tests/telemetry.test.ts @@ -0,0 +1,351 @@ +/* + * 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 Path from 'path'; +import axios, { type AxiosRequestConfig } from 'axios'; + +import type { + ExceptionListItemSchema, + ExceptionListSchema, +} from '@kbn/securitysolution-io-ts-list-types'; + +import { ENDPOINT_STAGING } from '@kbn/telemetry-plugin/common/constants'; + +import { eventually, setupTestServers, removeFile } from './lib/helpers'; +import { + cleanupMockedAlerts, + cleanupMockedExceptionLists, + cleanupMockedEndpointAlerts, + createMockedAlert, + createMockedEndpointAlert, + createMockedExceptionList, + getAsyncTelemetryEventSender, + getTelemetryTask, + getTelemetryTaskType, + getTelemetryTasks, +} from './lib/telemetry_helpers'; + +import { + type TestElasticsearchUtils, + type TestKibanaUtils, +} from '@kbn/core-test-helpers-kbn-server'; +import { Plugin as SecuritySolutionPlugin } from '../plugin'; +import { + TaskManagerPlugin, + type TaskManagerStartContract, +} from '@kbn/task-manager-plugin/server/plugin'; +import type { SecurityTelemetryTask } from '../lib/telemetry/task'; +import { TelemetryChannel } from '../lib/telemetry/types'; +import type { AsyncTelemetryEventsSender } from '../lib/telemetry/async_sender'; + +jest.mock('axios'); + +const logFilePath = Path.join(__dirname, 'logs.log'); + +const taskManagerStartSpy = jest.spyOn(TaskManagerPlugin.prototype, 'start'); +const telemetrySenderStartSpy = jest.spyOn(SecuritySolutionPlugin.prototype, 'start'); +const mockedAxiosGet = jest.spyOn(axios, 'get'); +const mockedAxiosPost = jest.spyOn(axios, 'post'); + +describe('telemetry tasks', () => { + let esServer: TestElasticsearchUtils; + let kibanaServer: TestKibanaUtils; + let taskManagerPlugin: TaskManagerStartContract; + let tasks: SecurityTelemetryTask[]; + let asyncTelemetryEventSender: AsyncTelemetryEventsSender; + let exceptionsList: ExceptionListSchema[] = []; + let exceptionsListItem: ExceptionListItemSchema[] = []; + + beforeAll(async () => { + await removeFile(logFilePath); + + const servers = await setupTestServers(logFilePath); + esServer = servers.esServer; + kibanaServer = servers.kibanaServer; + + expect(taskManagerStartSpy).toHaveBeenCalledTimes(1); + taskManagerPlugin = taskManagerStartSpy.mock.results[0].value; + + expect(telemetrySenderStartSpy).toHaveBeenCalledTimes(1); + + tasks = getTelemetryTasks(telemetrySenderStartSpy); + asyncTelemetryEventSender = getAsyncTelemetryEventSender(telemetrySenderStartSpy); + + // update queue config to not wait for a long bufferTimeSpanMillis + asyncTelemetryEventSender.updateQueueConfig(TelemetryChannel.TASK_METRICS, { + bufferTimeSpanMillis: 100, + inflightEventsThreshold: 1_000, + maxPayloadSizeBytes: 1024 * 1024, + }); + }); + + afterAll(async () => { + if (kibanaServer) { + await kibanaServer.stop(); + } + if (esServer) { + await esServer.stop(); + } + }); + + beforeEach(async () => { + jest.clearAllMocks(); + mockAxiosGet(); + }); + + afterEach(async () => { + await cleanupMockedExceptionLists( + exceptionsList, + exceptionsListItem, + kibanaServer.coreStart.savedObjects + ); + await cleanupMockedAlerts( + kibanaServer.coreStart.elasticsearch.client.asInternalUser, + kibanaServer.coreStart.savedObjects + ).then(() => { + exceptionsList = []; + exceptionsListItem = []; + }); + await cleanupMockedEndpointAlerts(kibanaServer.coreStart.elasticsearch.client.asInternalUser); + }); + + describe('detection-rules', () => { + it('should execute when scheduled', async () => { + await mockAndScheduleDetectionRulesTask(); + + // wait until the events are sent to the telemetry server + const body = await eventually(async () => { + const found = mockedAxiosPost.mock.calls.find(([url]) => { + return url.startsWith(ENDPOINT_STAGING) && url.endsWith('security-lists-v2'); + }); + + expect(found).not.toBeFalsy(); + + return JSON.parse((found ? found[1] : '{}') as string); + }); + + expect(body).not.toBeFalsy(); + expect(body.detection_rule).not.toBeFalsy(); + }); + + it('should send task metrics', async () => { + const task = await mockAndScheduleDetectionRulesTask(); + + const requests = await getTaskMetricsRequests(task); + + expect(requests.length).toBeGreaterThan(0); + requests.forEach(({ body }) => { + const asJson = JSON.parse(body); + expect(asJson).not.toBeFalsy(); + expect(asJson.passed).toEqual(true); + }); + }); + }); + + describe('sender configuration', () => { + it('should use legacy sender by default', async () => { + // launch a random task and verify it uses the new configuration + const task = await mockAndScheduleDetectionRulesTask(); + + const requests = await getTaskMetricsRequests(task); + expect(requests.length).toBeGreaterThan(0); + requests.forEach(({ config }) => { + expect(config).not.toBeFalsy(); + if (config && config.headers) { + expect(config.headers['X-Telemetry-Sender']).not.toEqual('async'); + } + }); + }); + + it('should use new sender when configured', async () => { + const configTaskType = 'security:telemetry-configuration'; + const configTask = getTelemetryTask(tasks, configTaskType); + + mockAxiosGet(fakeBufferAndSizesConfigAsyncEnabled); + await eventually(async () => { + await taskManagerPlugin.runSoon(configTask.getTaskId()); + }); + + // wait until the task finishes + await eventually(async () => { + const found = (await taskManagerPlugin.fetch()).docs.find( + (t) => t.taskType === configTaskType + ); + expect(found).toBeFalsy(); + }); + + const task = await mockAndScheduleDetectionRulesTask(); + + const requests = await getTaskMetricsRequests(task); + expect(requests.length).toBeGreaterThan(0); + requests.forEach(({ config }) => { + expect(config).not.toBeFalsy(); + if (config && config.headers) { + expect(config.headers['X-Telemetry-Sender']).toEqual('async'); + } + }); + }); + + it('should update sender queue config', async () => { + const expectedConfig = fakeBufferAndSizesConfigWithQueues.sender_channels['task-metrics']; + const configTaskType = 'security:telemetry-configuration'; + const configTask = getTelemetryTask(tasks, configTaskType); + + mockAxiosGet(fakeBufferAndSizesConfigWithQueues); + await eventually(async () => { + await taskManagerPlugin.runSoon(configTask.getTaskId()); + }); + + await eventually(async () => { + /* eslint-disable dot-notation */ + const taskMetricsConfigAfter = asyncTelemetryEventSender['queues']?.get( + TelemetryChannel.TASK_METRICS + ); + + expect(taskMetricsConfigAfter?.bufferTimeSpanMillis).toEqual( + expectedConfig.buffer_time_span_millis + ); + expect(taskMetricsConfigAfter?.inflightEventsThreshold).toEqual( + expectedConfig.inflight_events_threshold + ); + expect(taskMetricsConfigAfter?.maxPayloadSizeBytes).toEqual( + expectedConfig.max_payload_size_bytes + ); + }); + }); + }); + + describe('endpoint-diagnostics', () => { + it('should execute when scheduled', async () => { + await mockAndScheduleEndpointDiagnosticsTask(); + + // wait until the events are sent to the telemetry server + const body = await eventually(async () => { + const found = mockedAxiosPost.mock.calls.find(([url]) => { + return url.startsWith(ENDPOINT_STAGING) && url.endsWith('alerts-endpoint'); + }); + + expect(found).not.toBeFalsy(); + + return JSON.parse((found ? found[1] : '{}') as string); + }); + + expect(body).not.toBeFalsy(); + expect(body.Endpoint).not.toBeFalsy(); + }); + }); + + async function mockAndScheduleDetectionRulesTask(): Promise { + const task = getTelemetryTask(tasks, 'security:telemetry-detection-rules'); + + // create some data + await createMockedAlert( + kibanaServer.coreStart.elasticsearch.client.asInternalUser, + kibanaServer.coreStart.savedObjects + ); + const { exceptionList, exceptionListItem } = await createMockedExceptionList( + kibanaServer.coreStart.savedObjects + ); + + exceptionsList.push(exceptionList); + exceptionsListItem.push(exceptionListItem); + + // schedule task to run ASAP + await eventually(async () => { + await taskManagerPlugin.runSoon(task.getTaskId()); + }); + + return task; + } + + async function mockAndScheduleEndpointDiagnosticsTask(): Promise { + const task = getTelemetryTask(tasks, 'security:endpoint-diagnostics'); + + await createMockedEndpointAlert(kibanaServer.coreStart.elasticsearch.client.asInternalUser); + + // schedule task to run ASAP + await eventually(async () => { + await taskManagerPlugin.runSoon(task.getTaskId()); + }); + + return task; + } + + function mockAxiosGet(bufferConfig: unknown = fakeBufferAndSizesConfigAsyncDisabled) { + mockedAxiosGet.mockImplementation(async (url: string) => { + if (url.startsWith(ENDPOINT_STAGING) && url.endsWith('ping')) { + return { status: 200 }; + } else if (url.indexOf('kibana/manifest/artifacts') !== -1) { + return { + status: 200, + data: 'x-pack/plugins/security_solution/server/lib/telemetry/__mocks__/kibana-artifacts.zip', + }; + } else if (url.indexOf('telemetry-buffer-and-batch-sizes-v1') !== -1) { + return { + status: 200, + data: bufferConfig, + }; + } + return { status: 404 }; + }); + } + + async function getTaskMetricsRequests(task: SecurityTelemetryTask): Promise< + Array<{ + url: string; + body: string; + config: AxiosRequestConfig | undefined; + }> + > { + return eventually(async () => { + const calls = mockedAxiosPost.mock.calls.flatMap(([url, data, config]) => { + return (data as string).split('\n').map((body) => { + return { url, body, config }; + }); + }); + + const requests = calls.filter(({ url, body }) => { + return ( + body.indexOf(getTelemetryTaskType(task)) !== -1 && + url.startsWith(ENDPOINT_STAGING) && + url.endsWith('task-metrics') + ); + }); + expect(requests.length).toBeGreaterThan(0); + return requests; + }); + } +}); + +const fakeBufferAndSizesConfigAsyncDisabled = { + telemetry_max_buffer_size: 100, + max_security_list_telemetry_batch: 100, + max_endpoint_telemetry_batch: 300, + max_detection_rule_telemetry_batch: 1000, + max_detection_alerts_batch: 50, +}; + +const fakeBufferAndSizesConfigAsyncEnabled = { + ...fakeBufferAndSizesConfigAsyncDisabled, + use_async_sender: true, +}; + +const fakeBufferAndSizesConfigWithQueues = { + ...fakeBufferAndSizesConfigAsyncDisabled, + sender_channels: { + // should be ignored + 'invalid-channel': { + buffer_time_span_millis: 500, + inflight_events_threshold: 10, + max_payload_size_bytes: 20, + }, + 'task-metrics': { + buffer_time_span_millis: 500, + inflight_events_threshold: 10, + max_payload_size_bytes: 20, + }, + }, +}; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/telemetry/utils/get_detecton_rules_preview.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/telemetry/utils/get_detecton_rules_preview.ts index 5797164e8b2ae..30c9211ef4a95 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/telemetry/utils/get_detecton_rules_preview.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/telemetry/utils/get_detecton_rules_preview.ts @@ -8,6 +8,7 @@ import type { Logger } from '@kbn/core/server'; import { PreviewTelemetryEventsSender } from '../../../../telemetry/preview_sender'; +import { PreviewTaskMetricsService } from '../../../../telemetry/preview_task_metrics'; import type { ITelemetryReceiver } from '../../../../telemetry/receiver'; import type { ITelemetryEventsSender } from '../../../../telemetry/sender'; import { createTelemetryDetectionRuleListsTaskConfig } from '../../../../telemetry/tasks/detection_rule'; @@ -28,14 +29,17 @@ export const getDetectionRulesPreview = async ({ }; const taskSender = new PreviewTelemetryEventsSender(logger, telemetrySender); + const taskMetricsService = new PreviewTaskMetricsService(logger, taskSender); const task = createTelemetryDetectionRuleListsTaskConfig(1000); await task.runTask( 'detection-rules-preview', logger, telemetryReceiver, taskSender, + taskMetricsService, taskExecutionPeriod ); const messages = taskSender.getSentMessages(); - return parseNdjson(messages); + const taskMetrics = taskMetricsService.getSentMessages(); + return parseNdjson([...messages, ...taskMetrics]); }; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/telemetry/utils/get_diagnostics_preview.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/telemetry/utils/get_diagnostics_preview.ts index f065d140d8556..ffa035e2571ea 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/telemetry/utils/get_diagnostics_preview.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/telemetry/utils/get_diagnostics_preview.ts @@ -9,6 +9,7 @@ import type { Logger } from '@kbn/core/server'; import { PreviewTelemetryEventsSender } from '../../../../telemetry/preview_sender'; import type { ITelemetryReceiver } from '../../../../telemetry/receiver'; +import { PreviewTaskMetricsService } from '../../../../telemetry/preview_task_metrics'; import type { ITelemetryEventsSender } from '../../../../telemetry/sender'; import { createTelemetryDiagnosticsTaskConfig } from '../../../../telemetry/tasks/diagnostic'; import { parseNdjson } from './parse_ndjson'; @@ -28,14 +29,17 @@ export const getDiagnosticsPreview = async ({ }; const taskSender = new PreviewTelemetryEventsSender(logger, telemetrySender); + const taskMetricsService = new PreviewTaskMetricsService(logger, taskSender); const task = createTelemetryDiagnosticsTaskConfig(); await task.runTask( 'diagnostics-preview', logger, telemetryReceiver, taskSender, + taskMetricsService, taskExecutionPeriod ); const messages = taskSender.getSentMessages(); - return parseNdjson(messages); + const taskMetrics = taskMetricsService.getSentMessages(); + return parseNdjson([...messages, ...taskMetrics]); }; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/telemetry/utils/get_endpoint_preview.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/telemetry/utils/get_endpoint_preview.ts index 008a9d9b1d89d..e2f92811ed0e4 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/telemetry/utils/get_endpoint_preview.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/telemetry/utils/get_endpoint_preview.ts @@ -9,6 +9,7 @@ import type { Logger } from '@kbn/core/server'; import { PreviewTelemetryEventsSender } from '../../../../telemetry/preview_sender'; import type { ITelemetryReceiver } from '../../../../telemetry/receiver'; +import { PreviewTaskMetricsService } from '../../../../telemetry/preview_task_metrics'; import type { ITelemetryEventsSender } from '../../../../telemetry/sender'; import { createTelemetryEndpointTaskConfig } from '../../../../telemetry/tasks/endpoint'; import { parseNdjson } from './parse_ndjson'; @@ -28,14 +29,17 @@ export const getEndpointPreview = async ({ }; const taskSender = new PreviewTelemetryEventsSender(logger, telemetrySender); + const taskMetricsService = new PreviewTaskMetricsService(logger, taskSender); const task = createTelemetryEndpointTaskConfig(1000); await task.runTask( 'endpoint-preview', logger, telemetryReceiver, taskSender, + taskMetricsService, taskExecutionPeriod ); const messages = taskSender.getSentMessages(); - return parseNdjson(messages); + const taskMetrics = taskMetricsService.getSentMessages(); + return parseNdjson([...messages, ...taskMetrics]); }; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/telemetry/utils/get_security_lists_preview.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/telemetry/utils/get_security_lists_preview.ts index 4cb9fec83ff58..9df3637c0ecd0 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/telemetry/utils/get_security_lists_preview.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/telemetry/utils/get_security_lists_preview.ts @@ -9,6 +9,7 @@ import type { Logger } from '@kbn/core/server'; import { PreviewTelemetryEventsSender } from '../../../../telemetry/preview_sender'; import type { ITelemetryReceiver } from '../../../../telemetry/receiver'; +import { PreviewTaskMetricsService } from '../../../../telemetry/preview_task_metrics'; import type { ITelemetryEventsSender } from '../../../../telemetry/sender'; import { createTelemetrySecurityListTaskConfig } from '../../../../telemetry/tasks/security_lists'; import { parseNdjson } from './parse_ndjson'; @@ -28,14 +29,17 @@ export const getSecurityListsPreview = async ({ }; const taskSender = new PreviewTelemetryEventsSender(logger, telemetrySender); + const taskMetricsService = new PreviewTaskMetricsService(logger, taskSender); const task = createTelemetrySecurityListTaskConfig(1000); await task.runTask( 'security-lists-preview', logger, telemetryReceiver, taskSender, + taskMetricsService, taskExecutionPeriod ); const messages = taskSender.getSentMessages(); - return parseNdjson(messages); + const taskMetrics = taskMetricsService.getSentMessages(); + return parseNdjson([...messages, ...taskMetrics]); }; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/query/create_query_alert_type.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/query/create_query_alert_type.test.ts index 13c7e9e53df54..f91f806073ff1 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/query/create_query_alert_type.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/query/create_query_alert_type.test.ts @@ -144,6 +144,6 @@ describe('Custom Query Alerts', () => { await executor({ params }); expect((await ruleDataClient.getWriter()).bulk).toHaveBeenCalled(); - expect(eventsTelemetry.queueTelemetryEvents).toHaveBeenCalled(); + expect(eventsTelemetry.sendAsync).toHaveBeenCalled(); }); }); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/send_telemetry_events.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/send_telemetry_events.ts index 713428ca08557..c72be7edc9be8 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/send_telemetry_events.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/send_telemetry_events.ts @@ -9,6 +9,7 @@ import type { ITelemetryEventsSender } from '../../../telemetry/sender'; import type { TelemetryEvent } from '../../../telemetry/types'; import type { IRuleExecutionLogForExecutors } from '../../rule_monitoring'; import type { SignalSource, SignalSourceHit } from '../types'; +import { TelemetryChannel } from '../../../telemetry/types'; interface SearchResultSource { _source: SignalSource; @@ -70,7 +71,7 @@ export function sendAlertTelemetryEvents( selectedEvents = enrichEndpointAlertsSignalID(selectedEvents, signalIdMap); } try { - eventsTelemetry.queueTelemetryEvents(selectedEvents); + eventsTelemetry.sendAsync(TelemetryChannel.ENDPOINT_ALERTS, selectedEvents); } catch (exc) { ruleExecutionLogger.error(`Queuing telemetry events failed: ${exc}`); } diff --git a/x-pack/plugins/security_solution/server/lib/telemetry/__mocks__/index.ts b/x-pack/plugins/security_solution/server/lib/telemetry/__mocks__/index.ts index a5d38b430ec92..5f430ca9e41e0 100644 --- a/x-pack/plugins/security_solution/server/lib/telemetry/__mocks__/index.ts +++ b/x-pack/plugins/security_solution/server/lib/telemetry/__mocks__/index.ts @@ -5,13 +5,18 @@ * 2.0. */ +import { URL } from 'url'; import moment from 'moment'; import type { ConcreteTaskInstance } from '@kbn/task-manager-plugin/server'; +import type { TelemetryPluginSetup, TelemetryPluginStart } from '@kbn/telemetry-plugin/server'; import { TaskStatus } from '@kbn/task-manager-plugin/server'; import type { TelemetryEventsSender } from '../sender'; import type { ITelemetryReceiver, TelemetryReceiver } from '../receiver'; import type { SecurityTelemetryTaskConfig } from '../task'; import type { PackagePolicy } from '@kbn/fleet-plugin/common/types/models/package_policy'; +import type { ITaskMetricsService } from '../task_metrics.types'; +import { type IUsageCounter } from '@kbn/usage-collection-plugin/server/usage_counters/usage_counter'; +import { type UsageCounters } from '@kbn/usage-collection-plugin/common/types'; import { stubEndpointAlertResponse, stubProcessTree, stubFetchTimelineEvents } from './timeline'; import { stubEndpointMetricsResponse } from './metrics'; import { prebuiltRuleAlertsResponse } from './prebuilt_rule_alerts'; @@ -30,6 +35,7 @@ export const createMockTelemetryEventsSender = ( getTelemetryUsageCluster: jest.fn(), fetchTelemetryUrl: jest.fn(), queueTelemetryEvents: jest.fn(), + sendAsync: jest.fn(), processEvents: jest.fn(), isTelemetryOptedIn: jest.fn().mockReturnValue(enableTelemetry ?? jest.fn()), isTelemetryServicesReachable: jest.fn().mockReturnValue(canConnect ?? jest.fn()), @@ -69,6 +75,34 @@ export const stubLicenseInfo: ESLicense = { start_date_in_millis: -1, }; +export const createMockTelemetryPluginSetup = (): jest.Mocked => { + return { + getTelemetryUrl: jest.fn(() => Promise.resolve(new URL('http://localhost/v3/send'))), + } as unknown as jest.Mocked; +}; + +export const createMockTelemetryPluginStart = (): jest.Mocked => { + return { + getIsOptedIn: jest.fn(() => Promise.resolve(true)), + } as unknown as jest.Mocked; +}; + +export const createMockUsageCounter = (): jest.Mocked => { + return { + incrementCounter: jest.fn((_: UsageCounters.v1.IncrementCounterParams) => {}), + } as unknown as jest.Mocked; +}; + +export const createMockTaskMetrics = (): jest.Mocked => { + return { + incrementCounter: jest.fn((_: UsageCounters.v1.IncrementCounterParams) => {}), + start: jest.fn(() => { + return { name: 'test-trace', startedAt: 0 }; + }), + end: jest.fn(), + } as unknown as jest.Mocked; +}; + export const createMockTelemetryReceiver = ( diagnosticsAlert?: unknown, emptyTimelineTree?: boolean @@ -80,6 +114,7 @@ export const createMockTelemetryReceiver = ( return { start: jest.fn(), fetchClusterInfo: jest.fn().mockReturnValue(stubClusterInfo), + getClusterInfo: jest.fn().mockReturnValue(stubClusterInfo), fetchLicenseInfo: jest.fn().mockReturnValue(stubLicenseInfo), copyLicenseFields: jest.fn(), fetchFleetAgents: jest.fn().mockReturnValue(stubFleetAgentResponse), diff --git a/x-pack/plugins/security_solution/server/lib/telemetry/__mocks__/kibana-artifacts.zip b/x-pack/plugins/security_solution/server/lib/telemetry/__mocks__/kibana-artifacts.zip index 779e8d257b7b0bf2406e31cc64250d2507df4c35..12affbe9861423bd4662abbea45f275a607ffea7 100644 GIT binary patch delta 826 zcmeyvyH`LWz?+#xgaHJk;tV6^h&`VZ%gVqI%eqnGIrHSbOrqRrnK>n?MLC(pCB>8P zGpRcmCmC5LrkYroBpDf|nxEsSf*N}rWz$0BpMo}rKY7? zrX(39o0uD!Sy&jPB%2ye+@ru^ZfVOB zQ)|>)t$n9|o>aeFD|IpP&8>|88fR3P=2UOWbK1E2==Z(sx6H2Jn3|bWz%ze0_t~@# zxpzJXRGz7qc1zq;YO3o0^YmR@{p@wqaxG66Tc^~q$#_p+?`ax+GJ2O;;oQXmSp^p} zC*PHu>R%XB>i_=p=ZqielAcqxPrUcj=7))>?s`)**90vszNG?bW^w;B9|Yby>&C^S znVfw3#*?4FR^4lx*~j}Y_P4achuJchRE~aonRI1$_{?ou-pR(*OXp^{9=m#jXFErI zQ)%3!OU&*UmndGSP1CboxoydW%X73lWG^@TzW3oW^I{xXm7;|k_-ne6#*S=}v z)#+30Hw3&&erUZlwLapD)0-Q&I~Klva6SaW&5T%B1! zrc%7$6#X|_H!O-X(#$OSxFFqTr{j{xAD+!xHmToJ%9jZbBrSmTr)Kd-}bfz8e5PH}000p5&E iBFxxQ|Kx5qT@F|hntXswjH!cl@(ngQwoUAytO5YcQ(PYa delta 647 zcmdlh@P}6-z?+#xgaHIp+7m;$4maA!urM&luxylg&OF(kRc&$_t4=*gN?rHZ3I-;i zIw0l&Qn`tFnQ5uTC3?k~>4z-V=6_aq&A)fHX7dy27cYb!Br-U)o;i51dE5Lw^*@{^ z+>}o|6aKq2Ky}5_#Is&oE+JW_T>5O_uuRw}DUZ zxiZh|M9+OPtd9AS;d|?+?z+SBJZCRQ1`B^0Ph*t$dVvqjr+(*G2mPyfy>%k{*MP9; z;wy4FKUo;Gzw%o7e$HD~;{ zNpEV`$A27cSJh1Id!I}8t_Dy0JBwwZG{9eI*qO*RB_R1s)iyD*dDfM~>B78!W z9QoZQ`76pyVXJ;7Eh;_#`qk>#t7(&O1TtjgWx4!wRaWd=rXn~sH)vY@t8Br>p3MBLz3=NG4~}W~ z|E07jH(v=BTTs@0ZHi*)+B+%t)`_dt*SYi-T5-Gd&uY0=^LOI+Jmw_98^eD>zB z=`*|Y_tFE^6I-q--m1R4`nKH^`RMOFA8W<8%H|$t`yJ#aTCrFEO@KEelL#}mq%`>q ayDkST2~B3?5My#@nJmd6$5zM&N_PN>h8)}g diff --git a/x-pack/plugins/security_solution/server/lib/telemetry/artifact.test.ts b/x-pack/plugins/security_solution/server/lib/telemetry/artifact.test.ts index a4e8d38875b8f..d3aabc5b28d41 100644 --- a/x-pack/plugins/security_solution/server/lib/telemetry/artifact.test.ts +++ b/x-pack/plugins/security_solution/server/lib/telemetry/artifact.test.ts @@ -14,6 +14,10 @@ jest.mock('axios'); const mockedAxios = axios as jest.Mocked; describe('telemetry artifact test', () => { + beforeEach(() => { + mockedAxios.get.mockReset(); + }); + test('start should set manifest url for snapshot version', async () => { const expectedManifestUrl = 'https://artifacts.security.elastic.co/downloads/kibana/manifest/artifacts-8.0.0.zip'; @@ -61,6 +65,7 @@ describe('telemetry artifact test', () => { const artifact = new Artifact(); await artifact.start(mockTelemetryReceiver); const axiosResponse = { + status: 200, data: 'x-pack/plugins/security_solution/server/lib/telemetry/__mocks__/kibana-artifacts.zip', }; mockedAxios.get.mockImplementationOnce(() => Promise.resolve(axiosResponse)); @@ -74,12 +79,14 @@ describe('telemetry artifact test', () => { const artifact = new Artifact(); await artifact.start(mockTelemetryReceiver); const axiosResponse = { + status: 200, data: 'x-pack/plugins/security_solution/server/lib/telemetry/__mocks__/kibana-artifacts.zip', }; mockedAxios.get .mockImplementationOnce(() => Promise.resolve(axiosResponse)) .mockImplementationOnce(() => Promise.resolve({ + status: 200, data: { telemetry_max_buffer_size: 100, max_security_list_telemetry_batch: 100, @@ -89,13 +96,46 @@ describe('telemetry artifact test', () => { }, }) ); - const artifactObject: TelemetryConfiguration = (await artifact.getArtifact( - 'telemetry-buffer-and-batch-sizes-v1' - )) as unknown as TelemetryConfiguration; + const manifest = await artifact.getArtifact('telemetry-buffer-and-batch-sizes-v1'); + expect(manifest).not.toBeFalsy(); + const artifactObject: TelemetryConfiguration = + manifest.data as unknown as TelemetryConfiguration; expect(artifactObject.telemetry_max_buffer_size).toEqual(100); expect(artifactObject.max_security_list_telemetry_batch).toEqual(100); expect(artifactObject.max_endpoint_telemetry_batch).toEqual(300); expect(artifactObject.max_detection_rule_telemetry_batch).toEqual(1_000); expect(artifactObject.max_detection_alerts_batch).toEqual(50); }); + + test('getArtifact should cache response', async () => { + const fakeEtag = '123'; + const axiosResponse = { + status: 200, + data: 'x-pack/plugins/security_solution/server/lib/telemetry/__mocks__/kibana-artifacts.zip', + headers: { etag: fakeEtag }, + }; + const artifact = new Artifact(); + + await artifact.start(createMockTelemetryReceiver()); + + mockedAxios.get + .mockImplementationOnce(() => Promise.resolve(axiosResponse)) + .mockImplementationOnce(() => Promise.resolve({ status: 200, data: {} })) + .mockImplementationOnce(() => Promise.resolve({ status: 304 })); + + let manifest = await artifact.getArtifact('telemetry-buffer-and-batch-sizes-v1'); + expect(manifest).not.toBeFalsy(); + expect(manifest.notModified).toEqual(false); + expect(mockedAxios.get.mock.calls.length).toBe(2); + + manifest = await artifact.getArtifact('telemetry-buffer-and-batch-sizes-v1'); + expect(manifest).not.toBeFalsy(); + expect(manifest.notModified).toEqual(true); + expect(mockedAxios.get.mock.calls.length).toBe(3); + + const [_url, config] = mockedAxios.get.mock.calls[2]; + const headers = config?.headers ?? {}; + expect(headers).not.toBeFalsy(); + expect(headers['If-None-Match']).toEqual(fakeEtag); + }); }); diff --git a/x-pack/plugins/security_solution/server/lib/telemetry/artifact.ts b/x-pack/plugins/security_solution/server/lib/telemetry/artifact.ts index 8d4cd82ebb152..610b6eaac18c4 100644 --- a/x-pack/plugins/security_solution/server/lib/telemetry/artifact.ts +++ b/x-pack/plugins/security_solution/server/lib/telemetry/artifact.ts @@ -6,22 +6,34 @@ */ import axios from 'axios'; +import { cloneDeep } from 'lodash'; import AdmZip from 'adm-zip'; import type { ITelemetryReceiver } from './receiver'; import type { ESClusterInfo } from './types'; export interface IArtifact { start(receiver: ITelemetryReceiver): Promise; - getArtifact(name: string): Promise; + getArtifact(name: string): Promise; getManifestUrl(): string | undefined; } +export interface Manifest { + data: unknown; + notModified: boolean; +} + +interface CacheEntry { + manifest: Manifest; + etag: string; +} + export class Artifact implements IArtifact { private manifestUrl?: string; private readonly CDN_URL = 'https://artifacts.security.elastic.co'; private readonly AXIOS_TIMEOUT_MS = 10_000; private receiver?: ITelemetryReceiver; private esClusterInfo?: ESClusterInfo; + private cache: Map = new Map(); public async start(receiver: ITelemetryReceiver) { this.receiver = receiver; @@ -36,30 +48,85 @@ export class Artifact implements IArtifact { } } - public async getArtifact(name: string): Promise { - if (this.manifestUrl) { - const response = await axios.get(this.manifestUrl, { + public async getArtifact(name: string): Promise { + return axios + .get(this.getManifestUrl(), { + headers: this.headers(name), timeout: this.AXIOS_TIMEOUT_MS, + validateStatus: (status) => status < 500, responseType: 'arraybuffer', + }) + .then(async (response) => { + switch (response.status) { + case 200: + const manifest = { + data: await this.getManifest(name, response.data), + notModified: false, + }; + // only update etag if we got a valid manifest + if (response.headers && response.headers.etag) { + const cacheEntry = { + manifest: { ...manifest, notModified: true }, + etag: response.headers?.etag ?? '', + }; + this.cache.set(name, cacheEntry); + } + return cloneDeep(manifest); + case 304: + return cloneDeep(this.getCachedManifest(name)); + case 404: + throw Error(`No manifest resource found at url: ${this.manifestUrl}`); + default: + throw Error(`Failed to download manifest, unexpected status code: ${response.status}`); + } }); - const zip = new AdmZip(response.data); - const entries = zip.getEntries(); - const manifest = JSON.parse(entries[0].getData().toString()); - const relativeUrl = manifest.artifacts[name]?.relative_url; - if (relativeUrl) { - const url = `${this.CDN_URL}${relativeUrl}`; - const artifactResponse = await axios.get(url, { timeout: this.AXIOS_TIMEOUT_MS }); - return artifactResponse.data; - } else { - throw Error(`No artifact for name ${name}`); - } + } + + public getManifestUrl() { + if (this.manifestUrl) { + return this.manifestUrl; } else { throw Error(`No manifest url for version ${this.esClusterInfo?.version?.number}`); } } - public getManifestUrl() { - return this.manifestUrl; + public getCachedManifest(name: string): Manifest { + const entry = this.cache.get(name); + if (!entry) { + throw Error(`No cached manifest for name ${name}`); + } + return entry.manifest; + } + + private async getManifest(name: string, data: Buffer): Promise { + const zip = new AdmZip(data); + + const manifestFile = zip.getEntries().find((entry) => { + return entry.entryName === 'manifest.json'; + }); + + if (!manifestFile) { + throw Error('No manifest.json in artifact zip'); + } + + const manifest = JSON.parse(manifestFile.getData().toString()); + const relativeUrl = manifest.artifacts[name]?.relative_url; + if (relativeUrl) { + const url = `${this.CDN_URL}${relativeUrl}`; + const artifactResponse = await axios.get(url, { timeout: this.AXIOS_TIMEOUT_MS }); + return artifactResponse.data; + } else { + throw Error(`No artifact for name ${name}`); + } + } + + // morre info https://www.rfc-editor.org/rfc/rfc9110#name-etag + private headers(name: string): Record { + const etag = this.cache.get(name)?.etag; + if (etag) { + return { 'If-None-Match': etag }; + } + return {}; } } diff --git a/x-pack/plugins/security_solution/server/lib/telemetry/async_sender.test.ts b/x-pack/plugins/security_solution/server/lib/telemetry/async_sender.test.ts new file mode 100644 index 0000000000000..5545983ec9c17 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/telemetry/async_sender.test.ts @@ -0,0 +1,1148 @@ +/* + * 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 axios from 'axios'; + +import type { QueueConfig, IAsyncTelemetryEventsSender } from './async_sender.types'; +import { + DEFAULT_QUEUE_CONFIG, + DEFAULT_RETRY_CONFIG, + AsyncTelemetryEventsSender, +} from './async_sender'; +import { TelemetryChannel, TelemetryCounter } from './types'; +import { loggingSystemMock } from '@kbn/core/server/mocks'; +import { + createMockTelemetryReceiver, + createMockTelemetryPluginSetup, + createMockTelemetryPluginStart, + createMockUsageCounter, +} from './__mocks__'; +import { TelemetryEventsSender } from './sender'; + +jest.mock('axios'); +jest.mock('./receiver'); + +describe('AsyncTelemetryEventsSender', () => { + const mockedAxiosPost = jest.spyOn(axios, 'post'); + const mockedAxiosGet = jest.spyOn(axios, 'get'); + const telemetryPluginSetup = createMockTelemetryPluginSetup(); + const telemetryPluginStart = createMockTelemetryPluginStart(); + const receiver = createMockTelemetryReceiver(); + const telemetryUsageCounter = createMockUsageCounter(); + const ch1 = TelemetryChannel.INSIGHTS; + const ch2 = TelemetryChannel.LISTS; + const ch3 = TelemetryChannel.DETECTION_ALERTS; + const ch1Config: QueueConfig = { + bufferTimeSpanMillis: 100, + inflightEventsThreshold: 1000, + maxPayloadSizeBytes: 10_000, + }; + const ch2Config: QueueConfig = { + bufferTimeSpanMillis: 1000, + inflightEventsThreshold: 500, + maxPayloadSizeBytes: 10_000, + }; + const ch3Config: QueueConfig = { + bufferTimeSpanMillis: 5000, + inflightEventsThreshold: 10, + maxPayloadSizeBytes: 10_000, + }; + + let service: IAsyncTelemetryEventsSender; + + beforeEach(() => { + service = new AsyncTelemetryEventsSender(loggingSystemMock.createLogger()); + jest.useFakeTimers({ advanceTimers: true }); + mockedAxiosPost.mockClear(); + telemetryUsageCounter.incrementCounter.mockClear(); + mockedAxiosPost.mockResolvedValue({ status: 201 }); + mockedAxiosGet.mockResolvedValue({ status: 200 }); + }); + + afterEach(() => { + jest.useRealTimers(); + }); + + describe('initialization', () => { + it('uses default configu', async () => { + const events = ['e1', 'e2', 'e3']; + const expectedBody = events.map((e) => JSON.stringify(e)).join('\n'); + + service.setup(DEFAULT_RETRY_CONFIG, DEFAULT_QUEUE_CONFIG, receiver, telemetryPluginSetup); + service.start(telemetryPluginStart); + + service.send(ch1, events); + await jest.advanceTimersByTimeAsync(DEFAULT_QUEUE_CONFIG.bufferTimeSpanMillis * 1.1); + + expect(mockedAxiosPost).toHaveBeenCalledTimes(1); + expect(mockedAxiosPost).toHaveBeenCalledWith( + expect.anything(), + expectedBody, + expect.anything() + ); + + await service.stop(); + expect(mockedAxiosPost).toHaveBeenCalledTimes(1); + }); + + it('does not lose data during startup', async () => { + service.setup(DEFAULT_RETRY_CONFIG, DEFAULT_QUEUE_CONFIG, receiver, telemetryPluginSetup); + + const events = ['e1', 'e2', 'e3']; + const expectedBody = events.map((e) => JSON.stringify(e)).join('\n'); + + service.send(ch1, events); + + await jest.advanceTimersByTimeAsync(DEFAULT_QUEUE_CONFIG.bufferTimeSpanMillis * 1.1); + + expect(mockedAxiosPost).toHaveBeenCalledTimes(0); + + service.start(telemetryPluginStart); + + await jest.advanceTimersByTimeAsync(DEFAULT_QUEUE_CONFIG.bufferTimeSpanMillis * 1.1); + + expect(mockedAxiosPost).toHaveBeenCalledTimes(1); + expect(mockedAxiosPost).toHaveBeenCalledWith( + expect.anything(), + expectedBody, + expect.anything() + ); + + await service.stop(); + expect(mockedAxiosPost).toHaveBeenCalledTimes(1); + }); + + it('should not start without being configured', () => { + expect(() => { + service.start(telemetryPluginStart); + }).toThrow('CREATED: invalid status. Expected [CONFIGURED]'); + }); + + it('should not start twice', () => { + service.setup(DEFAULT_RETRY_CONFIG, DEFAULT_QUEUE_CONFIG, receiver, telemetryPluginSetup); + + service.start(telemetryPluginStart); + + expect(() => { + service.start(telemetryPluginStart); + }).toThrow('STARTED: invalid status. Expected [CONFIGURED]'); + }); + + it('should not send events if the servise is not configured', () => { + expect(() => { + service.send(ch1, ['hello']); + }).toThrow('CREATED: invalid status. Expected [CONFIGURED,STARTED]'); + }); + }); + + describe('simple use cases', () => { + it('should chunk events by size', async () => { + const events = ['aaaaa', 'b', 'c']; + const expectedBodies = [ + events + .slice(0, 2) + .map((e) => JSON.stringify(e)) + .join('\n'), + events + .slice(2) + .map((e) => JSON.stringify(e)) + .join('\n'), + ]; + + service.setup(DEFAULT_RETRY_CONFIG, DEFAULT_QUEUE_CONFIG, receiver, telemetryPluginSetup); + service.updateQueueConfig(ch1, { ...ch1Config, maxPayloadSizeBytes: 10 }); + service.start(telemetryPluginStart); + + // at most 10 bytes per payload (after serialized to JSON): it should send + // two posts: ["aaaaa", "b"] and ["c"] + service.send(ch1, events); + + await service.stop(); + + expect(mockedAxiosPost).toHaveBeenCalledTimes(2); + + expectedBodies.forEach((expectedBody) => { + expect(mockedAxiosPost).toHaveBeenCalledWith( + expect.anything(), + expectedBody, + expect.anything() + ); + }); + }); + + it('should chunk events by size, even if one event is bigger than `maxTelemetryPayloadSizeBytes`', async () => { + service.setup(DEFAULT_RETRY_CONFIG, DEFAULT_QUEUE_CONFIG, receiver, telemetryPluginSetup); + service.updateQueueConfig(ch1, { ...ch1Config, maxPayloadSizeBytes: 3 }); + service.start(telemetryPluginStart); + + // at most 10 bytes per payload (after serialized to JSON): it should + // send two posts: ["aaaaa", "b"] and ["c"] + const events = ['aaaaa', 'b', 'c']; + const expectedBodies = [ + events + .slice(0, 1) + .map((e) => JSON.stringify(e)) + .join('\n'), + events + .slice(1, 2) + .map((e) => JSON.stringify(e)) + .join('\n'), + events + .slice(2) + .map((e) => JSON.stringify(e)) + .join('\n'), + ]; + + service.send(ch1, events); + + await service.stop(); + + expect(mockedAxiosPost).toHaveBeenCalledTimes(3); + + expectedBodies.forEach((expectedBody) => { + expect(mockedAxiosPost).toHaveBeenCalledWith( + expect.anything(), + expectedBody, + expect.anything() + ); + }); + }); + + it('should buffer for a specific time period', async () => { + const bufferTimeSpanMillis = 2000; + + service.setup(DEFAULT_RETRY_CONFIG, DEFAULT_QUEUE_CONFIG, receiver, telemetryPluginSetup); + service.updateQueueConfig(ch1, { ...ch1Config, bufferTimeSpanMillis }); + service.start(telemetryPluginStart); + + const events = ['a', 'b', 'c']; + const expectedBody = events.map((e) => JSON.stringify(e)).join('\n'); + + // send some events + service.send(ch1, events); + + // advance time by less than the buffer time span + await jest.advanceTimersByTimeAsync(bufferTimeSpanMillis * 0.2); + + // check that no events are sent before the buffer time span + expect(mockedAxiosPost).toHaveBeenCalledTimes(0); + + // advance time by more than the buffer time span + await jest.advanceTimersByTimeAsync(bufferTimeSpanMillis * 1.2); + + // check that the events are sent + expect(mockedAxiosPost).toHaveBeenCalledTimes(1); + expect(mockedAxiosPost).toHaveBeenCalledWith( + expect.anything(), + expectedBody, + expect.anything() + ); + + await service.stop(); + + // check that no more events are sent + expect(mockedAxiosPost).toHaveBeenCalledTimes(1); + }); + }); + + describe('error handling', () => { + it('retries when the backend fails', async () => { + mockedAxiosPost + .mockReturnValueOnce(Promise.resolve({ status: 500 })) + .mockReturnValueOnce(Promise.resolve({ status: 500 })) + .mockReturnValue(Promise.resolve({ status: 201 })); + + const bufferTimeSpanMillis = 3; + + service.setup(DEFAULT_RETRY_CONFIG, DEFAULT_QUEUE_CONFIG, receiver, telemetryPluginSetup); + service.updateQueueConfig(ch1, { ...ch1Config, bufferTimeSpanMillis }); + service.start(telemetryPluginStart); + + // send some events + service.send(ch1, ['a']); + + // advance time by more than the retry delay for all the retries + await jest.advanceTimersByTimeAsync( + DEFAULT_RETRY_CONFIG.retryCount * DEFAULT_RETRY_CONFIG.retryDelayMillis + ); + + // check that the events are sent + expect(mockedAxiosPost).toHaveBeenCalledTimes(DEFAULT_RETRY_CONFIG.retryCount); + + await service.stop(); + + // check that no more events are sent + expect(mockedAxiosPost).toHaveBeenCalledTimes(DEFAULT_RETRY_CONFIG.retryCount); + }); + + it('retries runtime errors', async () => { + mockedAxiosPost + .mockReturnValueOnce(Promise.resolve({ status: 500 })) + .mockReturnValueOnce(Promise.resolve({ status: 500 })) + .mockReturnValue(Promise.resolve({ status: 201 })); + + const bufferTimeSpanMillis = 3; + + service.setup(DEFAULT_RETRY_CONFIG, DEFAULT_QUEUE_CONFIG, receiver, telemetryPluginSetup); + service.updateQueueConfig(ch1, { ...ch1Config, bufferTimeSpanMillis }); + service.start(telemetryPluginStart); + + // send some events + service.send(ch1, ['a']); + + // advance time by more than the retry delay for all the retries + await jest.advanceTimersByTimeAsync( + DEFAULT_RETRY_CONFIG.retryCount * DEFAULT_RETRY_CONFIG.retryDelayMillis + ); + + // check that the events are sent + expect(mockedAxiosPost).toHaveBeenCalledTimes(DEFAULT_RETRY_CONFIG.retryCount); + + await service.stop(); + + // check that no more events are sent + expect(mockedAxiosPost).toHaveBeenCalledTimes(DEFAULT_RETRY_CONFIG.retryCount); + }); + + it('only retries `retryCount` times', async () => { + mockedAxiosPost.mockReturnValue(Promise.resolve({ status: 500 })); + const bufferTimeSpanMillis = 100; + + service.setup(DEFAULT_RETRY_CONFIG, DEFAULT_QUEUE_CONFIG, receiver, telemetryPluginSetup); + service.updateQueueConfig(ch1, { ...ch1Config, bufferTimeSpanMillis }); + service.start(telemetryPluginStart); + + // send some events + service.send(ch1, ['a']); + + // advance time by more than the buffer time span + await jest.advanceTimersByTimeAsync( + (DEFAULT_RETRY_CONFIG.retryCount + 1) * DEFAULT_RETRY_CONFIG.retryDelayMillis * 1.2 + ); + + // check that the events are sent + expect(mockedAxiosPost).toHaveBeenCalledTimes(DEFAULT_RETRY_CONFIG.retryCount + 1); + + await service.stop(); + + // check that no more events are sent + expect(mockedAxiosPost).toHaveBeenCalledTimes(DEFAULT_RETRY_CONFIG.retryCount + 1); + }); + + it('should catch fatal errors', async () => { + mockedAxiosPost.mockImplementation(() => { + throw Error('fatal error'); + }); + const bufferTimeSpanMillis = 100; + + service.setup(DEFAULT_RETRY_CONFIG, DEFAULT_QUEUE_CONFIG, receiver, telemetryPluginSetup); + service.updateQueueConfig(ch1, { ...ch1Config, bufferTimeSpanMillis }); + service.start(telemetryPluginStart); + + // send some events + service.send(ch1, ['a']); + + // advance time by more than the buffer time span + await jest.advanceTimersByTimeAsync( + (DEFAULT_RETRY_CONFIG.retryCount + 1) * DEFAULT_RETRY_CONFIG.retryDelayMillis * 1.2 + ); + + // check that the events are sent + expect(mockedAxiosPost).toHaveBeenCalledTimes(DEFAULT_RETRY_CONFIG.retryCount + 1); + + await service.stop(); + + // check that no more events are sent + expect(mockedAxiosPost).toHaveBeenCalledTimes(DEFAULT_RETRY_CONFIG.retryCount + 1); + }); + }); + + describe('throttling', () => { + it('drop events above inflightEventsThreshold', async () => { + const inflightEventsThreshold = 3; + const bufferTimeSpanMillis = 2000; + + service.setup(DEFAULT_RETRY_CONFIG, DEFAULT_QUEUE_CONFIG, receiver, telemetryPluginSetup); + service.updateQueueConfig(ch1, { + ...ch1Config, + bufferTimeSpanMillis, + inflightEventsThreshold, + }); + service.start(telemetryPluginStart); + + // send five events + service.send(ch1, ['a', 'b', 'c', 'd']); + + // check that no events are sent before the buffer time span + expect(mockedAxiosPost).toHaveBeenCalledTimes(0); + + // advance time + await jest.advanceTimersByTimeAsync(bufferTimeSpanMillis * 2); + + // check that only `inflightEventsThreshold` events were sent + expect(mockedAxiosPost).toHaveBeenCalledTimes(1); + expect(mockedAxiosPost).toHaveBeenCalledWith( + expect.anything(), + '"a"\n"b"\n"c"', + expect.anything() + ); + + await service.stop(); + + // check that no more events are sent + expect(mockedAxiosPost).toHaveBeenCalledTimes(1); + }); + + it('do not drop events if they are processed before the next batch', async () => { + const batches = 3; + const inflightEventsThreshold = 3; + const bufferTimeSpanMillis = 2000; + + service.setup(DEFAULT_RETRY_CONFIG, DEFAULT_QUEUE_CONFIG, receiver, telemetryPluginSetup); + service.updateQueueConfig(ch1, { + ...ch1Config, + bufferTimeSpanMillis, + inflightEventsThreshold, + }); + + service.start(telemetryPluginStart); + + // check that no events are sent before the buffer time span + expect(mockedAxiosPost).toHaveBeenCalledTimes(0); + + for (let i = 0; i < batches; i++) { + // send the next batch + service.send(ch1, ['a', 'b', 'c']); + + // advance time + await jest.advanceTimersByTimeAsync(bufferTimeSpanMillis * 2); + } + + expect(mockedAxiosPost).toHaveBeenCalledTimes(batches); + for (let i = 0; i < batches; i++) { + const expected = '"a"\n"b"\n"c"'; + + expect(mockedAxiosPost).toHaveBeenNthCalledWith( + i + 1, + expect.anything(), + expected, + expect.anything() + ); + } + + await service.stop(); + + // check that no more events are sent + expect(mockedAxiosPost).toHaveBeenCalledTimes(batches); + }); + }); + + describe('priority queues', () => { + it('manage multiple queues for a single channel', async () => { + const ch1Events = ['high-a', 'high-b', 'high-c', 'high-d']; + const ch2Events = ['med-a', 'med-b', 'med-c', 'med-d']; + const ch3Events = ['low-a', 'low-b', 'low-c', 'low-d']; + + service.setup(DEFAULT_RETRY_CONFIG, DEFAULT_QUEUE_CONFIG, receiver, telemetryPluginSetup); + service.updateQueueConfig(ch1, ch1Config); + service.updateQueueConfig(ch2, ch2Config); + service.updateQueueConfig(ch3, ch3Config); + service.start(telemetryPluginStart); + + // send low-priority events + service.send(ch3, ch3Events.slice(0, 2)); + + // wait less than low priority latency + await jest.advanceTimersByTimeAsync(ch2Config.bufferTimeSpanMillis); + + // send more low-priority events + service.send(ch3, ch3Events.slice(2, ch3Events.length)); + + // also send mid-priority events + service.send(ch2, ch2Events); + + // and finally send some high-priority events + service.send(ch1, ch1Events); + + // wait a little bit, just the high priority queue latency + await jest.advanceTimersByTimeAsync(ch1Config.bufferTimeSpanMillis); + + // only high priority events should have been sent + expect(mockedAxiosPost).toHaveBeenCalledTimes(1); + expect(mockedAxiosPost).toHaveBeenNthCalledWith( + 1, + expect.anything(), + ch1Events.map((e) => JSON.stringify(e)).join('\n'), + expect.anything() + ); + + // wait just the medium priority queue latency + await jest.advanceTimersByTimeAsync(ch2Config.bufferTimeSpanMillis); + + // only medium priority events should have been sent + expect(mockedAxiosPost).toHaveBeenCalledTimes(2); + expect(mockedAxiosPost).toHaveBeenNthCalledWith( + 2, + expect.anything(), + ch2Events.map((e) => JSON.stringify(e)).join('\n'), + expect.anything() + ); + + // wait more time + await jest.advanceTimersByTimeAsync(ch3Config.bufferTimeSpanMillis); + + // all events should have been sent + expect(mockedAxiosPost).toHaveBeenCalledTimes(3); + expect(mockedAxiosPost).toHaveBeenNthCalledWith( + 3, + expect.anything(), + ch3Events.map((e) => JSON.stringify(e)).join('\n'), + expect.anything() + ); + + // no more events sent after the service was stopped + await service.stop(); + expect(mockedAxiosPost).toHaveBeenCalledTimes(3); + }); + + it('discard events when inflightEventsThreshold is reached and process other queues', async () => { + service.setup(DEFAULT_RETRY_CONFIG, DEFAULT_QUEUE_CONFIG, receiver, telemetryPluginSetup); + service.updateQueueConfig(ch2, ch2Config); + service.updateQueueConfig(ch3, ch3Config); + service.start(telemetryPluginStart); + + const ch2Events = Array.from( + { length: ch2Config.inflightEventsThreshold }, + (_, i) => `ch2-${i}` + ); + // double the inflightEventsThreshold for ch3 events, the service should drop half of them + const ch3Events = Array.from( + { length: ch3Config.inflightEventsThreshold * 2 }, + (_, i) => `ch3-${i}` + ); + + service.send(ch3, ch3Events); + service.send(ch2, ch2Events); + + await jest.advanceTimersByTimeAsync(ch2Config.bufferTimeSpanMillis * 1.2); + + expect(mockedAxiosPost).toHaveBeenCalledTimes(1); + + await jest.advanceTimersByTimeAsync(ch3Config.bufferTimeSpanMillis * 1.2); + + expect(mockedAxiosPost).toHaveBeenCalledTimes(2); + + expect(mockedAxiosPost).toHaveBeenNthCalledWith( + 1, + expect.anything(), + // gets all ch2 events + ch2Events.map((e) => JSON.stringify(e)).join('\n'), + expect.anything() + ); + expect(mockedAxiosPost).toHaveBeenNthCalledWith( + 2, + expect.anything(), + // only got `inflightEventsThreshold` events, the remaining ch3 events were dropped + ch3Events + .slice(0, ch3Config.inflightEventsThreshold) + .map((e) => JSON.stringify(e)) + .join('\n'), + expect.anything() + ); + + await service.stop(); + expect(mockedAxiosPost).toHaveBeenCalledTimes(2); + }); + + it('should manage queue priorities and channels', async () => { + service.setup(DEFAULT_RETRY_CONFIG, DEFAULT_QUEUE_CONFIG, receiver, telemetryPluginSetup); + service.updateQueueConfig(ch2, ch2Config); + service.updateQueueConfig(ch3, ch3Config); + service.start(telemetryPluginStart); + + const cases = [ + { + events: ['ch3-1', 'ch3-2'], + channel: ch3, + wait: ch3Config.bufferTimeSpanMillis * 0.2, + }, + { + events: ['ch2-1', 'ch2-2', 'ch2-3'], + channel: ch2, + wait: ch2Config.bufferTimeSpanMillis * 0.3, + }, + { + events: ['ch2-4', 'ch2-5'], + channel: ch2, + wait: ch2Config.bufferTimeSpanMillis * 0.9, + }, + { + events: ['ch2-6', 'ch2-7', 'ch2-8'], + channel: ch2, + wait: ch2Config.bufferTimeSpanMillis * 0.2, + }, + { + events: ['ch3-3', 'ch3-4', 'ch3-5'], + channel: ch3, + wait: ch3Config.bufferTimeSpanMillis * 1.1, + }, + ]; + + for (let i = 0; i < cases.length; i++) { + const testCase = cases[i]; + + service.send(testCase.channel, testCase.events); + await jest.advanceTimersByTimeAsync(testCase.wait); + } + + expect(mockedAxiosPost).toHaveBeenCalledTimes(3); + + expect(mockedAxiosPost).toHaveBeenNthCalledWith( + 1, + expect.stringMatching(`.*${ch2}.*`), // url contains the channel name + [...cases[1].events, ...cases[2].events].map((e) => JSON.stringify(e)).join('\n'), + expect.anything() + ); + + expect(mockedAxiosPost).toHaveBeenNthCalledWith( + 2, + expect.stringMatching(`.*${ch2}.*`), // url contains the channel name + cases[3].events.map((e) => JSON.stringify(e)).join('\n'), + expect.anything() + ); + + expect(mockedAxiosPost).toHaveBeenNthCalledWith( + 3, + expect.stringMatching(`.*${ch3}.*`), // url contains the channel name + [...cases[0].events, ...cases[4].events].map((e) => JSON.stringify(e)).join('\n'), + expect.anything() + ); + + await service.stop(); + expect(mockedAxiosPost).toHaveBeenCalledTimes(3); + }); + }); + + describe('dynamic configuration', () => { + it('should update default queue config', async () => { + const initialTimeSpan = DEFAULT_QUEUE_CONFIG.bufferTimeSpanMillis; + const bufferTimeSpanMillis = initialTimeSpan * 10; + const events = ['e1', 'e2', 'e3']; + const expectedBody = events.map((e) => JSON.stringify(e)).join('\n'); + + service.setup(DEFAULT_RETRY_CONFIG, DEFAULT_QUEUE_CONFIG, receiver, telemetryPluginSetup); + service.start(telemetryPluginStart); + + service.updateDefaultQueueConfig({ ...DEFAULT_QUEUE_CONFIG, bufferTimeSpanMillis }); + + // send data and wait the initial time span + service.send(ch1, events); + await jest.advanceTimersByTimeAsync(initialTimeSpan * 1.1); + expect(mockedAxiosPost).toHaveBeenCalledTimes(0); + + // wait the new timespan, now we should have data + await jest.advanceTimersByTimeAsync(bufferTimeSpanMillis * 1.1); + expect(mockedAxiosPost).toHaveBeenCalledTimes(1); + expect(mockedAxiosPost).toHaveBeenCalledWith( + expect.anything(), + expectedBody, + expect.anything() + ); + + await service.stop(); + expect(mockedAxiosPost).toHaveBeenCalledTimes(1); + }); + + it('should update buffer time config dinamically', async () => { + const channel = TelemetryChannel.DETECTION_ALERTS; + const bufferTimeSpanMillis = 5001; + const detectionAlertsAfter = { + ...ch1Config, + bufferTimeSpanMillis, + }; + const events = ['a', 'b', 'c']; + const expectedBody = events.map((e) => JSON.stringify(e)).join('\n'); + + service.setup(DEFAULT_RETRY_CONFIG, DEFAULT_QUEUE_CONFIG, receiver, telemetryPluginSetup); + service.updateQueueConfig(channel, ch1Config); + service.start(telemetryPluginStart); + + service.send(channel, events); + + await jest.advanceTimersByTimeAsync(ch1Config.bufferTimeSpanMillis * 1.1); + + expect(mockedAxiosPost).toHaveBeenCalledTimes(1); + expect(mockedAxiosPost).toHaveBeenCalledWith( + expect.anything(), + expectedBody, + expect.anything() + ); + + service.updateQueueConfig(channel, detectionAlertsAfter); + + expect(mockedAxiosPost).toHaveBeenCalledTimes(1); + + service.send(channel, events); + // the old buffer time shouldn't trigger a new buffer (we increased it) + await jest.advanceTimersByTimeAsync(ch1Config.bufferTimeSpanMillis * 1.1); + expect(mockedAxiosPost).toHaveBeenCalledTimes(1); + + // wait more time... + await jest.advanceTimersByTimeAsync(detectionAlertsAfter.bufferTimeSpanMillis); + expect(mockedAxiosPost).toHaveBeenCalledTimes(2); + + expect(mockedAxiosPost).toHaveBeenCalledWith( + expect.anything(), + expectedBody, + expect.anything() + ); + + await service.stop(); + expect(mockedAxiosPost).toHaveBeenCalledTimes(2); + }); + + it('should update max payload size dinamically', async () => { + const channel = TelemetryChannel.DETECTION_ALERTS; + const detectionAlertsBefore = { + bufferTimeSpanMillis: 1000, + inflightEventsThreshold: 10, + maxPayloadSizeBytes: 10, + }; + const detectionAlertsAfter = { + ...detectionAlertsBefore, + maxPayloadSizeBytes: 10_000, + }; + + service.setup(DEFAULT_RETRY_CONFIG, DEFAULT_QUEUE_CONFIG, receiver, telemetryPluginSetup); + service.updateQueueConfig(channel, detectionAlertsBefore); + service.start(telemetryPluginStart); + + service.send(channel, ['aaaaa', 'b', 'c']); + let expectedBodies = ['"aaaaa"\n"b"', '"c"']; + + await jest.advanceTimersByTimeAsync(detectionAlertsBefore.bufferTimeSpanMillis * 1.1); + + expect(mockedAxiosPost).toHaveBeenCalledTimes(2); + expectedBodies.forEach((expectedBody) => { + expect(mockedAxiosPost).toHaveBeenCalledWith( + expect.anything(), + expectedBody, + expect.anything() + ); + }); + + service.updateQueueConfig(channel, detectionAlertsAfter); + + service.send(channel, ['aaaaa', 'b', 'c']); + expectedBodies = ['"aaaaa"\n"b"\n"c"']; + + await jest.advanceTimersByTimeAsync(detectionAlertsAfter.bufferTimeSpanMillis * 1.1); + + expect(mockedAxiosPost).toHaveBeenCalledTimes(3); + expectedBodies.forEach((expectedBody) => { + expect(mockedAxiosPost).toHaveBeenCalledWith( + expect.anything(), + expectedBody, + expect.anything() + ); + }); + + await service.stop(); + }); + + it('should configure a new queue', async () => { + const bufferTimeSpanMillis = DEFAULT_QUEUE_CONFIG.bufferTimeSpanMillis * 10; + const events = ['a', 'b', 'c']; + const expectedBody = events.map((e) => JSON.stringify(e)).join('\n'); + + service.setup(DEFAULT_RETRY_CONFIG, DEFAULT_QUEUE_CONFIG, receiver, telemetryPluginSetup); + service.start(telemetryPluginStart); + + service.send(ch1, events); + + await jest.advanceTimersByTimeAsync(DEFAULT_QUEUE_CONFIG.bufferTimeSpanMillis * 1.1); + + expect(mockedAxiosPost).toHaveBeenCalledTimes(1); + expect(mockedAxiosPost).toHaveBeenCalledWith( + expect.anything(), + expectedBody, + expect.anything() + ); + + service.updateQueueConfig(ch1, { ...DEFAULT_QUEUE_CONFIG, bufferTimeSpanMillis }); + + service.send(ch1, events); + + await jest.advanceTimersByTimeAsync(DEFAULT_QUEUE_CONFIG.bufferTimeSpanMillis * 1.1); + expect(mockedAxiosPost).toHaveBeenCalledTimes(1); + + await jest.advanceTimersByTimeAsync(bufferTimeSpanMillis * 1.1); + + expect(mockedAxiosPost).toHaveBeenCalledTimes(2); + expect(mockedAxiosPost).toHaveBeenCalledWith( + expect.anything(), + expectedBody, + expect.anything() + ); + + await service.stop(); + expect(mockedAxiosPost).toHaveBeenCalledTimes(2); + }); + }); + + describe('http headers', () => { + it('should add X-Telemetry-Sender header', async () => { + service.setup( + DEFAULT_RETRY_CONFIG, + DEFAULT_QUEUE_CONFIG, + receiver, + telemetryPluginSetup, + telemetryUsageCounter + ); + service.start(telemetryPluginStart); + + service.send(ch1, ['a']); + await service.stop(); + + expect(mockedAxiosPost).toHaveBeenCalledTimes(1); + const found = mockedAxiosPost.mock.calls.some( + ([_url, _body, config]) => + config && config.headers && config.headers['X-Telemetry-Sender'] === 'async' + ); + + expect(found).not.toBeFalsy(); + }); + }); + + describe('usage counter', () => { + it('should increment the counter when sending events ok', async () => { + service.setup( + DEFAULT_RETRY_CONFIG, + DEFAULT_QUEUE_CONFIG, + receiver, + telemetryPluginSetup, + telemetryUsageCounter + ); + service.start(telemetryPluginStart); + + service.send(ch1, ['a', 'b', 'c']); + await service.stop(); + + const found = telemetryUsageCounter.incrementCounter.mock.calls.some( + ([param]) => param.counterType === TelemetryCounter.DOCS_SENT && param.incrementBy === 3 + ); + expect(found).not.toBeFalsy(); + }); + + it('should increment the counter when sending events with errors', async () => { + mockedAxiosPost.mockReturnValue(Promise.resolve({ status: 500 })); + + service.setup( + DEFAULT_RETRY_CONFIG, + DEFAULT_QUEUE_CONFIG, + receiver, + telemetryPluginSetup, + telemetryUsageCounter + ); + service.updateQueueConfig(ch1, ch1Config); + service.updateQueueConfig(ch2, ch2Config); + service.updateQueueConfig(ch3, ch3Config); + service.start(telemetryPluginStart); + + service.send(ch1, ['a', 'b', 'c']); + await service.stop(); + + const found = telemetryUsageCounter.incrementCounter.mock.calls.some( + ([param]) => param.counterType === TelemetryCounter.DOCS_LOST && param.incrementBy === 3 + ); + expect(found).not.toBeFalsy(); + }); + + it('should increment the counter when sending events with errors and without errors', async () => { + // retries count is set to 3 + mockedAxiosPost + .mockReturnValueOnce(Promise.resolve({ status: 500 })) + .mockReturnValueOnce(Promise.resolve({ status: 500 })) + .mockReturnValueOnce(Promise.resolve({ status: 500 })) + .mockReturnValueOnce(Promise.resolve({ status: 500 })); + + service.setup( + DEFAULT_RETRY_CONFIG, + DEFAULT_QUEUE_CONFIG, + receiver, + telemetryPluginSetup, + telemetryUsageCounter + ); + service.updateQueueConfig(ch1, ch1Config); + service.updateQueueConfig(ch2, ch2Config); + service.updateQueueConfig(ch3, ch3Config); + service.start(telemetryPluginStart); + + service.send(ch1, ['a', 'b', 'c']); + await jest.advanceTimersByTimeAsync(ch1Config.bufferTimeSpanMillis * 1.1); + service.send(ch1, ['a', 'b']); + await service.stop(); + + const foundLost = telemetryUsageCounter.incrementCounter.mock.calls.some( + ([param]) => param.counterType === TelemetryCounter.DOCS_LOST && param.incrementBy === 3 + ); + expect(foundLost).not.toBeFalsy(); + + const foundSent = telemetryUsageCounter.incrementCounter.mock.calls.some( + ([param]) => param.counterType === TelemetryCounter.DOCS_SENT && param.incrementBy === 2 + ); + expect(foundSent).not.toBeFalsy(); + }); + + it('should increment the counter when drops events', async () => { + const inflightEventsThreshold = 3; + const bufferTimeSpanMillis = 2000; + + service.setup( + DEFAULT_RETRY_CONFIG, + DEFAULT_QUEUE_CONFIG, + receiver, + telemetryPluginSetup, + telemetryUsageCounter + ); + service.updateQueueConfig(ch1, { + ...ch1Config, + bufferTimeSpanMillis, + inflightEventsThreshold, + }); + service.start(telemetryPluginStart); + + // send five events + service.send(ch1, ['a', 'b', 'c', 'd']); + + // check that no events are sent before the buffer time span + expect(mockedAxiosPost).toHaveBeenCalledTimes(0); + + // advance time + await jest.advanceTimersByTimeAsync(bufferTimeSpanMillis * 2); + + // check that only `inflightEventsThreshold` events were sent + expect(mockedAxiosPost).toHaveBeenCalledTimes(1); + expect(mockedAxiosPost).toHaveBeenCalledWith( + expect.anything(), + '"a"\n"b"\n"c"', + expect.anything() + ); + + const found = telemetryUsageCounter.incrementCounter.mock.calls.some( + ([param]) => param.counterType === TelemetryCounter.DOCS_DROPPED && param.incrementBy === 1 + ); + expect(found).not.toBeFalsy(); + + await service.stop(); + + // check that no more events are sent + expect(mockedAxiosPost).toHaveBeenCalledTimes(1); + }); + + it('should increment runtime error counter for expected errors', async () => { + mockedAxiosPost.mockReturnValue(Promise.resolve({ status: 401 })); + + service.setup( + DEFAULT_RETRY_CONFIG, + DEFAULT_QUEUE_CONFIG, + receiver, + telemetryPluginSetup, + telemetryUsageCounter + ); + + service.start(telemetryPluginStart); + + service.send(ch1, ['a']); + + await jest.advanceTimersByTimeAsync(DEFAULT_QUEUE_CONFIG.bufferTimeSpanMillis * 10); + await service.stop(); + + const foundFatal = telemetryUsageCounter.incrementCounter.mock.calls.some( + ([param]) => param.counterType === TelemetryCounter.FATAL_ERROR && param.incrementBy === 1 + ); + expect(foundFatal).toBeFalsy(); + + const foundRuntime = telemetryUsageCounter.incrementCounter.mock.calls.some( + ([param]) => param.counterType === TelemetryCounter.RUNTIME_ERROR && param.incrementBy === 1 + ); + expect(foundRuntime).not.toBeFalsy(); + }); + + it('should increment fatal error counter when applies', async () => { + mockedAxiosPost.mockImplementation(() => { + throw Error('fatal error'); + }); + + service.setup( + DEFAULT_RETRY_CONFIG, + DEFAULT_QUEUE_CONFIG, + receiver, + telemetryPluginSetup, + telemetryUsageCounter + ); + + service.start(telemetryPluginStart); + + service.send(ch1, ['a']); + + await jest.advanceTimersByTimeAsync(DEFAULT_QUEUE_CONFIG.bufferTimeSpanMillis * 10); + await service.stop(); + + const foundFatal = telemetryUsageCounter.incrementCounter.mock.calls.some( + ([param]) => param.counterType === TelemetryCounter.FATAL_ERROR && param.incrementBy === 1 + ); + expect(foundFatal).not.toBeFalsy(); + + const foundRuntime = telemetryUsageCounter.incrementCounter.mock.calls.some( + ([param]) => param.counterType === TelemetryCounter.RUNTIME_ERROR && param.incrementBy === 1 + ); + expect(foundRuntime).toBeFalsy(); + }); + }); + + describe('ITelemetryEventsSender integration', () => { + it('should send events using the async service', async () => { + const serviceV1 = new TelemetryEventsSender(loggingSystemMock.createLogger()); + + service.setup(DEFAULT_RETRY_CONFIG, DEFAULT_QUEUE_CONFIG, receiver, telemetryPluginSetup); + service.start(telemetryPluginStart); + + serviceV1.setup(receiver, telemetryPluginSetup, undefined, telemetryUsageCounter, service); + + const events = ['a', 'b', 'c']; + const expectedBody = events.map((e) => JSON.stringify(e)).join('\n'); + + serviceV1.sendAsync(ch1, events); + + await jest.advanceTimersByTimeAsync(DEFAULT_QUEUE_CONFIG.bufferTimeSpanMillis * 1.1); + + expect(mockedAxiosPost).toHaveBeenCalledTimes(1); + + expect(mockedAxiosPost).toHaveBeenCalledWith( + expect.anything(), + expectedBody, + expect.anything() + ); + + await service.stop(); + serviceV1.stop(); + expect(mockedAxiosPost).toHaveBeenCalledTimes(1); + }); + + it('should configure the default queue config in the async service', async () => { + const initialTimeSpan = DEFAULT_QUEUE_CONFIG.bufferTimeSpanMillis; + const bufferTimeSpanMillis = initialTimeSpan * 10; + const events = ['e1', 'e2', 'e3']; + const expectedBody = events.map((e) => JSON.stringify(e)).join('\n'); + const serviceV1 = new TelemetryEventsSender(loggingSystemMock.createLogger()); + + serviceV1.setup(receiver, telemetryPluginSetup, undefined, telemetryUsageCounter, service); + + service.setup(DEFAULT_RETRY_CONFIG, DEFAULT_QUEUE_CONFIG, receiver, telemetryPluginSetup); + service.start(telemetryPluginStart); + + serviceV1.updateDefaultQueueConfig({ ...DEFAULT_QUEUE_CONFIG, bufferTimeSpanMillis }); + + // send data and wait the initial time span + serviceV1.sendAsync(ch1, events); + await jest.advanceTimersByTimeAsync(initialTimeSpan * 1.1); + expect(mockedAxiosPost).toHaveBeenCalledTimes(0); + + // wait the new timespan, now we should have data + await jest.advanceTimersByTimeAsync(bufferTimeSpanMillis * 1.1); + expect(mockedAxiosPost).toHaveBeenCalledTimes(1); + expect(mockedAxiosPost).toHaveBeenCalledWith( + expect.anything(), + expectedBody, + expect.anything() + ); + + await service.stop(); + serviceV1.stop(); + expect(mockedAxiosPost).toHaveBeenCalledTimes(1); + }); + + it('should configure a queue config in the async service', async () => { + const channel = TelemetryChannel.DETECTION_ALERTS; + const detectionAlertsBefore = { + bufferTimeSpanMillis: 900, + inflightEventsThreshold: 10, + maxPayloadSizeBytes: 10_000, + }; + const detectionAlertsAfter = { + ...detectionAlertsBefore, + bufferTimeSpanMillis: 5001, + }; + const serviceV1 = new TelemetryEventsSender(loggingSystemMock.createLogger()); + + serviceV1.setup(receiver, telemetryPluginSetup, undefined, telemetryUsageCounter, service); + + service.setup(DEFAULT_RETRY_CONFIG, DEFAULT_QUEUE_CONFIG, receiver, telemetryPluginSetup); + service.updateQueueConfig(channel, detectionAlertsBefore); + service.start(telemetryPluginStart); + + serviceV1.sendAsync(channel, ['a', 'b', 'c']); + const expectedBodies = ['"a"\n"b"\n"c"']; + + await jest.advanceTimersByTimeAsync(detectionAlertsBefore.bufferTimeSpanMillis * 1.1); + + expect(mockedAxiosPost).toHaveBeenCalledTimes(1); + expectedBodies.forEach((expectedBody) => { + expect(mockedAxiosPost).toHaveBeenCalledWith( + expect.anything(), + expectedBody, + expect.anything() + ); + }); + + serviceV1.updateQueueConfig(channel, detectionAlertsAfter); + + expect(mockedAxiosPost).toHaveBeenCalledTimes(1); + + serviceV1.sendAsync(channel, ['a', 'b', 'c']); + // the old buffer time shouldn't trigger a new buffer (we increased it) + await jest.advanceTimersByTimeAsync(detectionAlertsBefore.bufferTimeSpanMillis * 1.1); + expect(mockedAxiosPost).toHaveBeenCalledTimes(1); + + // wait more time... + await jest.advanceTimersByTimeAsync(detectionAlertsAfter.bufferTimeSpanMillis); + expect(mockedAxiosPost).toHaveBeenCalledTimes(2); + + expectedBodies.forEach((expectedBody) => { + expect(mockedAxiosPost).toHaveBeenCalledWith( + expect.anything(), + expectedBody, + expect.anything() + ); + }); + + await service.stop(); + serviceV1.stop(); + expect(mockedAxiosPost).toHaveBeenCalledTimes(2); + }); + }); + + describe('simulateSend', () => { + it('should send events using the async service', async () => { + jest.useRealTimers(); + const events = ['a', 'b', 'c']; + const expectedResult = events.map((e) => JSON.stringify(e)); + + service.setup(DEFAULT_RETRY_CONFIG, DEFAULT_QUEUE_CONFIG, receiver, telemetryPluginSetup); + service.start(telemetryPluginStart); + + const result = service.simulateSend(ch1, events); + + await service.stop(); + + // no events sent to the telemetry service + expect(mockedAxiosPost).toHaveBeenCalledTimes(0); + + expect(result).toEqual(expectedResult); + }); + }); +}); diff --git a/x-pack/plugins/security_solution/server/lib/telemetry/async_sender.ts b/x-pack/plugins/security_solution/server/lib/telemetry/async_sender.ts new file mode 100644 index 0000000000000..48afbddc4e1d7 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/telemetry/async_sender.ts @@ -0,0 +1,414 @@ +/* + * 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 axios from 'axios'; +import * as rx from 'rxjs'; +import _, { cloneDeep } from 'lodash'; + +import type { Logger } from '@kbn/core/server'; +import type { TelemetryPluginSetup, TelemetryPluginStart } from '@kbn/telemetry-plugin/server'; +import { type IUsageCounter } from '@kbn/usage-collection-plugin/server/usage_counters/usage_counter'; +import type { ITelemetryReceiver } from './receiver'; +import { + type IAsyncTelemetryEventsSender, + type QueueConfig, + type RetryConfig, +} from './async_sender.types'; +import { TelemetryChannel, TelemetryCounter } from './types'; +import * as collections from './collections_helpers'; +import { CachedSubject, retryOnError$ } from './rxjs_helpers'; +import { SenderUtils } from './sender_helpers'; +import { newTelemetryLogger, type TelemetryLogger } from './helpers'; + +export const DEFAULT_QUEUE_CONFIG: QueueConfig = { + bufferTimeSpanMillis: 30 * 1_000, + inflightEventsThreshold: 1_000, + maxPayloadSizeBytes: 1024 * 1024, // 1MiB +}; +export const DEFAULT_RETRY_CONFIG: RetryConfig = { + retryCount: 3, + retryDelayMillis: 1000, +}; + +export class AsyncTelemetryEventsSender implements IAsyncTelemetryEventsSender { + private retryConfig: RetryConfig | undefined; + private fallbackQueueConfig: QueueConfig | undefined; + private queues: Map | undefined; + + private readonly flush$ = new rx.Subject(); + + private readonly events$ = new rx.Subject(); + + private readonly finished$ = new rx.Subject(); + private cache: CachedSubject | undefined; + + private status: ServiceStatus = ServiceStatus.CREATED; + + private readonly logger: TelemetryLogger; + + private telemetryReceiver?: ITelemetryReceiver; + private telemetrySetup?: TelemetryPluginSetup; + private telemetryUsageCounter?: IUsageCounter; + private senderUtils: SenderUtils | undefined; + + constructor(logger: Logger) { + this.logger = newTelemetryLogger(logger.get('telemetry_events.async_sender')); + } + + public setup( + retryConfig: RetryConfig, + fallbackQueueConfig: QueueConfig, + telemetryReceiver: ITelemetryReceiver, + telemetrySetup?: TelemetryPluginSetup, + telemetryUsageCounter?: IUsageCounter + ): void { + this.logger.l(`Setting up ${AsyncTelemetryEventsSender.name}`); + + this.ensureStatus(ServiceStatus.CREATED); + + this.retryConfig = retryConfig; + this.fallbackQueueConfig = fallbackQueueConfig; + this.queues = new Map(); + this.cache = new CachedSubject(this.events$); + this.telemetryReceiver = telemetryReceiver; + this.telemetrySetup = telemetrySetup; + this.telemetryUsageCounter = telemetryUsageCounter; + + this.updateStatus(ServiceStatus.CONFIGURED); + } + + public start(telemetryStart?: TelemetryPluginStart): void { + this.logger.l(`Starting ${AsyncTelemetryEventsSender.name}`); + + this.ensureStatus(ServiceStatus.CONFIGURED); + + this.senderUtils = new SenderUtils( + this.telemetrySetup, + telemetryStart, + this.telemetryReceiver, + this.telemetryUsageCounter + ); + + this.cache?.stop(); + this.events$ + .pipe( + rx.connect((shared$) => { + const queues$ = Object.values(TelemetryChannel).map((channel) => + this.queue$(shared$, channel, this.sendEvents.bind(this)) + ); + return rx.merge(...queues$); + }) + ) + .subscribe({ + next: (result: Result) => { + if (isFailure(result)) { + this.logger.l( + `Failure! unable to send ${result.events} events to channel "${result.channel}": ${result.message}` + ); + this.senderUtils?.incrementCounter( + TelemetryCounter.DOCS_LOST, + result.events, + result.channel + ); + } else { + this.logger.l(`Success! ${result.events} events sent to channel "${result.channel}"`); + this.senderUtils?.incrementCounter( + TelemetryCounter.DOCS_SENT, + result.events, + result.channel + ); + } + }, + error: (err) => { + this.logger.l(`Unexpected error: "${err}"`); + }, + complete: () => { + this.logger.l('Shutting down'); + this.finished$.next(); + }, + }); + + this.cache?.flush(); + this.updateStatus(ServiceStatus.STARTED); + } + + public async stop(): Promise { + this.logger.l(`Stopping ${AsyncTelemetryEventsSender.name}`); + + this.ensureStatus(ServiceStatus.CONFIGURED, ServiceStatus.STARTED); + + const finishPromise = rx.firstValueFrom(this.finished$); + this.events$.complete(); + this.cache?.stop(); + await finishPromise; + + this.updateStatus(ServiceStatus.CONFIGURED); + } + + public send(channel: TelemetryChannel, events: unknown[]): void { + this.ensureStatus(ServiceStatus.CONFIGURED, ServiceStatus.STARTED); + + events.forEach((event) => { + this.events$.next({ channel, payload: event }); + }); + } + + public simulateSend(channel: TelemetryChannel, events: unknown[]): string[] { + const payloads: string[] = []; + + const localEvents$: rx.Observable = rx.of( + ...events.map((e) => { + return { channel, payload: e }; + }) + ); + + const localSubscription$ = this.queue$(localEvents$, channel, (_ch, p) => { + const result = { events: events.length, channel }; + payloads.push(...p); + return Promise.resolve(result); + }).subscribe(); + + localSubscription$.unsubscribe(); + + return payloads; + } + + public updateQueueConfig(channel: TelemetryChannel, config: QueueConfig): void { + const currentConfig = this.getQueues().get(channel); + if (!_.isEqual(config, currentConfig)) { + this.getQueues().set(channel, cloneDeep(config)); + // flush the queues to get the new configuration asap + this.flush$.next(); + } + } + + public updateDefaultQueueConfig(config: QueueConfig): void { + if (!_.isEqual(config, this.fallbackQueueConfig)) { + this.fallbackQueueConfig = cloneDeep(config); + // flush the queues to get the new configuration asap + this.flush$.next(); + } + } + + // internal methods + private queue$( + upstream$: rx.Observable, + channel: TelemetryChannel, + send: (channel: TelemetryChannel, events: string[]) => Promise + ): rx.Observable { + let inflightEventsCounter: number = 0; + const inflightEvents$: rx.Subject = new rx.Subject(); + + inflightEvents$.subscribe((value) => (inflightEventsCounter += value)); + + return upstream$.pipe( + // only take events for the configured channel + rx.filter((event) => event.channel === channel), + + rx.switchMap((event) => { + if (inflightEventsCounter < this.getConfigFor(channel).inflightEventsThreshold) { + return rx.of(event); + } + this.logger.l( + `>> Dropping event ${event} (channel: ${channel}, inflightEventsCounter: ${inflightEventsCounter})` + ); + this.senderUtils?.incrementCounter(TelemetryCounter.DOCS_DROPPED, 1, channel); + + return rx.EMPTY; + }), + + // update inflight events counter + rx.tap(() => { + inflightEvents$.next(1); + }), + + // buffer events for a while or after a flush$ event is sent (see updateConfig) + rx.bufferWhen(() => + rx.merge(rx.interval(this.getConfigFor(channel).bufferTimeSpanMillis), this.flush$) + ), + + // exclude empty buffers + rx.filter((n: Event[]) => n.length > 0), + + // serialize the payloads + rx.map((events) => events.map((e) => JSON.stringify(e.payload))), + + // chunk by size + rx.map((values) => + collections.chunkedBy( + values, + this.getConfigFor(channel).maxPayloadSizeBytes, + (payload) => payload.length + ) + ), + rx.concatAll(), + + // send events to the telemetry server + rx.concatMap((payloads: string[]) => + retryOnError$( + this.getRetryConfig().retryCount, + this.getRetryConfig().retryDelayMillis, + async () => send(channel, payloads) + ) + ), + + // update inflight events counter + rx.tap((result: Result) => { + inflightEvents$.next(-result.events); + }) + ) as rx.Observable; + } + + private async sendEvents(channel: TelemetryChannel, events: string[]): Promise { + this.logger.l(`Sending ${events.length} telemetry events to channel "${channel}"`); + + try { + const senderMetadata = await this.getSenderMetadata(channel); + + const isTelemetryOptedIn = await senderMetadata.isTelemetryOptedIn(); + if (!isTelemetryOptedIn) { + this.senderUtils?.incrementCounter( + TelemetryCounter.TELEMETRY_OPTED_OUT, + events.length, + channel + ); + + this.logger.l(`Unable to send events to channel "${channel}": Telemetry is not opted-in.`); + throw newFailure('Telemetry is not opted-in', channel, events.length); + } + + const isElasticTelemetryReachable = await senderMetadata.isTelemetryServicesReachable(); + if (!isElasticTelemetryReachable) { + this.logger.l('Telemetry Services are not reachable.'); + this.senderUtils?.incrementCounter( + TelemetryCounter.TELEMETRY_NOT_REACHABLE, + events.length, + channel + ); + + this.logger.l( + `Unable to send events to channel "${channel}": Telemetry services are not reachable.` + ); + throw newFailure('Telemetry Services are not reachable', channel, events.length); + } + + const body = events.join('\n'); + + const telemetryUrl = senderMetadata.telemetryUrl; + + return await axios + .post(telemetryUrl, body, { + headers: { + ...senderMetadata.telemetryRequestHeaders(), + 'X-Telemetry-Sender': 'async', + }, + timeout: 10000, + }) + .then((r) => { + this.senderUtils?.incrementCounter( + TelemetryCounter.HTTP_STATUS, + events.length, + channel, + r.status.toString() + ); + + if (r.status < 400) { + return { events: events.length, channel }; + } else { + this.logger.l(`Unexpected response, got ${r.status}`); + throw newFailure(`Got ${r.status}`, channel, events.length); + } + }) + .catch((err) => { + this.senderUtils?.incrementCounter( + TelemetryCounter.RUNTIME_ERROR, + events.length, + channel + ); + + this.logger.l(`Runtime error: ${err.message}`); + throw newFailure(`Error posting events: ${err}`, channel, events.length); + }); + } catch (err: unknown) { + if (isFailure(err)) { + throw err; + } else { + this.senderUtils?.incrementCounter(TelemetryCounter.FATAL_ERROR, events.length, channel); + throw newFailure(`Unexpected error posting events: ${err}`, channel, events.length); + } + } + } + + private getQueues(): Map { + if (this.queues === undefined) throw new Error('Service not initialized'); + return this.queues; + } + + private getConfigFor(channel: TelemetryChannel): QueueConfig { + const config = this.queues?.get(channel) ?? this.fallbackQueueConfig; + if (config === undefined) throw new Error(`No queue config found for channel "${channel}"`); + return config; + } + + private async getSenderMetadata(channel: TelemetryChannel) { + if (this.senderUtils === undefined) throw new Error('Service not initialized'); + return this.senderUtils?.fetchSenderMetadata(channel); + } + + private getRetryConfig(): RetryConfig { + if (this.retryConfig === undefined) throw new Error('Service not initialized'); + return this.retryConfig; + } + + private ensureStatus(...expected: ServiceStatus[]): void { + if (!expected.includes(this.status)) { + throw new Error(`${this.status}: invalid status. Expected [${expected.join(',')}]`); + } + } + + private updateStatus(newStatus: ServiceStatus): void { + this.status = newStatus; + } +} + +function newFailure(message: string, channel: TelemetryChannel, events: number): Failure { + const failure: Failure = { name: 'Failure', message, channel, events }; + return failure; +} + +function isFailure(result: unknown): result is Failure { + return ( + result !== null && + typeof result === 'object' && + 'name' in result && + 'message' in result && + 'events' in result && + 'channel' in result + ); +} + +interface Event { + channel: TelemetryChannel; + payload: unknown; +} + +type Result = Success | Failure; + +interface Success { + events: number; + channel: TelemetryChannel; +} + +interface Failure extends Error { + events: number; + channel: TelemetryChannel; +} + +export enum ServiceStatus { + CREATED = 'CREATED', + CONFIGURED = 'CONFIGURED', + STARTED = 'STARTED', +} diff --git a/x-pack/plugins/security_solution/server/lib/telemetry/async_sender.types.ts b/x-pack/plugins/security_solution/server/lib/telemetry/async_sender.types.ts new file mode 100644 index 0000000000000..249493cfdbfc8 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/telemetry/async_sender.types.ts @@ -0,0 +1,55 @@ +/* + * 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 { TelemetryPluginSetup, TelemetryPluginStart } from '@kbn/telemetry-plugin/server'; +import { type IUsageCounter } from '@kbn/usage-collection-plugin/server/usage_counters/usage_counter'; +import { type TelemetryChannel } from './types'; +import type { ITelemetryReceiver } from './receiver'; + +/** + * This service sends telemetry events to the telemetry service asynchronously. Managing + * different configurations per channel and changing them dynamically without restarting + * the service is possible. + */ +export interface IAsyncTelemetryEventsSender { + setup: ( + retryConfig: RetryConfig, + fallbackQueueConfig: QueueConfig, + telemetryReceiver: ITelemetryReceiver, + telemetrySetup?: TelemetryPluginSetup, + telemetryUsageCounter?: IUsageCounter + ) => void; + start: (telemetryStart?: TelemetryPluginStart) => void; + stop: () => Promise; + send: (channel: TelemetryChannel, events: unknown[]) => void; + simulateSend: (channel: TelemetryChannel, events: unknown[]) => string[]; + updateQueueConfig: (channel: TelemetryChannel, config: QueueConfig) => void; + updateDefaultQueueConfig: (config: QueueConfig) => void; +} + +/** + * Values used to configure each queue. + * + * @property bufferTimeSpanMillis - The time span to buffer events before sending them. + * @property inflightEventsThreshold - The maximum number of events that can be buffered at the same time, waiting to be sent. + * @property maxPayloadSizeBytes - The maximum size of the payload sent to the server, in bytes. + */ +export interface QueueConfig { + bufferTimeSpanMillis: number; + inflightEventsThreshold: number; + maxPayloadSizeBytes: number; +} + +/** + * Values used to configure the retry logic. + * + * @property retryCount - The number of times to retry before propagate the error. + * @property retryDelayMillis - The delay between retries, in milliseconds. + */ +export interface RetryConfig { + retryCount: number; + retryDelayMillis: number; +} diff --git a/x-pack/plugins/security_solution/server/lib/telemetry/collections_helpers.test.ts b/x-pack/plugins/security_solution/server/lib/telemetry/collections_helpers.test.ts new file mode 100644 index 0000000000000..3d67d6cd22b17 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/telemetry/collections_helpers.test.ts @@ -0,0 +1,81 @@ +/* + * 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 { chunked, chunkedBy } from './collections_helpers'; + +describe('telemetry.utils.chunked', () => { + it('should chunk simple case', async () => { + const input = [1, 2, 3, 4, 5, 6, 7, 8, 9]; + const output = chunked(input, 3); + expect(output).toEqual([ + [1, 2, 3], + [4, 5, 6], + [7, 8, 9], + ]); + }); + + it('should chunk with remainder', async () => { + const input = [1, 2, 3, 4, 5, 6, 7, 8, 9]; + const output = chunked(input, 4); + expect(output).toEqual([[1, 2, 3, 4], [5, 6, 7, 8], [9]]); + }); + + it('should chunk with empty list', async () => { + const input: unknown[] = []; + const output = chunked(input, 4); + expect(output).toEqual([]); + }); + + it('should chunk with single element', async () => { + const input = [1]; + const output = chunked(input, 4); + expect(output).toEqual([[1]]); + }); + + it('should chunk with single element and chunk size 1', async () => { + const input = [1]; + const output = chunked(input, 1); + expect(output).toEqual([[1]]); + }); + + it('should chunk arrays smaller than the chunk size', async () => { + const input = [1]; + const output = chunked(input, 10); + expect(output).toEqual([[1]]); + }); +}); + +describe('telemetry.utils.chunkedBy', () => { + it('should chunk simple case', async () => { + const input = ['aa', 'b', 'ccc', 'ddd']; + const output = chunkedBy(input, 3, (v) => v.length); + expect(output).toEqual([['aa', 'b'], ['ccc'], ['ddd']]); + }); + + it('should chunk with remainder', async () => { + const input = ['aaa', 'b']; + const output = chunkedBy(input, 3, (v) => v.length); + expect(output).toEqual([['aaa'], ['b']]); + }); + + it('should chunk with empty list', async () => { + const input: string[] = []; + const output = chunkedBy(input, 3, (v) => v.length); + expect(output).toEqual([]); + }); + + it('should chunk with single element smaller than max weight', async () => { + const input = ['aa']; + const output = chunkedBy(input, 3, (v) => v.length); + expect(output).toEqual([['aa']]); + }); + + it('should chunk with single element bigger than max weight', async () => { + const input = ['aaaa']; + const output = chunkedBy(input, 3, (v) => v.length); + expect(output).toEqual([['aaaa']]); + }); +}); diff --git a/x-pack/plugins/security_solution/server/lib/telemetry/collections_helpers.ts b/x-pack/plugins/security_solution/server/lib/telemetry/collections_helpers.ts new file mode 100644 index 0000000000000..a104ea1d55bf8 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/telemetry/collections_helpers.ts @@ -0,0 +1,65 @@ +/* + * 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. + */ + +/** + * Splits the list into a list of lists each not exceeding the given size. + * The order of the elements is preserved. + * + * @param list - The list to split + * @param size - The maximum size of each chunk + * + * @returns - The list of chunks + */ +export const chunked = function (list: T[], size: number): T[][] { + return chunkedBy(list, size, () => 1); +}; + +/** + * Splits the list into a list of lists each not exceeding the given size. + * The size of each element is determined by the weight function, that is called + * for each element in the list. + * The sum of the weights of the elements in each chunk will not exceed the given size. + * The order of the elements is preserved. + * + * @param list - The list to split + * @param size - The maximum size of each chunk + * @param weight - The function that determines the weight of each element + * @returns - The list of chunks + */ +export const chunkedBy = function (list: T[], size: number, weight: (v: T) => number): T[][] { + function chunk(acc: Chunked, value: T): Chunked { + const currentWeight = weight(value); + if (acc.weight + currentWeight <= size) { + acc.current.push(value); + acc.weight += currentWeight; + } else { + acc.chunks.push(acc.current); + acc.current = [value]; + acc.weight = currentWeight; + } + return acc; + } + + return list.reduce(chunk, new Chunked()).flush(); +}; + +/** + * Helper class used internally. + */ +class Chunked { + public weight: number = 0; + + constructor(public chunks: T[][] = [], public current: T[] = []) {} + + public flush(): T[][] { + if (this.current.length !== 0) { + this.chunks.push(this.current); + this.current = []; + } + return this.chunks.filter((chunk) => chunk.length > 0); + } +} diff --git a/x-pack/plugins/security_solution/server/lib/telemetry/configuration.ts b/x-pack/plugins/security_solution/server/lib/telemetry/configuration.ts index f1957705dcfd8..d5c321ec8bc13 100644 --- a/x-pack/plugins/security_solution/server/lib/telemetry/configuration.ts +++ b/x-pack/plugins/security_solution/server/lib/telemetry/configuration.ts @@ -5,17 +5,25 @@ * 2.0. */ -class TelemetryConfiguration { +import type { TelemetrySenderChannelConfiguration } from './types'; + +class TelemetryConfigurationDTO { private readonly DEFAULT_TELEMETRY_MAX_BUFFER_SIZE = 100; private readonly DEFAULT_MAX_SECURITY_LIST_TELEMETRY_BATCH = 100; private readonly DEFAULT_MAX_ENDPOINT_TELEMETRY_BATCH = 300; private readonly DEFAULT_MAX_DETECTION_RULE_TELEMETRY_BATCH = 1_000; private readonly DEFAULT_MAX_DETECTION_ALERTS_BATCH = 50; + private readonly DEFAULT_ASYNC_SENDER = false; + private readonly DEFAULT_SENDER_CHANNELS = {}; private _telemetry_max_buffer_size = this.DEFAULT_TELEMETRY_MAX_BUFFER_SIZE; private _max_security_list_telemetry_batch = this.DEFAULT_MAX_SECURITY_LIST_TELEMETRY_BATCH; private _max_endpoint_telemetry_batch = this.DEFAULT_MAX_ENDPOINT_TELEMETRY_BATCH; private _max_detection_rule_telemetry_batch = this.DEFAULT_MAX_DETECTION_RULE_TELEMETRY_BATCH; private _max_detection_alerts_batch = this.DEFAULT_MAX_DETECTION_ALERTS_BATCH; + private _use_async_sender = this.DEFAULT_ASYNC_SENDER; + private _sender_channels: { + [key: string]: TelemetrySenderChannelConfiguration; + } = this.DEFAULT_SENDER_CHANNELS; public get telemetry_max_buffer_size(): number { return this._telemetry_max_buffer_size; @@ -57,6 +65,22 @@ class TelemetryConfiguration { this._max_detection_alerts_batch = num; } + public get use_async_sender(): boolean { + return this._use_async_sender; + } + + public set use_async_sender(num: boolean) { + this._use_async_sender = num; + } + + public set sender_channels(config: { [key: string]: TelemetrySenderChannelConfiguration }) { + this._sender_channels = config; + } + + public get sender_channels(): { [key: string]: TelemetrySenderChannelConfiguration } { + return this._sender_channels; + } + public resetAllToDefault() { this._telemetry_max_buffer_size = this.DEFAULT_TELEMETRY_MAX_BUFFER_SIZE; this._max_security_list_telemetry_batch = this.DEFAULT_MAX_SECURITY_LIST_TELEMETRY_BATCH; @@ -66,4 +90,4 @@ class TelemetryConfiguration { } } -export const telemetryConfiguration = new TelemetryConfiguration(); +export const telemetryConfiguration = new TelemetryConfigurationDTO(); diff --git a/x-pack/plugins/security_solution/server/lib/telemetry/helpers.test.ts b/x-pack/plugins/security_solution/server/lib/telemetry/helpers.test.ts index e01eb21cbe68c..8d19d653cf5ed 100644 --- a/x-pack/plugins/security_solution/server/lib/telemetry/helpers.test.ts +++ b/x-pack/plugins/security_solution/server/lib/telemetry/helpers.test.ts @@ -24,7 +24,6 @@ import { formatValueListMetaData, tlog, setIsElasticCloudDeployment, - createTaskMetric, processK8sUsernames, } from './helpers'; import type { ESClusterInfo, ESLicense, ExceptionListItem } from './types'; @@ -944,47 +943,6 @@ describe('test tlog', () => { }); }); -// FLAKY: https://github.com/elastic/kibana/issues/141356 -describe.skip('test create task metrics', () => { - test('can succeed when all parameters are given', async () => { - const stubTaskName = 'test'; - const stubPassed = true; - const stubStartTime = Date.now(); - await new Promise((r) => setTimeout(r, 11)); - const response = createTaskMetric(stubTaskName, stubPassed, stubStartTime); - const { - time_executed_in_ms: timeExecutedInMs, - start_time: startTime, - end_time: endTime, - ...rest - } = response; - expect(timeExecutedInMs).toBeGreaterThan(10); - expect(rest).toEqual({ - name: 'test', - passed: true, - }); - }); - - test('can succeed when error given', async () => { - const stubTaskName = 'test'; - const stubPassed = false; - const stubStartTime = Date.now(); - const errorMessage = 'failed'; - const response = createTaskMetric(stubTaskName, stubPassed, stubStartTime, errorMessage); - const { - time_executed_in_ms: timeExecutedInMs, - start_time: startTime, - end_time: endTime, - ...rest - } = response; - expect(rest).toEqual({ - name: 'test', - passed: false, - error_message: 'failed', - }); - }); -}); - describe('Pii is removed from a kubernetes prebuilt rule alert', () => { test('a document without the sensitive values is ignored', async () => { const clusterUuid = '7c5f1d31-ce87-4090-8dbf-decaac0261ca'; diff --git a/x-pack/plugins/security_solution/server/lib/telemetry/helpers.ts b/x-pack/plugins/security_solution/server/lib/telemetry/helpers.ts index 32ac1cf7f4dd0..de49dad3b1c26 100644 --- a/x-pack/plugins/security_solution/server/lib/telemetry/helpers.ts +++ b/x-pack/plugins/security_solution/server/lib/telemetry/helpers.ts @@ -22,7 +22,6 @@ import type { ExceptionListItem, ExtraInfo, ListTemplate, - TaskMetric, TelemetryEvent, TimeFrame, TimelineResult, @@ -294,20 +293,18 @@ export const tlog = (logger: Logger, message: string) => { } }; -export const createTaskMetric = ( - name: string, - passed: boolean, - startTime: number, - errorMessage?: string -): TaskMetric => { - const endTime = Date.now(); +export interface TelemetryLogger extends Logger { + l: (message: string) => void; +} + +export const newTelemetryLogger = (logger: Logger): TelemetryLogger => { return { - name, - passed, - time_executed_in_ms: endTime - startTime, - start_time: startTime, - end_time: endTime, - error_message: errorMessage, + ...logger, + error: logger.error, + info: logger.info, + debug: logger.debug, + warn: logger.warn, + l: (message: string) => tlog(logger, message), }; }; @@ -366,14 +363,12 @@ export const ranges = ( }; export class TelemetryTimelineFetcher { - startTime: number; private receiver: ITelemetryReceiver; private extraInfo: Promise; private timeFrame: TimeFrame; constructor(receiver: ITelemetryReceiver) { this.receiver = receiver; - this.startTime = Date.now(); this.extraInfo = this.lookupExtraInfo(); this.timeFrame = this.calculateTimeFrame(); } diff --git a/x-pack/plugins/security_solution/server/lib/telemetry/preview_sender.ts b/x-pack/plugins/security_solution/server/lib/telemetry/preview_sender.ts index a26bbef5ee9d7..fc195098787dd 100644 --- a/x-pack/plugins/security_solution/server/lib/telemetry/preview_sender.ts +++ b/x-pack/plugins/security_solution/server/lib/telemetry/preview_sender.ts @@ -16,9 +16,10 @@ import type { TaskManagerStartContract, } from '@kbn/task-manager-plugin/server'; import type { ITelemetryEventsSender } from './sender'; -import type { TelemetryEvent } from './types'; +import { TelemetryChannel, type TelemetryEvent } from './types'; import type { ITelemetryReceiver } from './receiver'; import { tlog } from './helpers'; +import type { QueueConfig } from './async_sender.types'; /** * Preview telemetry events sender for the telemetry route. @@ -28,7 +29,9 @@ export class PreviewTelemetryEventsSender implements ITelemetryEventsSender { /** Inner composite telemetry events sender */ private composite: ITelemetryEventsSender; - /** Axios local instance */ + /** + * Axios local instance + * @deprecated `IAsyncTelemetryEventsSender` has a dedicated method for preview. */ private axiosInstance = axios.create(); /** Last sent message */ @@ -115,9 +118,9 @@ export class PreviewTelemetryEventsSender implements ITelemetryEventsSender { } public async queueTelemetryEvents(events: TelemetryEvent[]) { - const result = this.composite.queueTelemetryEvents(events); - await this.composite.sendIfDue(this.axiosInstance); - return result; + const result = this.composite.simulateSendAsync(TelemetryChannel.ENDPOINT_ALERTS, events); + + this.sentMessages = [...this.sentMessages, ...result]; } public getTelemetryUsageCluster(): UsageCounter | undefined { @@ -141,11 +144,34 @@ export class PreviewTelemetryEventsSender implements ITelemetryEventsSender { } public async sendOnDemand(channel: string, toSend: unknown[]) { - const result = await this.composite.sendOnDemand(channel, toSend, this.axiosInstance); - return result; + const ch = Object.values(TelemetryChannel).find((c) => c === channel); + if (ch === undefined) { + throw new Error(`Channel ${channel} not found`); + } + const result = this.composite.simulateSendAsync(ch, toSend); + + this.sentMessages = [...this.sentMessages, ...result]; + + return Promise.resolve(); } public getV3UrlFromV2(v2url: string, channel: string): string { return this.composite.getV3UrlFromV2(v2url, channel); } + + public sendAsync(channel: TelemetryChannel, events: unknown[]): void { + this.composite.sendAsync(channel, events); + } + + public simulateSendAsync(channel: TelemetryChannel, events: unknown[]): string[] { + return this.composite.simulateSendAsync(channel, events); + } + + public updateQueueConfig(channel: TelemetryChannel, config: QueueConfig): void { + this.composite.updateQueueConfig(channel, config); + } + + public updateDefaultQueueConfig(config: QueueConfig): void { + this.composite.updateDefaultQueueConfig(config); + } } diff --git a/x-pack/plugins/security_solution/server/lib/telemetry/preview_task_metrics.ts b/x-pack/plugins/security_solution/server/lib/telemetry/preview_task_metrics.ts new file mode 100644 index 0000000000000..96b23e1ebe087 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/telemetry/preview_task_metrics.ts @@ -0,0 +1,52 @@ +/* + * 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 { Logger } from '@kbn/core/server'; +import { TelemetryChannel } from './types'; +import type { ITaskMetricsService, TaskMetric, Trace } from './task_metrics.types'; +import { TaskMetricsService } from './task_metrics'; +import type { ITelemetryEventsSender } from './sender'; + +/** + * Preview telemetry events sender for the telemetry route. + * @see telemetry_detection_rules_preview_route + */ +export class PreviewTaskMetricsService implements ITaskMetricsService { + /** Last sent message */ + private sentMessages: string[] = []; + + /** Logger for this class */ + private readonly logger: Logger; + + private readonly composite: TaskMetricsService; + + constructor(logger: Logger, private readonly sender: ITelemetryEventsSender) { + this.logger = logger; + this.composite = new TaskMetricsService(logger, sender); + } + + public getSentMessages() { + return this.sentMessages; + } + + public start(name: string): Trace { + this.logger.error('Simulating TaskMetricsService.start'); + return this.composite.start(name); + } + + public createTaskMetric(trace: Trace, error?: Error): TaskMetric { + this.logger.error('Simulating TaskMetricsService.createTaskMetric'); + return this.composite.createTaskMetric(trace, error); + } + + public async end(trace: Trace, error?: Error): Promise { + this.logger.error('Simulating TaskMetricsService.end'); + const metric = this.composite.createTaskMetric(trace, error); + const result = this.sender.simulateSendAsync(TelemetryChannel.TASK_METRICS, [metric]); + this.sentMessages = [...this.sentMessages, ...result]; + } +} diff --git a/x-pack/plugins/security_solution/server/lib/telemetry/rxjs_helpers.test.ts b/x-pack/plugins/security_solution/server/lib/telemetry/rxjs_helpers.test.ts new file mode 100644 index 0000000000000..cc95172b6bcc6 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/telemetry/rxjs_helpers.test.ts @@ -0,0 +1,166 @@ +/* + * 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 { CachedSubject, retryOnError$ } from './rxjs_helpers'; +import * as rx from 'rxjs'; + +describe('telemetry.helpers.rxjs.retryOnError$', () => { + const retries = 5; + const delay = 100; + + beforeEach(() => { + jest.useFakeTimers({ advanceTimers: true }); + }); + + afterEach(() => { + jest.useRealTimers(); + }); + + it('should not retry if the computation does not fail', async () => { + const callback = jest.fn(() => 'success'); + + retryOnError$(1, 100, callback).subscribe({ + next: (response) => { + expect(response).toBe('success'); + }, + error: (err) => { + throw new Error(`Unexpected error: ${err}`); + }, + }); + + await jest.advanceTimersByTimeAsync(delay * 1.1); + expect(callback).toHaveBeenCalledTimes(1); + }); + + it('should retry runtime errors until the computation works', async () => { + const callback = jest + .fn() + .mockImplementationOnce(() => { + throw new Error('first'); + }) + .mockImplementationOnce(() => { + throw new Error('second'); + }) + .mockImplementationOnce(() => 'success'); + + retryOnError$(retries, delay, callback).subscribe({ + next: (response) => { + expect(response).toBe('success'); + }, + error: (err) => { + throw new Error(`Unexpected error: ${err}`); + }, + }); + + await jest.advanceTimersByTimeAsync(delay * 2 * 1.1); + + expect(callback).toHaveBeenCalledTimes(3); + }); + + it('should exhaust retries with runtime errors and emit an error', async () => { + const callback = jest.fn().mockImplementation(() => { + throw new Error('boom!'); + }); + + retryOnError$(retries, delay, callback).subscribe({ + next: (response) => { + expect(response.message).toBe('boom!'); + }, + error: (err) => { + throw new Error(`Unexpected error: ${err}`); + }, + }); + + await jest.advanceTimersByTimeAsync(retries * delay * 1.1); + + expect(callback).toHaveBeenCalledTimes(retries + 1); + }); + + it('should exhaust retries with rejected promises and emit an error', async () => { + const callback = jest.fn().mockImplementation(() => Promise.reject(new Error('boom!'))); + + retryOnError$(retries, delay, callback).subscribe({ + next: (response) => { + expect(response.message).toBe('boom!'); + }, + error: (err) => { + throw new Error(`Unexpected error: ${err}`); + }, + }); + + await jest.advanceTimersByTimeAsync(retries * delay * 1.1); + + expect(callback).toHaveBeenCalledTimes(retries + 1); + }); + + it('should retry rejected promises until the computation works', async () => { + const callback = jest + .fn() + .mockImplementationOnce(() => Promise.reject(new Error('first'))) + .mockImplementationOnce(() => Promise.reject(new Error('second'))) + .mockImplementationOnce(() => Promise.resolve('success')); + + retryOnError$(retries, delay, callback).subscribe({ + next: (response) => { + expect(response).toBe('success'); + }, + error: (err) => { + throw new Error(`Unexpected error: ${err}`); + }, + }); + + await jest.advanceTimersByTimeAsync(delay * 3 * 1.1); + + expect(callback).toHaveBeenCalledTimes(3); + }); + + it('should retry rejected promises and runtime errors until the computation works', async () => { + const callback = jest + .fn() + .mockImplementationOnce(() => Promise.reject(new Error('first'))) + .mockImplementationOnce(() => { + throw new Error('second'); + }) + .mockImplementationOnce(() => Promise.resolve('success')); + + retryOnError$(retries, delay, callback).subscribe({ + next: (response) => { + expect(response).toBe('success'); + }, + error: (err) => { + throw new Error(`Unexpected error: ${err}`); + }, + }); + + await jest.advanceTimersByTimeAsync(delay * 3 * 1.1); + + expect(callback).toHaveBeenCalledTimes(3); + }); +}); + +describe('CachedSubject', () => { + it('should cache values until is flushed', () => { + const elements = [1, 2, 3, 4, 5]; + const subject$ = new rx.Subject(); + const cache = new CachedSubject(subject$); + const values: number[] = []; + + elements.forEach((v) => subject$.next(v)); + + subject$.subscribe({ + next: (v) => values.push(v), + }); + + expect(values).toHaveLength(0); + + cache.stop(); + expect(values).toHaveLength(0); + + cache.flush(); + expect(values).toEqual(elements); + }); +}); diff --git a/x-pack/plugins/security_solution/server/lib/telemetry/rxjs_helpers.ts b/x-pack/plugins/security_solution/server/lib/telemetry/rxjs_helpers.ts new file mode 100644 index 0000000000000..ce8df3df40b45 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/telemetry/rxjs_helpers.ts @@ -0,0 +1,83 @@ +/* + * 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 * as rx from 'rxjs'; + +/** + * This utility class stores the values sent to a reactive flow while its state + * is `started`. Once it's stopped (after calling `flush()`) it will send all + * the cached values to the configured `subject$`. + */ +export class CachedSubject { + public readonly flushCache$ = new rx.Subject(); + public readonly stopCaching$ = new rx.Subject(); + + constructor(subject$: rx.Subject) { + this.setup(subject$); + } + + public stop(): void { + this.stopCaching$.next(); + } + + public flush(): void { + this.flushCache$.next(); + } + + private setup(subject$: rx.Subject): void { + // Cache the incoming events that are sent during the timeframe between + // `service.setup()` and `service.start()`, otherwise, they would be lost + const cache$ = new rx.ReplaySubject(); + const storingCache$ = new rx.BehaviorSubject(true); + + // 1. sends incoming values to the cache$, works only while + // `storingCache$` is set to true + storingCache$ + .pipe( + rx.distinctUntilChanged(), + rx.switchMap((isCaching) => (isCaching ? subject$ : rx.EMPTY)), + rx.takeUntil(rx.merge(this.stopCaching$)) + ) + .subscribe((data) => { + cache$.next(data); + }); + + // 2. when flushCache is triggered, stop caching values and send the cached + // ones to the real flow (i.e. `subject$`). + this.flushCache$.pipe(rx.exhaustMap(() => cache$)).subscribe((data) => { + storingCache$.next(false); + subject$.next(data); + }); + } +} + +/** + * Executes the given `body()` function wrappig it in a retry logic by using + * the rxjs `retry` operator. + * + * @param retryCount the number of times to retry the `body()` function + * @param retryDelayMillis the delay between each retry + * @param body the function to execute + * @returns an observable that emits either the result returned by the `body()` + * function or the latest caught error after exhausting the retryCount. + */ +export function retryOnError$( + retryCount: number, + retryDelayMillis: number, + body: () => R +): rx.Observable { + return rx + .defer(async () => body()) + .pipe( + rx.retry({ + count: retryCount, + delay: retryDelayMillis, + }), + rx.catchError((error) => { + return rx.of(error); + }) + ); +} diff --git a/x-pack/plugins/security_solution/server/lib/telemetry/sender.ts b/x-pack/plugins/security_solution/server/lib/telemetry/sender.ts index 041d9b67720d9..7addae79f148d 100644 --- a/x-pack/plugins/security_solution/server/lib/telemetry/sender.ts +++ b/x-pack/plugins/security_solution/server/lib/telemetry/sender.ts @@ -22,10 +22,12 @@ import type { ITelemetryReceiver } from './receiver'; import { copyAllowlistedFields, filterList } from './filterlists'; import { createTelemetryTaskConfigs } from './tasks'; import { createUsageCounterLabel, tlog } from './helpers'; -import type { TelemetryEvent } from './types'; +import type { TelemetryChannel, TelemetryEvent } from './types'; import type { SecurityTelemetryTaskConfig } from './task'; import { SecurityTelemetryTask } from './task'; import { telemetryConfiguration } from './configuration'; +import type { IAsyncTelemetryEventsSender, QueueConfig } from './async_sender.types'; +import { TaskMetricsService } from './task_metrics'; const usageLabelPrefix: string[] = ['security_telemetry', 'sender']; @@ -34,7 +36,8 @@ export interface ITelemetryEventsSender { telemetryReceiver: ITelemetryReceiver, telemetrySetup?: TelemetryPluginSetup, taskManager?: TaskManagerSetupContract, - telemetryUsageCounter?: UsageCounter + telemetryUsageCounter?: UsageCounter, + asyncTelemetrySender?: IAsyncTelemetryEventsSender ): void; getTelemetryUsageCluster(): UsageCounter | undefined; @@ -47,6 +50,9 @@ export interface ITelemetryEventsSender { ): void; stop(): void; + /** + * @deprecated Use `sendAsync` instead. + */ queueTelemetryEvents(events: TelemetryEvent[]): void; isTelemetryOptedIn(): Promise; isTelemetryServicesReachable(): Promise; @@ -54,6 +60,32 @@ export interface ITelemetryEventsSender { processEvents(events: TelemetryEvent[]): TelemetryEvent[]; sendOnDemand(channel: string, toSend: unknown[], axiosInstance?: AxiosInstance): Promise; getV3UrlFromV2(v2url: string, channel: string): string; + + // As a transition to the new sender, `IAsyncTelemetryEventsSender`, we wrap + // its "public API" here to expose a single Sender interface to the rest + // of the code. The `queueTelemetryEvents` is deprecated in favor of + // `sendAsync`. + + /** + * Sends events to a given telemetry channel asynchronously. + */ + sendAsync: (channel: TelemetryChannel, events: unknown[]) => void; + + /** + * Simulates sending events to a given telemetry channel asynchronously + * and returns the request that should be sent to the server + */ + simulateSendAsync: (channel: TelemetryChannel, events: unknown[]) => string[]; + + /** + * Updates the queue configuration for a given channel. + */ + updateQueueConfig: (channel: TelemetryChannel, config: QueueConfig) => void; + + /** + * Updates the default queue configuration. + */ + updateDefaultQueueConfig: (config: QueueConfig) => void; } export class TelemetryEventsSender implements ITelemetryEventsSender { @@ -75,6 +107,8 @@ export class TelemetryEventsSender implements ITelemetryEventsSender { private telemetryUsageCounter?: UsageCounter; private telemetryTasks?: SecurityTelemetryTask[]; + private asyncTelemetrySender?: IAsyncTelemetryEventsSender; + constructor(logger: Logger) { this.logger = logger.get('telemetry_events'); } @@ -83,19 +117,28 @@ export class TelemetryEventsSender implements ITelemetryEventsSender { telemetryReceiver: ITelemetryReceiver, telemetrySetup?: TelemetryPluginSetup, taskManager?: TaskManagerSetupContract, - telemetryUsageCounter?: UsageCounter + telemetryUsageCounter?: UsageCounter, + asyncTelemetrySender?: IAsyncTelemetryEventsSender ) { this.telemetrySetup = telemetrySetup; this.telemetryUsageCounter = telemetryUsageCounter; if (taskManager) { + const taskMetricsService = new TaskMetricsService(this.logger, this); this.telemetryTasks = createTelemetryTaskConfigs().map( (config: SecurityTelemetryTaskConfig) => { - const task = new SecurityTelemetryTask(config, this.logger, this, telemetryReceiver); + const task = new SecurityTelemetryTask( + config, + this.logger, + this, + telemetryReceiver, + taskMetricsService + ); task.register(taskManager); return task; } ); } + this.asyncTelemetrySender = asyncTelemetrySender; } public getTelemetryUsageCluster(): UsageCounter | undefined { @@ -365,6 +408,29 @@ export class TelemetryEventsSender implements ITelemetryEventsSender { return url.toString(); } + public sendAsync(channel: TelemetryChannel, events: unknown[]): void { + this.getAsyncTelemetrySender().send(channel, events); + } + + public simulateSendAsync(channel: TelemetryChannel, events: unknown[]): string[] { + return this.getAsyncTelemetrySender().simulateSend(channel, events); + } + + public updateQueueConfig(channel: TelemetryChannel, config: QueueConfig): void { + this.getAsyncTelemetrySender().updateQueueConfig(channel, config); + } + + public updateDefaultQueueConfig(config: QueueConfig): void { + this.getAsyncTelemetrySender().updateDefaultQueueConfig(config); + } + + private getAsyncTelemetrySender(): IAsyncTelemetryEventsSender { + if (!this.asyncTelemetrySender) { + throw new Error('Telemetry Sender V2 not initialized'); + } + return this.asyncTelemetrySender; + } + private async fetchTelemetryPingUrl(): Promise { const telemetryUrl = await this.telemetrySetup?.getTelemetryUrl(); if (!telemetryUrl) { diff --git a/x-pack/plugins/security_solution/server/lib/telemetry/sender_helpers.ts b/x-pack/plugins/security_solution/server/lib/telemetry/sender_helpers.ts new file mode 100644 index 0000000000000..c7bb9d74ce1c5 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/telemetry/sender_helpers.ts @@ -0,0 +1,125 @@ +/* + * 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 axios from 'axios'; + +import type { TelemetryPluginStart, TelemetryPluginSetup } from '@kbn/telemetry-plugin/server'; +import type { RawAxiosRequestHeaders } from 'axios'; +import { type IUsageCounter } from '@kbn/usage-collection-plugin/server/usage_counters/usage_counter'; +import type { ITelemetryReceiver } from './receiver'; +import type { ESClusterInfo, ESLicense, TelemetryChannel, TelemetryCounter } from './types'; +import { createUsageCounterLabel } from './helpers'; + +export interface SenderMetadata { + telemetryUrl: string; + licenseInfo: ESLicense | undefined; + clusterInfo: ESClusterInfo | undefined; + telemetryRequestHeaders: () => RawAxiosRequestHeaders; + isTelemetryOptedIn(): Promise; + isTelemetryServicesReachable(): Promise; +} + +export class SenderUtils { + private readonly usageLabelPrefix: string[] = ['security_telemetry', 'sender']; + + constructor( + private readonly telemetrySetup?: TelemetryPluginSetup, + private readonly telemetryStart?: TelemetryPluginStart, + private readonly receiver?: ITelemetryReceiver, + private readonly telemetryUsageCounter?: IUsageCounter + ) {} + + public async fetchSenderMetadata(channel: TelemetryChannel): Promise { + const [telemetryUrl, licenseInfo] = await Promise.all([ + this.fetchTelemetryUrl(channel), + this.receiver?.fetchLicenseInfo(), + ]); + const clusterInfo = this.receiver?.getClusterInfo(); + + const isTelemetryOptedIn = async () => (await this.telemetryStart?.getIsOptedIn()) === true; + + return { + telemetryUrl, + licenseInfo, + clusterInfo, + telemetryRequestHeaders: () => { + const clusterName = clusterInfo?.cluster_name; + const clusterUuid = clusterInfo?.cluster_uuid; + const clusterVersionNumber = clusterInfo?.version?.number; + const licenseId = licenseInfo?.uid; + + return { + 'Content-Type': 'application/x-ndjson', + ...(clusterName ? { 'X-Elastic-Cluster-Name': clusterName } : undefined), + ...(clusterUuid ? { 'X-Elastic-Cluster-ID': clusterUuid } : undefined), + 'X-Elastic-Stack-Version': clusterVersionNumber ? clusterVersionNumber : '8.0.0', + ...(licenseId ? { 'X-Elastic-License-ID': licenseId } : {}), + }; + }, + isTelemetryOptedIn, + isTelemetryServicesReachable: async () => { + const isOptedIn = await isTelemetryOptedIn(); + if (!isOptedIn) { + return false; + } + + try { + const telemetryPingUrl = await this.fetchTelemetryPingUrl(); + const resp = await axios.get(telemetryPingUrl, { timeout: 3000 }); + if (resp.status === 200) { + return true; + } + + return false; + } catch (_) { + return false; + } + }, + }; + } + + public incrementCounter( + counterType: TelemetryCounter, + incrementBy: number, + ...tags: string[] + ): void { + const counterName = createUsageCounterLabel([...this.usageLabelPrefix, ...tags]); + this.telemetryUsageCounter?.incrementCounter({ counterName, counterType, incrementBy }); + } + + private async fetchTelemetryUrl(channel: TelemetryChannel): Promise { + const telemetryUrl = await this.telemetrySetup?.getTelemetryUrl(); + if (!telemetryUrl) { + throw Error("Couldn't get telemetry URL"); + } + return this.getV3UrlFromV2(telemetryUrl.toString(), channel); + } + + private async fetchTelemetryPingUrl(): Promise { + const telemetryUrl = await this.telemetrySetup?.getTelemetryUrl(); + if (!telemetryUrl) { + throw Error("Couldn't get telemetry URL"); + } + + telemetryUrl.pathname = `/ping`; + return telemetryUrl.toString(); + } + + /** + * This method converts a v2 URL to a v3 URL like: + * - https://telemetry.elastic.co/v3/send/my-channel-name + * - https://telemetry-staging.elastic.co/v3-dev/send/my-channel-name + */ + private getV3UrlFromV2(v2url: string, channel: TelemetryChannel): string { + const url = new URL(v2url); + if (!url.hostname.includes('staging')) { + url.pathname = `/v3/send/${channel}`; + } else { + url.pathname = `/v3-dev/send/${channel}`; + } + return url.toString(); + } +} diff --git a/x-pack/plugins/security_solution/server/lib/telemetry/task.test.ts b/x-pack/plugins/security_solution/server/lib/telemetry/task.test.ts index 6c7dd8e9afc6e..91cb7d881d4c9 100644 --- a/x-pack/plugins/security_solution/server/lib/telemetry/task.test.ts +++ b/x-pack/plugins/security_solution/server/lib/telemetry/task.test.ts @@ -13,6 +13,7 @@ import { createMockTaskInstance, createMockTelemetryEventsSender, createMockTelemetryReceiver, + createMockTaskMetrics, createMockSecurityTelemetryTask, } from './__mocks__'; @@ -28,7 +29,8 @@ describe('test security telemetry task', () => { createMockSecurityTelemetryTask(), logger, createMockTelemetryEventsSender(true), - createMockTelemetryReceiver() + createMockTelemetryReceiver(), + createMockTaskMetrics() ); expect(telemetryTask).toBeInstanceOf(SecurityTelemetryTask); @@ -41,7 +43,8 @@ describe('test security telemetry task', () => { createMockSecurityTelemetryTask(), logger, createMockTelemetryEventsSender(true), - createMockTelemetryReceiver() + createMockTelemetryReceiver(), + createMockTaskMetrics() ); telemetryTask.register(mockTaskManagerSetup); await telemetryTask.start(mockTaskManagerStart); @@ -58,6 +61,7 @@ describe('test security telemetry task', () => { mockTelemetryTaskConfig, mockTelemetryEventsSender, mockTelemetryReceiver, + mockTaskMetrics, } = await testTelemetryTaskRun(true, true); expect(mockTelemetryTaskConfig.runTask).toHaveBeenCalledWith( @@ -65,6 +69,7 @@ describe('test security telemetry task', () => { logger, mockTelemetryReceiver, mockTelemetryEventsSender, + mockTaskMetrics, { last: testLastTimestamp, current: testResult.state.lastExecutionTimestamp, @@ -92,11 +97,13 @@ describe('test security telemetry task', () => { const mockTelemetryTaskConfig = createMockSecurityTelemetryTask(testType, testLastTimestamp); const mockTelemetryEventsSender = createMockTelemetryEventsSender(optedIn, canConnect); const mockTelemetryReceiver = createMockTelemetryReceiver(); + const mockTaskMetrics = createMockTaskMetrics(); const telemetryTask = new SecurityTelemetryTask( mockTelemetryTaskConfig, logger, mockTelemetryEventsSender, - mockTelemetryReceiver + mockTelemetryReceiver, + mockTaskMetrics ); const mockTaskInstance = createMockTaskInstance(telemetryTask.getTaskId(), testType); @@ -122,6 +129,7 @@ describe('test security telemetry task', () => { mockTelemetryTaskConfig, mockTelemetryEventsSender, mockTelemetryReceiver, + mockTaskMetrics, }; } }); diff --git a/x-pack/plugins/security_solution/server/lib/telemetry/task.ts b/x-pack/plugins/security_solution/server/lib/telemetry/task.ts index 84558d35df9ff..62d827930e6a9 100644 --- a/x-pack/plugins/security_solution/server/lib/telemetry/task.ts +++ b/x-pack/plugins/security_solution/server/lib/telemetry/task.ts @@ -14,6 +14,7 @@ import type { } from '@kbn/task-manager-plugin/server'; import type { ITelemetryReceiver } from './receiver'; import type { ITelemetryEventsSender } from './sender'; +import type { ITaskMetricsService } from './task_metrics.types'; import { tlog } from './helpers'; import { stateSchemaByVersion, emptyState, type LatestTaskStateSchema } from './task_state'; @@ -32,6 +33,7 @@ export type SecurityTelemetryTaskRunner = ( logger: Logger, receiver: ITelemetryReceiver, sender: ITelemetryEventsSender, + taskMetricsService: ITaskMetricsService, taskExecutionPeriod: TaskExecutionPeriod ) => Promise; @@ -50,17 +52,20 @@ export class SecurityTelemetryTask { private readonly logger: Logger; private readonly sender: ITelemetryEventsSender; private readonly receiver: ITelemetryReceiver; + private readonly taskMetricsService: ITaskMetricsService; constructor( config: SecurityTelemetryTaskConfig, logger: Logger, sender: ITelemetryEventsSender, - receiver: ITelemetryReceiver + receiver: ITelemetryReceiver, + taskMetricsService: ITaskMetricsService ) { this.config = config; this.logger = logger; this.sender = sender; this.receiver = receiver; + this.taskMetricsService = taskMetricsService; } public getLastExecutionTime = ( @@ -154,6 +159,13 @@ export class SecurityTelemetryTask { } tlog(this.logger, `[task ${taskId}]: running task`); - return this.config.runTask(taskId, this.logger, this.receiver, this.sender, executionPeriod); + return this.config.runTask( + taskId, + this.logger, + this.receiver, + this.sender, + this.taskMetricsService, + executionPeriod + ); }; } diff --git a/x-pack/plugins/security_solution/server/lib/telemetry/task_metrics.test.ts b/x-pack/plugins/security_solution/server/lib/telemetry/task_metrics.test.ts new file mode 100644 index 0000000000000..784c34bf11d7d --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/telemetry/task_metrics.test.ts @@ -0,0 +1,80 @@ +/* + * 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 { loggingSystemMock } from '@kbn/core/server/mocks'; +import type { ITaskMetricsService, TaskMetric } from './task_metrics.types'; +import { TaskMetricsService } from './task_metrics'; +import { createMockTelemetryEventsSender } from './__mocks__'; +import type { ITelemetryEventsSender } from './sender'; +import { telemetryConfiguration } from './configuration'; + +describe('task metrics', () => { + let logger: ReturnType; + let taskMetricsService: ITaskMetricsService; + let mockTelemetryEventsSender: jest.Mocked; + + beforeEach(() => { + logger = loggingSystemMock.createLogger(); + mockTelemetryEventsSender = createMockTelemetryEventsSender(); + taskMetricsService = new TaskMetricsService(logger, mockTelemetryEventsSender); + jest.spyOn(telemetryConfiguration, 'use_async_sender', 'get').mockReturnValue(true); + }); + + afterEach(() => { + jest.clearAllMocks(); + }); + + it('should start trace', async () => { + const trace = taskMetricsService.start('test'); + expect(trace).toBeDefined(); + expect(trace.name).toEqual('test'); + }); + + it('should record passed task metrics', async () => { + const metric = sendMetric('test'); + + expect(metric.name).toEqual('test'); + expect(metric.passed).toBeTruthy(); + expect(metric.error_message).toBeUndefined(); + expect(metric.time_executed_in_ms).toBeGreaterThan(0); + expect(metric.start_time).toBeGreaterThan(0); + expect(metric.end_time).toBeGreaterThan(0); + }); + + it('should use legacy sender when feature flag is disabled', async () => { + jest.spyOn(telemetryConfiguration, 'use_async_sender', 'get').mockReturnValue(false); + + const trace = taskMetricsService.start('test'); + taskMetricsService.end(trace); + expect(mockTelemetryEventsSender.sendAsync).toHaveBeenCalledTimes(0); + + expect(mockTelemetryEventsSender.sendAsync).toHaveBeenCalledTimes(0); + expect(mockTelemetryEventsSender.sendOnDemand).toHaveBeenCalledTimes(1); + }); + + it('should record failed task metrics', async () => { + const metric = sendMetric('test', Error('Boom!')); + + expect(metric.name).toEqual('test'); + expect(metric.passed).toBeFalsy(); + expect(metric.error_message).toEqual('Boom!'); + expect(metric.time_executed_in_ms).toBeGreaterThan(0); + expect(metric.start_time).toBeGreaterThan(0); + expect(metric.end_time).toBeGreaterThan(0); + }); + + function sendMetric(name: string, error?: Error): TaskMetric { + const trace = taskMetricsService.start(name); + taskMetricsService.end(trace, error); + + expect(mockTelemetryEventsSender.sendAsync).toHaveBeenCalledTimes(1); + const events = mockTelemetryEventsSender.sendAsync.mock.calls[0][1]; + expect(events).toHaveLength(1); + + return events[0] as TaskMetric; + } +}); diff --git a/x-pack/plugins/security_solution/server/lib/telemetry/task_metrics.ts b/x-pack/plugins/security_solution/server/lib/telemetry/task_metrics.ts new file mode 100644 index 0000000000000..10041f46c4196 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/telemetry/task_metrics.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 { Logger } from '@kbn/core/server'; +import { newTelemetryLogger, type TelemetryLogger } from './helpers'; +import type { TaskMetric, ITaskMetricsService, Trace } from './task_metrics.types'; +import type { ITelemetryEventsSender } from './sender'; +import { TelemetryChannel } from './types'; +import { telemetryConfiguration } from './configuration'; + +export class TaskMetricsService implements ITaskMetricsService { + private readonly logger: TelemetryLogger; + + constructor(logger: Logger, private readonly sender: ITelemetryEventsSender) { + this.logger = newTelemetryLogger(logger.get('telemetry_events.task_metrics')); + } + + public start(name: string): Trace { + return { + name, + startedAt: performance.now(), + }; + } + + public async end(trace: Trace, error?: Error): Promise { + const event = this.createTaskMetric(trace, error); + + this.logger.l(`Task ${event.name} complete. Task run took ${event.time_executed_in_ms}ms`); + + if (telemetryConfiguration.use_async_sender) { + this.sender.sendAsync(TelemetryChannel.TASK_METRICS, [event]); + } else { + await this.sender.sendOnDemand(TelemetryChannel.TASK_METRICS, [event]); + } + } + + public createTaskMetric(trace: Trace, error?: Error): TaskMetric { + const finishedAt = performance.now(); + return { + name: trace.name, + passed: error === undefined, + time_executed_in_ms: finishedAt - trace.startedAt, + start_time: trace.startedAt, + end_time: finishedAt, + error_message: error?.message, + }; + } +} diff --git a/x-pack/plugins/security_solution/server/lib/telemetry/task_metrics.types.ts b/x-pack/plugins/security_solution/server/lib/telemetry/task_metrics.types.ts new file mode 100644 index 0000000000000..68baaacfb8602 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/telemetry/task_metrics.types.ts @@ -0,0 +1,25 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +export interface ITaskMetricsService { + start(name: string): Trace; + end(trace: Trace, error?: Error): Promise; + createTaskMetric(trace: Trace, error?: Error): TaskMetric; +} + +export interface Trace { + name: string; + startedAt: number; +} + +export interface TaskMetric { + name: string; + passed: boolean; + time_executed_in_ms: number; + start_time: number; + end_time: number; + error_message?: string; +} diff --git a/x-pack/plugins/security_solution/server/lib/telemetry/tasks/configuration.test.ts b/x-pack/plugins/security_solution/server/lib/telemetry/tasks/configuration.test.ts index 65a1395a40e4c..9e65e7960c219 100644 --- a/x-pack/plugins/security_solution/server/lib/telemetry/tasks/configuration.test.ts +++ b/x-pack/plugins/security_solution/server/lib/telemetry/tasks/configuration.test.ts @@ -7,7 +7,11 @@ import { loggingSystemMock } from '@kbn/core/server/mocks'; import { createTelemetryConfigurationTaskConfig } from './configuration'; -import { createMockTelemetryEventsSender, createMockTelemetryReceiver } from '../__mocks__'; +import { + createMockTelemetryEventsSender, + createMockTelemetryReceiver, + createMockTaskMetrics, +} from '../__mocks__'; describe('telemetry configuration task test', () => { let logger: ReturnType; @@ -24,12 +28,14 @@ describe('telemetry configuration task test', () => { const mockTelemetryReceiver = createMockTelemetryReceiver(); const mockTelemetryEventsSender = createMockTelemetryEventsSender(); const telemetryDiagnoticsTaskConfig = createTelemetryConfigurationTaskConfig(); + const mockTaskMetrics = createMockTaskMetrics(); await telemetryDiagnoticsTaskConfig.runTask( 'test-id', logger, mockTelemetryReceiver, mockTelemetryEventsSender, + mockTaskMetrics, testTaskExecutionPeriod ); diff --git a/x-pack/plugins/security_solution/server/lib/telemetry/tasks/configuration.ts b/x-pack/plugins/security_solution/server/lib/telemetry/tasks/configuration.ts index 75c7a9080b3b8..fdddc67b84d07 100644 --- a/x-pack/plugins/security_solution/server/lib/telemetry/tasks/configuration.ts +++ b/x-pack/plugins/security_solution/server/lib/telemetry/tasks/configuration.ts @@ -6,37 +6,53 @@ */ import type { Logger } from '@kbn/core/server'; -import { TASK_METRICS_CHANNEL } from '../constants'; import type { ITelemetryEventsSender } from '../sender'; -import type { TelemetryConfiguration } from '../types'; +import { TelemetryChannel, type TelemetryConfiguration } from '../types'; import type { ITelemetryReceiver } from '../receiver'; import type { TaskExecutionPeriod } from '../task'; +import type { ITaskMetricsService } from '../task_metrics.types'; import { artifactService } from '../artifact'; import { telemetryConfiguration } from '../configuration'; -import { createTaskMetric, tlog } from '../helpers'; +import { newTelemetryLogger } from '../helpers'; export function createTelemetryConfigurationTaskConfig() { + const taskName = 'Security Solution Telemetry Configuration Task'; + const taskType = 'security:telemetry-configuration'; return { - type: 'security:telemetry-configuration', - title: 'Security Solution Telemetry Configuration Task', + type: taskType, + title: taskName, interval: '1h', timeout: '1m', version: '1.0.0', runTask: async ( taskId: string, logger: Logger, - receiver: ITelemetryReceiver, + _receiver: ITelemetryReceiver, sender: ITelemetryEventsSender, + taskMetricsService: ITaskMetricsService, taskExecutionPeriod: TaskExecutionPeriod ) => { - const startTime = Date.now(); - const taskName = 'Security Solution Telemetry Configuration Task'; + const log = newTelemetryLogger(logger.get('configuration')).l; + const trace = taskMetricsService.start(taskType); + + log( + `Running task: ${taskId} [last: ${taskExecutionPeriod.last} - current: ${taskExecutionPeriod.current}]` + ); + try { const artifactName = 'telemetry-buffer-and-batch-sizes-v1'; - const configArtifact = (await artifactService.getArtifact( - artifactName - )) as unknown as TelemetryConfiguration; - tlog(logger, `New telemetry configuration artifact: ${JSON.stringify(configArtifact)}`); + const manifest = await artifactService.getArtifact(artifactName); + + if (manifest.notModified) { + log('No new configuration artifact found, skipping...'); + taskMetricsService.end(trace); + return 0; + } + + const configArtifact = manifest.data as unknown as TelemetryConfiguration; + + log(`Got telemetry configuration artifact: ${JSON.stringify(configArtifact)}`); + telemetryConfiguration.max_detection_alerts_batch = configArtifact.max_detection_alerts_batch; telemetryConfiguration.telemetry_max_buffer_size = configArtifact.telemetry_max_buffer_size; @@ -46,16 +62,51 @@ export function createTelemetryConfigurationTaskConfig() { configArtifact.max_endpoint_telemetry_batch; telemetryConfiguration.max_security_list_telemetry_batch = configArtifact.max_security_list_telemetry_batch; - await sender.sendOnDemand(TASK_METRICS_CHANNEL, [ - createTaskMetric(taskName, true, startTime), - ]); + + if (configArtifact.use_async_sender) { + telemetryConfiguration.use_async_sender = configArtifact.use_async_sender; + } + + if (configArtifact.sender_channels) { + log('Updating sender channels configuration'); + telemetryConfiguration.sender_channels = configArtifact.sender_channels; + const channelsDict = Object.values(TelemetryChannel).reduce( + (acc, channel) => acc.set(channel as string, channel), + new Map() + ); + + Object.entries(configArtifact.sender_channels).forEach(([channelName, config]) => { + if (channelName === 'default') { + log('Updating default configuration'); + sender.updateDefaultQueueConfig({ + bufferTimeSpanMillis: config.buffer_time_span_millis, + inflightEventsThreshold: config.inflight_events_threshold, + maxPayloadSizeBytes: config.max_payload_size_bytes, + }); + } else { + const channel = channelsDict.get(channelName); + if (!channel) { + log(`Ignoring unknown channel "${channelName}"`); + } else { + log(`Updating configuration for channel "${channelName}`); + sender.updateQueueConfig(channel, { + bufferTimeSpanMillis: config.buffer_time_span_millis, + inflightEventsThreshold: config.inflight_events_threshold, + maxPayloadSizeBytes: config.max_payload_size_bytes, + }); + } + } + }); + } + + taskMetricsService.end(trace); + + log(`Updated TelemetryConfiguration: ${JSON.stringify(telemetryConfiguration)}`); return 0; } catch (err) { - tlog(logger, `Failed to set telemetry configuration due to ${err.message}`); + log(`Failed to set telemetry configuration due to ${err.message}`); telemetryConfiguration.resetAllToDefault(); - await sender.sendOnDemand(TASK_METRICS_CHANNEL, [ - createTaskMetric(taskName, false, startTime, err.message), - ]); + taskMetricsService.end(trace, err); return 0; } }, diff --git a/x-pack/plugins/security_solution/server/lib/telemetry/tasks/detection_rule.test.ts b/x-pack/plugins/security_solution/server/lib/telemetry/tasks/detection_rule.test.ts index 9cfbaffbcaa42..de59f350865c7 100644 --- a/x-pack/plugins/security_solution/server/lib/telemetry/tasks/detection_rule.test.ts +++ b/x-pack/plugins/security_solution/server/lib/telemetry/tasks/detection_rule.test.ts @@ -7,7 +7,11 @@ import { loggingSystemMock } from '@kbn/core/server/mocks'; import { createTelemetryDetectionRuleListsTaskConfig } from './detection_rule'; -import { createMockTelemetryEventsSender, createMockTelemetryReceiver } from '../__mocks__'; +import { + createMockTelemetryEventsSender, + createMockTelemetryReceiver, + createMockTaskMetrics, +} from '../__mocks__'; describe('security detection rule task test', () => { let logger: ReturnType; @@ -24,12 +28,14 @@ describe('security detection rule task test', () => { const mockTelemetryEventsSender = createMockTelemetryEventsSender(); const mockTelemetryReceiver = createMockTelemetryReceiver(); const telemetryDetectionRuleListsTaskConfig = createTelemetryDetectionRuleListsTaskConfig(1); + const mockTaskMetrics = createMockTaskMetrics(); await telemetryDetectionRuleListsTaskConfig.runTask( 'test-id', logger, mockTelemetryReceiver, mockTelemetryEventsSender, + mockTaskMetrics, testTaskExecutionPeriod ); diff --git a/x-pack/plugins/security_solution/server/lib/telemetry/tasks/detection_rule.ts b/x-pack/plugins/security_solution/server/lib/telemetry/tasks/detection_rule.ts index 00da024b0049e..e8acc4e222958 100644 --- a/x-pack/plugins/security_solution/server/lib/telemetry/tasks/detection_rule.ts +++ b/x-pack/plugins/security_solution/server/lib/telemetry/tasks/detection_rule.ts @@ -5,28 +5,27 @@ * 2.0. */ +import { cloneDeep } from 'lodash'; import type { Logger } from '@kbn/core/server'; -import { - LIST_DETECTION_RULE_EXCEPTION, - TELEMETRY_CHANNEL_LISTS, - TASK_METRICS_CHANNEL, -} from '../constants'; +import { LIST_DETECTION_RULE_EXCEPTION, TELEMETRY_CHANNEL_LISTS } from '../constants'; import { batchTelemetryRecords, templateExceptionList, - tlog, - createTaskMetric, + newTelemetryLogger, createUsageCounterLabel, } from '../helpers'; import type { ITelemetryEventsSender } from '../sender'; import type { ITelemetryReceiver } from '../receiver'; import type { ExceptionListItem, ESClusterInfo, ESLicense, RuleSearchResult } from '../types'; import type { TaskExecutionPeriod } from '../task'; +import type { ITaskMetricsService } from '../task_metrics.types'; export function createTelemetryDetectionRuleListsTaskConfig(maxTelemetryBatch: number) { + const taskName = 'Security Solution Detection Rule Lists Telemetry'; + const taskType = 'security:telemetry-detection-rules'; return { - type: 'security:telemetry-detection-rules', - title: 'Security Solution Detection Rule Lists Telemetry', + type: taskType, + title: taskName, interval: '24h', timeout: '10m', version: '1.0.0', @@ -35,17 +34,15 @@ export function createTelemetryDetectionRuleListsTaskConfig(maxTelemetryBatch: n logger: Logger, receiver: ITelemetryReceiver, sender: ITelemetryEventsSender, + taskMetricsService: ITaskMetricsService, taskExecutionPeriod: TaskExecutionPeriod ) => { + const log = newTelemetryLogger(logger.get('detection_rule')).l; const usageCollector = sender.getTelemetryUsageCluster(); - const usageLabelPrefix: string[] = ['security_telemetry', 'detection-rules']; + const trace = taskMetricsService.start(taskType); - const startTime = Date.now(); - const taskName = 'Security Solution Detection Rule Lists Telemetry'; - - tlog( - logger, + log( `Running task: ${taskId} [last: ${taskExecutionPeriod.last} - current: ${taskExecutionPeriod.current}]` ); @@ -69,10 +66,8 @@ export function createTelemetryDetectionRuleListsTaskConfig(maxTelemetryBatch: n const { body: prebuiltRules } = await receiver.fetchDetectionRules(); if (!prebuiltRules) { - tlog(logger, 'no prebuilt rules found'); - await sender.sendOnDemand(TASK_METRICS_CHANNEL, [ - createTaskMetric(taskName, true, startTime), - ]); + log('no prebuilt rules found'); + taskMetricsService.end(trace); return 0; } @@ -113,7 +108,7 @@ export function createTelemetryDetectionRuleListsTaskConfig(maxTelemetryBatch: n licenseInfo, LIST_DETECTION_RULE_EXCEPTION ); - tlog(logger, `Detection rule exception json length ${detectionRuleExceptionsJson.length}`); + log(`Detection rule exception json length ${detectionRuleExceptionsJson.length}`); usageCollector?.incrementCounter({ counterName: createUsageCounterLabel(usageLabelPrefix), @@ -121,18 +116,22 @@ export function createTelemetryDetectionRuleListsTaskConfig(maxTelemetryBatch: n incrementBy: detectionRuleExceptionsJson.length, }); - const batches = batchTelemetryRecords(detectionRuleExceptionsJson, maxTelemetryBatch); + const batches = batchTelemetryRecords( + cloneDeep(detectionRuleExceptionsJson), + maxTelemetryBatch + ); for (const batch of batches) { await sender.sendOnDemand(TELEMETRY_CHANNEL_LISTS, batch); } - await sender.sendOnDemand(TASK_METRICS_CHANNEL, [ - createTaskMetric(taskName, true, startTime), - ]); + taskMetricsService.end(trace); + + log( + `Task: ${taskId} executed. Processed ${detectionRuleExceptionsJson.length} exceptions` + ); + return detectionRuleExceptionsJson.length; } catch (err) { - await sender.sendOnDemand(TASK_METRICS_CHANNEL, [ - createTaskMetric(taskName, false, startTime, err.message), - ]); + taskMetricsService.end(trace, err); return 0; } }, diff --git a/x-pack/plugins/security_solution/server/lib/telemetry/tasks/diagnostic.test.ts b/x-pack/plugins/security_solution/server/lib/telemetry/tasks/diagnostic.test.ts index c2f2b596583c0..8b1237c6f8222 100644 --- a/x-pack/plugins/security_solution/server/lib/telemetry/tasks/diagnostic.test.ts +++ b/x-pack/plugins/security_solution/server/lib/telemetry/tasks/diagnostic.test.ts @@ -7,7 +7,11 @@ import { loggingSystemMock } from '@kbn/core/server/mocks'; import { createTelemetryDiagnosticsTaskConfig } from './diagnostic'; -import { createMockTelemetryEventsSender, createMockTelemetryReceiver } from '../__mocks__'; +import { + createMockTelemetryEventsSender, + createMockTelemetryReceiver, + createMockTaskMetrics, +} from '../__mocks__'; describe('diagnostics telemetry task test', () => { let logger: ReturnType; @@ -29,18 +33,21 @@ describe('diagnostics telemetry task test', () => { const mockTelemetryEventsSender = createMockTelemetryEventsSender(); const mockTelemetryReceiver = createMockTelemetryReceiver(testDiagnosticsAlerts); const telemetryDiagnoticsTaskConfig = createTelemetryDiagnosticsTaskConfig(); + const mockTaskMetrics = createMockTaskMetrics(); await telemetryDiagnoticsTaskConfig.runTask( 'test-id', logger, mockTelemetryReceiver, mockTelemetryEventsSender, + mockTaskMetrics, testTaskExecutionPeriod ); expect(mockTelemetryReceiver.fetchDiagnosticAlertsBatch).toHaveBeenCalledWith( testTaskExecutionPeriod.last, testTaskExecutionPeriod.current ); - expect(mockTelemetryEventsSender.sendOnDemand).toBeCalledTimes(1); + expect(mockTaskMetrics.start).toBeCalledTimes(1); + expect(mockTaskMetrics.end).toBeCalledTimes(1); }); }); diff --git a/x-pack/plugins/security_solution/server/lib/telemetry/tasks/diagnostic.ts b/x-pack/plugins/security_solution/server/lib/telemetry/tasks/diagnostic.ts index b52caeb165a3b..9b91dd557526e 100644 --- a/x-pack/plugins/security_solution/server/lib/telemetry/tasks/diagnostic.ts +++ b/x-pack/plugins/security_solution/server/lib/telemetry/tasks/diagnostic.ts @@ -6,18 +6,21 @@ */ import type { Logger } from '@kbn/core/server'; -import { tlog, getPreviousDiagTaskTimestamp, createTaskMetric } from '../helpers'; +import { newTelemetryLogger, getPreviousDiagTaskTimestamp } from '../helpers'; import type { ITelemetryEventsSender } from '../sender'; import type { TelemetryEvent } from '../types'; import type { ITelemetryReceiver } from '../receiver'; import type { TaskExecutionPeriod } from '../task'; -import { TELEMETRY_CHANNEL_ENDPOINT_ALERTS, TASK_METRICS_CHANNEL } from '../constants'; +import type { ITaskMetricsService } from '../task_metrics.types'; +import { TELEMETRY_CHANNEL_ENDPOINT_ALERTS } from '../constants'; import { copyAllowlistedFields, filterList } from '../filterlists'; export function createTelemetryDiagnosticsTaskConfig() { + const taskName = 'Security Solution Telemetry Diagnostics task'; + const taskType = 'security:endpoint-diagnostics'; return { - type: 'security:endpoint-diagnostics', - title: 'Security Solution Telemetry Diagnostics task', + type: taskType, + title: taskName, interval: '5m', timeout: '4m', version: '1.1.0', @@ -27,10 +30,16 @@ export function createTelemetryDiagnosticsTaskConfig() { logger: Logger, receiver: ITelemetryReceiver, sender: ITelemetryEventsSender, + taskMetricsService: ITaskMetricsService, taskExecutionPeriod: TaskExecutionPeriod ) => { - const startTime = Date.now(); - const taskName = 'Security Solution Telemetry Diagnostics task'; + const log = newTelemetryLogger(logger.get('diagnostic')).l; + const trace = taskMetricsService.start(taskType); + + log( + `Running task: ${taskId} [last: ${taskExecutionPeriod.last} - current: ${taskExecutionPeriod.current}]` + ); + try { if (!taskExecutionPeriod.last) { throw new Error('last execution timestamp is required'); @@ -48,27 +57,20 @@ export function createTelemetryDiagnosticsTaskConfig() { ); if (alerts.length === 0) { - tlog(logger, 'no diagnostic alerts retrieved'); - await sender.sendOnDemand(TASK_METRICS_CHANNEL, [ - createTaskMetric(taskName, true, startTime), - ]); + log('no diagnostic alerts retrieved'); + taskMetricsService.end(trace); return alertCount; } alertCount += alerts.length; - tlog(logger, `Sending ${alerts.length} diagnostic alerts`); + log(`Sending ${alerts.length} diagnostic alerts`); await sender.sendOnDemand(TELEMETRY_CHANNEL_ENDPOINT_ALERTS, processedAlerts); } - await sender.sendOnDemand(TASK_METRICS_CHANNEL, [ - createTaskMetric(taskName, true, startTime), - ]); - + taskMetricsService.end(trace); return alertCount; } catch (err) { - await sender.sendOnDemand(TASK_METRICS_CHANNEL, [ - createTaskMetric(taskName, false, startTime, err.message), - ]); + taskMetricsService.end(trace, err); return 0; } }, diff --git a/x-pack/plugins/security_solution/server/lib/telemetry/tasks/endpoint.test.ts b/x-pack/plugins/security_solution/server/lib/telemetry/tasks/endpoint.test.ts index 4cffeccc0db3c..7b1b59e316c7e 100644 --- a/x-pack/plugins/security_solution/server/lib/telemetry/tasks/endpoint.test.ts +++ b/x-pack/plugins/security_solution/server/lib/telemetry/tasks/endpoint.test.ts @@ -7,7 +7,11 @@ import { loggingSystemMock } from '@kbn/core/server/mocks'; import { createTelemetryEndpointTaskConfig } from './endpoint'; -import { createMockTelemetryEventsSender, createMockTelemetryReceiver } from '../__mocks__'; +import { + createMockTelemetryEventsSender, + createMockTelemetryReceiver, + createMockTaskMetrics, +} from '../__mocks__'; import { usageCountersServiceMock } from '@kbn/usage-collection-plugin/server/usage_counters/usage_counters_service.mock'; const usageCountersServiceSetup = usageCountersServiceMock.createSetupContract(); @@ -33,12 +37,14 @@ describe('endpoint telemetry task test', () => { .mockReturnValue(telemetryUsageCounter); const mockTelemetryReceiver = createMockTelemetryReceiver(); const telemetryEndpointTaskConfig = createTelemetryEndpointTaskConfig(1); + const mockTaskMetrics = createMockTaskMetrics(); await telemetryEndpointTaskConfig.runTask( 'test-id', logger, mockTelemetryReceiver, mockTelemetryEventsSender, + mockTaskMetrics, testTaskExecutionPeriod ); @@ -69,16 +75,19 @@ describe('endpoint telemetry task test', () => { const mockTelemetryReceiver = createMockTelemetryReceiver(); mockTelemetryReceiver.fetchPolicyConfigs = jest.fn().mockRejectedValueOnce(new Error()); const telemetryEndpointTaskConfig = createTelemetryEndpointTaskConfig(1); + const mockTaskMetrics = createMockTaskMetrics(); await telemetryEndpointTaskConfig.runTask( 'test-id', logger, mockTelemetryReceiver, mockTelemetryEventsSender, + mockTaskMetrics, testTaskExecutionPeriod ); expect(mockTelemetryReceiver.fetchPolicyConfigs).toHaveBeenCalled(); - expect(mockTelemetryEventsSender.sendOnDemand).toHaveBeenCalled(); + expect(mockTaskMetrics.start).toBeCalledTimes(1); + expect(mockTaskMetrics.end).toBeCalledTimes(1); }); }); diff --git a/x-pack/plugins/security_solution/server/lib/telemetry/tasks/endpoint.ts b/x-pack/plugins/security_solution/server/lib/telemetry/tasks/endpoint.ts index f221a11204bf4..4455ae1833e4b 100644 --- a/x-pack/plugins/security_solution/server/lib/telemetry/tasks/endpoint.ts +++ b/x-pack/plugins/security_solution/server/lib/telemetry/tasks/endpoint.ts @@ -21,6 +21,7 @@ import type { } from '../types'; import type { ITelemetryReceiver } from '../receiver'; import type { TaskExecutionPeriod } from '../task'; +import type { ITaskMetricsService } from '../task_metrics.types'; import { addDefaultAdvancedPolicyConfigSettings, batchTelemetryRecords, @@ -28,11 +29,10 @@ import { extractEndpointPolicyConfig, getPreviousDailyTaskTimestamp, isPackagePolicyList, - tlog, - createTaskMetric, + newTelemetryLogger, } from '../helpers'; import type { PolicyData } from '../../../../common/endpoint/types'; -import { TELEMETRY_CHANNEL_ENDPOINT_META, TASK_METRICS_CHANNEL } from '../constants'; +import { TELEMETRY_CHANNEL_ENDPOINT_META } from '../constants'; // Endpoint agent uses this Policy ID while it's installing. const DefaultEndpointPolicyIdToIgnore = '00000000-0000-0000-0000-000000000000'; @@ -47,9 +47,11 @@ const EmptyFleetAgentResponse = { const usageLabelPrefix: string[] = ['security_telemetry', 'endpoint_task']; export function createTelemetryEndpointTaskConfig(maxTelemetryBatch: number) { + const taskName = 'Security Solution Telemetry Endpoint Metrics and Info task'; + const taskType = 'security:endpoint-meta-telemetry'; return { - type: 'security:endpoint-meta-telemetry', - title: 'Security Solution Telemetry Endpoint Metrics and Info task', + type: taskType, + title: taskName, interval: '24h', timeout: '5m', version: '1.0.0', @@ -59,10 +61,16 @@ export function createTelemetryEndpointTaskConfig(maxTelemetryBatch: number) { logger: Logger, receiver: ITelemetryReceiver, sender: ITelemetryEventsSender, + taskMetricsService: ITaskMetricsService, taskExecutionPeriod: TaskExecutionPeriod ) => { - const startTime = Date.now(); - const taskName = 'Security Solution Telemetry Endpoint Metrics and Info task'; + const log = newTelemetryLogger(logger.get('endpoint')).l; + const trace = taskMetricsService.start(taskType); + + log( + `Running task: ${taskId} [last: ${taskExecutionPeriod.last} - current: ${taskExecutionPeriod.current}]` + ); + try { if (!taskExecutionPeriod.last) { throw new Error('last execution timestamp is required'); @@ -95,10 +103,8 @@ export function createTelemetryEndpointTaskConfig(maxTelemetryBatch: number) { * a metric document(s) exists for an EP agent we map to fleet agent and policy */ if (endpointData.endpointMetrics === undefined) { - tlog(logger, `no endpoint metrics to report`); - await sender.sendOnDemand(TASK_METRICS_CHANNEL, [ - createTaskMetric(taskName, true, startTime), - ]); + log('no endpoint metrics to report'); + taskMetricsService.end(trace); return 0; } @@ -107,10 +113,8 @@ export function createTelemetryEndpointTaskConfig(maxTelemetryBatch: number) { }; if (endpointMetricsResponse.aggregations === undefined) { - tlog(logger, `no endpoint metrics to report`); - await sender.sendOnDemand(TASK_METRICS_CHANNEL, [ - createTaskMetric(taskName, true, startTime), - ]); + log(`no endpoint metrics to report`); + taskMetricsService.end(trace); return 0; } @@ -143,10 +147,8 @@ export function createTelemetryEndpointTaskConfig(maxTelemetryBatch: number) { const agentsResponse = endpointData.fleetAgentsResponse; if (agentsResponse === undefined) { - tlog(logger, 'no fleet agent information available'); - await sender.sendOnDemand(TASK_METRICS_CHANNEL, [ - createTaskMetric(taskName, true, startTime), - ]); + log('no fleet agent information available'); + taskMetricsService.end(trace); return 0; } @@ -173,7 +175,7 @@ export function createTelemetryEndpointTaskConfig(maxTelemetryBatch: number) { try { agentPolicy = await receiver.fetchPolicyConfigs(policyInfo); } catch (err) { - tlog(logger, `error fetching policy config due to ${err?.message}`); + log(`error fetching policy config due to ${err?.message}`); } const packagePolicies = agentPolicy?.package_policies; @@ -234,7 +236,7 @@ export function createTelemetryEndpointTaskConfig(maxTelemetryBatch: number) { * a metadata document(s) exists for an EP agent we map to fleet agent and policy */ if (endpointData.endpointMetadata === undefined) { - tlog(logger, `no endpoint metadata to report`); + log(`no endpoint metadata to report`); } const { body: endpointMetadataResponse } = endpointData.endpointMetadata as unknown as { @@ -242,7 +244,7 @@ export function createTelemetryEndpointTaskConfig(maxTelemetryBatch: number) { }; if (endpointMetadataResponse.aggregations === undefined) { - tlog(logger, `no endpoint metadata to report`); + log(`no endpoint metadata to report`); } const endpointMetadata = @@ -354,21 +356,15 @@ export function createTelemetryEndpointTaskConfig(maxTelemetryBatch: number) { for (const batch of batches) { await sender.sendOnDemand(TELEMETRY_CHANNEL_ENDPOINT_META, batch); } - await sender.sendOnDemand(TASK_METRICS_CHANNEL, [ - createTaskMetric(taskName, true, startTime), - ]); + taskMetricsService.end(trace); return telemetryPayloads.length; } catch (err) { logger.warn(`could not complete endpoint alert telemetry task due to ${err?.message}`); - await sender.sendOnDemand(TASK_METRICS_CHANNEL, [ - createTaskMetric(taskName, false, startTime, err.message), - ]); + taskMetricsService.end(trace, err); return 0; } } catch (err) { - await sender.sendOnDemand(TASK_METRICS_CHANNEL, [ - createTaskMetric(taskName, false, startTime, err.message), - ]); + taskMetricsService.end(trace, err); return 0; } }, diff --git a/x-pack/plugins/security_solution/server/lib/telemetry/tasks/filterlists.ts b/x-pack/plugins/security_solution/server/lib/telemetry/tasks/filterlists.ts index 50bf10c964e08..c09a5ec497a01 100644 --- a/x-pack/plugins/security_solution/server/lib/telemetry/tasks/filterlists.ts +++ b/x-pack/plugins/security_solution/server/lib/telemetry/tasks/filterlists.ts @@ -6,50 +6,59 @@ */ import type { Logger } from '@kbn/core/server'; -import { TASK_METRICS_CHANNEL } from '../constants'; import type { ITelemetryEventsSender } from '../sender'; import type { TelemetryFilterListArtifact } from '../types'; import type { ITelemetryReceiver } from '../receiver'; +import type { ITaskMetricsService } from '../task_metrics.types'; import type { TaskExecutionPeriod } from '../task'; import { artifactService } from '../artifact'; import { filterList } from '../filterlists'; -import { createTaskMetric, tlog } from '../helpers'; +import { newTelemetryLogger } from '../helpers'; export function createTelemetryFilterListArtifactTaskConfig() { + const taskName = 'Security Solution Telemetry Filter List Artifact Task'; + const taskType = 'security:telemetry-filterlist-artifact'; return { - type: 'security:telemetry-filterlist-artifact', - title: 'Security Solution Telemetry Filter List Artifact Task', + type: taskType, + title: taskName, interval: '45m', timeout: '1m', version: '1.0.0', runTask: async ( taskId: string, logger: Logger, - receiver: ITelemetryReceiver, - sender: ITelemetryEventsSender, + _receiver: ITelemetryReceiver, + _sender: ITelemetryEventsSender, + taskMetricsService: ITaskMetricsService, taskExecutionPeriod: TaskExecutionPeriod ) => { - const startTime = Date.now(); - const taskName = 'Security Solution Telemetry Filter List Artifact Task'; + const log = newTelemetryLogger(logger.get('filterlists')).l; + const trace = taskMetricsService.start(taskType); + + log( + `Running task: ${taskId} [last: ${taskExecutionPeriod.last} - current: ${taskExecutionPeriod.current}]` + ); + try { const artifactName = 'telemetry-filterlists-v1'; - const artifact = (await artifactService.getArtifact( - artifactName - )) as unknown as TelemetryFilterListArtifact; - tlog(logger, `New filterlist artifact: ${JSON.stringify(artifact)}`); + const manifest = await artifactService.getArtifact(artifactName); + if (manifest.notModified) { + log('No new filterlist artifact found, skipping...'); + taskMetricsService.end(trace); + return 0; + } + + const artifact = manifest.data as unknown as TelemetryFilterListArtifact; + log(`New filterlist artifact: ${JSON.stringify(artifact)}`); filterList.endpointAlerts = artifact.endpoint_alerts; filterList.exceptionLists = artifact.exception_lists; filterList.prebuiltRulesAlerts = artifact.prebuilt_rules_alerts; - await sender.sendOnDemand(TASK_METRICS_CHANNEL, [ - createTaskMetric(taskName, true, startTime), - ]); + taskMetricsService.end(trace); return 0; } catch (err) { - tlog(logger, `Failed to set telemetry filterlist artifact due to ${err.message}`); + log(`Failed to set telemetry filterlist artifact due to ${err.message}`); filterList.resetAllToDefault(); - await sender.sendOnDemand(TASK_METRICS_CHANNEL, [ - createTaskMetric(taskName, false, startTime, err.message), - ]); + taskMetricsService.end(trace, err); return 0; } }, diff --git a/x-pack/plugins/security_solution/server/lib/telemetry/tasks/prebuilt_rule_alerts.test.ts b/x-pack/plugins/security_solution/server/lib/telemetry/tasks/prebuilt_rule_alerts.test.ts index 479ceabd65a3b..cbca2b32e677d 100644 --- a/x-pack/plugins/security_solution/server/lib/telemetry/tasks/prebuilt_rule_alerts.test.ts +++ b/x-pack/plugins/security_solution/server/lib/telemetry/tasks/prebuilt_rule_alerts.test.ts @@ -7,7 +7,11 @@ import { loggingSystemMock } from '@kbn/core/server/mocks'; import { createTelemetryPrebuiltRuleAlertsTaskConfig } from './prebuilt_rule_alerts'; -import { createMockTelemetryEventsSender, createMockTelemetryReceiver } from '../__mocks__'; +import { + createMockTelemetryEventsSender, + createMockTelemetryReceiver, + createMockTaskMetrics, +} from '../__mocks__'; import { usageCountersServiceMock } from '@kbn/usage-collection-plugin/server/usage_counters/usage_counters_service.mock'; const usageCountersServiceSetup = usageCountersServiceMock.createSetupContract(); @@ -33,16 +37,19 @@ describe('security telemetry - detection rule alerts task test', () => { .mockReturnValue(telemetryUsageCounter); const mockTelemetryReceiver = createMockTelemetryReceiver(); const telemetryDetectionRuleAlertsTaskConfig = createTelemetryPrebuiltRuleAlertsTaskConfig(1); + const mockTaskMetrics = createMockTaskMetrics(); await telemetryDetectionRuleAlertsTaskConfig.runTask( 'test-id', logger, mockTelemetryReceiver, mockTelemetryEventsSender, + mockTaskMetrics, testTaskExecutionPeriod ); expect(mockTelemetryReceiver.fetchDetectionRulesPackageVersion).toHaveBeenCalled(); expect(mockTelemetryReceiver.fetchPrebuiltRuleAlertsBatch).toHaveBeenCalled(); - expect(mockTelemetryEventsSender.sendOnDemand).toHaveBeenCalled(); + expect(mockTaskMetrics.start).toHaveBeenCalled(); + expect(mockTaskMetrics.end).toHaveBeenCalled(); }); }); diff --git a/x-pack/plugins/security_solution/server/lib/telemetry/tasks/prebuilt_rule_alerts.ts b/x-pack/plugins/security_solution/server/lib/telemetry/tasks/prebuilt_rule_alerts.ts index 0765d1d5bbdae..9d0324b8144c2 100644 --- a/x-pack/plugins/security_solution/server/lib/telemetry/tasks/prebuilt_rule_alerts.ts +++ b/x-pack/plugins/security_solution/server/lib/telemetry/tasks/prebuilt_rule_alerts.ts @@ -9,18 +9,20 @@ import type { Logger } from '@kbn/core/server'; import type { SortResults } from '@elastic/elasticsearch/lib/api/types'; import type { ITelemetryEventsSender } from '../sender'; import type { ITelemetryReceiver } from '../receiver'; +import type { ITaskMetricsService } from '../task_metrics.types'; import type { ESClusterInfo, ESLicense, TelemetryEvent } from '../types'; import type { TaskExecutionPeriod } from '../task'; -import { TELEMETRY_CHANNEL_DETECTION_ALERTS, TASK_METRICS_CHANNEL } from '../constants'; -import { batchTelemetryRecords, createTaskMetric, processK8sUsernames, tlog } from '../helpers'; +import { TELEMETRY_CHANNEL_DETECTION_ALERTS } from '../constants'; +import { batchTelemetryRecords, processK8sUsernames, newTelemetryLogger } from '../helpers'; import { copyAllowlistedFields, filterList } from '../filterlists'; export function createTelemetryPrebuiltRuleAlertsTaskConfig(maxTelemetryBatch: number) { + const taskName = 'Security Solution - Prebuilt Rule and Elastic ML Alerts Telemetry'; const taskVersion = '1.2.0'; - + const taskType = 'security:telemetry-prebuilt-rule-alerts'; return { - type: 'security:telemetry-prebuilt-rule-alerts', - title: 'Security Solution - Prebuilt Rule and Elastic ML Alerts Telemetry', + type: taskType, + title: taskName, interval: '1h', timeout: '15m', version: taskVersion, @@ -29,10 +31,16 @@ export function createTelemetryPrebuiltRuleAlertsTaskConfig(maxTelemetryBatch: n logger: Logger, receiver: ITelemetryReceiver, sender: ITelemetryEventsSender, + taskMetricsService: ITaskMetricsService, taskExecutionPeriod: TaskExecutionPeriod ) => { - const startTime = Date.now(); - const taskName = 'Security Solution - Prebuilt Rule and Elastic ML Alerts Telemetry'; + const log = newTelemetryLogger(logger.get('prebuilt_rule_alerts')).l; + const trace = taskMetricsService.start(taskType); + + log( + `Running task: ${taskId} [last: ${taskExecutionPeriod.last} - current: ${taskExecutionPeriod.current}]` + ); + try { const [clusterInfoPromise, licenseInfoPromise, packageVersion] = await Promise.allSettled([ receiver.fetchClusterInfo(), @@ -53,7 +61,8 @@ export function createTelemetryPrebuiltRuleAlertsTaskConfig(maxTelemetryBatch: n const index = receiver.getAlertsIndex(); if (index === undefined) { - tlog(logger, `alerts index is not ready yet, skipping telemetry task`); + log(`alerts index is not ready yet, skipping telemetry task`); + taskMetricsService.end(trace); return 0; } @@ -66,6 +75,7 @@ export function createTelemetryPrebuiltRuleAlertsTaskConfig(maxTelemetryBatch: n await receiver.fetchPrebuiltRuleAlertsBatch(pitId, searchAfterValue); if (alerts.length === 0) { + taskMetricsService.end(trace); return 0; } @@ -94,7 +104,7 @@ export function createTelemetryPrebuiltRuleAlertsTaskConfig(maxTelemetryBatch: n }) ); - tlog(logger, `sending ${enrichedAlerts.length} elastic prebuilt alerts`); + log(`sending ${enrichedAlerts.length} elastic prebuilt alerts`); const batches = batchTelemetryRecords(enrichedAlerts, maxTelemetryBatch); const promises = batches.map(async (batch) => { @@ -104,13 +114,12 @@ export function createTelemetryPrebuiltRuleAlertsTaskConfig(maxTelemetryBatch: n await Promise.all(promises); } + taskMetricsService.end(trace); await receiver.closePointInTime(pitId); return 0; } catch (err) { logger.error('could not complete prebuilt alerts telemetry task'); - await sender.sendOnDemand(TASK_METRICS_CHANNEL, [ - createTaskMetric(taskName, false, startTime, err.message), - ]); + taskMetricsService.end(trace, err); return 0; } }, diff --git a/x-pack/plugins/security_solution/server/lib/telemetry/tasks/security_lists.test.ts b/x-pack/plugins/security_solution/server/lib/telemetry/tasks/security_lists.test.ts index 8c227dd56c13e..58a3c4720dab1 100644 --- a/x-pack/plugins/security_solution/server/lib/telemetry/tasks/security_lists.test.ts +++ b/x-pack/plugins/security_solution/server/lib/telemetry/tasks/security_lists.test.ts @@ -7,7 +7,11 @@ import { loggingSystemMock } from '@kbn/core/server/mocks'; import { createTelemetrySecurityListTaskConfig } from './security_lists'; -import { createMockTelemetryEventsSender, createMockTelemetryReceiver } from '../__mocks__'; +import { + createMockTelemetryEventsSender, + createMockTelemetryReceiver, + createMockTaskMetrics, +} from '../__mocks__'; import { ENDPOINT_LIST_ID, ENDPOINT_EVENT_FILTERS_LIST_ID, @@ -28,12 +32,14 @@ describe('security list telemetry task test', () => { const mockTelemetryEventsSender = createMockTelemetryEventsSender(); const mockTelemetryReceiver = createMockTelemetryReceiver(); const telemetrySecurityListTaskConfig = createTelemetrySecurityListTaskConfig(1); + const mockTaskMetrics = createMockTaskMetrics(); await telemetrySecurityListTaskConfig.runTask( 'test-id', logger, mockTelemetryReceiver, mockTelemetryEventsSender, + mockTaskMetrics, testTaskExecutionPeriod ); diff --git a/x-pack/plugins/security_solution/server/lib/telemetry/tasks/security_lists.ts b/x-pack/plugins/security_solution/server/lib/telemetry/tasks/security_lists.ts index 863d66d55c4e7..45fe385e76329 100644 --- a/x-pack/plugins/security_solution/server/lib/telemetry/tasks/security_lists.ts +++ b/x-pack/plugins/security_solution/server/lib/telemetry/tasks/security_lists.ts @@ -12,25 +12,26 @@ import { LIST_ENDPOINT_EVENT_FILTER, LIST_TRUSTED_APPLICATION, TELEMETRY_CHANNEL_LISTS, - TASK_METRICS_CHANNEL, } from '../constants'; import type { ESClusterInfo, ESLicense } from '../types'; import { batchTelemetryRecords, templateExceptionList, - createTaskMetric, formatValueListMetaData, createUsageCounterLabel, - tlog, + newTelemetryLogger, } from '../helpers'; import type { ITelemetryEventsSender } from '../sender'; import type { ITelemetryReceiver } from '../receiver'; import type { TaskExecutionPeriod } from '../task'; +import type { ITaskMetricsService } from '../task_metrics.types'; export function createTelemetrySecurityListTaskConfig(maxTelemetryBatch: number) { + const taskName = 'Security Solution Lists Telemetry'; + const taskType = 'security:telemetry-lists'; return { - type: 'security:telemetry-lists', - title: 'Security Solution Lists Telemetry', + type: taskType, + title: taskName, interval: '24h', timeout: '3m', version: '1.0.0', @@ -39,14 +40,18 @@ export function createTelemetrySecurityListTaskConfig(maxTelemetryBatch: number) logger: Logger, receiver: ITelemetryReceiver, sender: ITelemetryEventsSender, + taskMetricsService: ITaskMetricsService, taskExecutionPeriod: TaskExecutionPeriod ) => { - const usageCollector = sender.getTelemetryUsageCluster(); + const log = newTelemetryLogger(logger.get('security_lists')).l; + const trace = taskMetricsService.start(taskType); - const usageLabelPrefix: string[] = ['security_telemetry', 'lists']; + log( + `Running task: ${taskId} [last: ${taskExecutionPeriod.last} - current: ${taskExecutionPeriod.current}]` + ); - const startTime = Date.now(); - const taskName = 'Security Solution Lists Telemetry'; + const usageCollector = sender.getTelemetryUsageCluster(); + const usageLabelPrefix: string[] = ['security_telemetry', 'lists']; try { let trustedApplicationsCount = 0; let endpointExceptionsCount = 0; @@ -77,7 +82,7 @@ export function createTelemetrySecurityListTaskConfig(maxTelemetryBatch: number) LIST_TRUSTED_APPLICATION ); trustedApplicationsCount = trustedAppsJson.length; - tlog(logger, `Trusted Apps: ${trustedApplicationsCount}`); + log(`Trusted Apps: ${trustedApplicationsCount}`); usageCollector?.incrementCounter({ counterName: createUsageCounterLabel(usageLabelPrefix), @@ -102,7 +107,7 @@ export function createTelemetrySecurityListTaskConfig(maxTelemetryBatch: number) LIST_ENDPOINT_EXCEPTION ); endpointExceptionsCount = epExceptionsJson.length; - tlog(logger, `EP Exceptions: ${endpointExceptionsCount}`); + log(`EP Exceptions: ${endpointExceptionsCount}`); usageCollector?.incrementCounter({ counterName: createUsageCounterLabel(usageLabelPrefix), @@ -127,7 +132,7 @@ export function createTelemetrySecurityListTaskConfig(maxTelemetryBatch: number) LIST_ENDPOINT_EVENT_FILTER ); endpointEventFiltersCount = epFiltersJson.length; - tlog(logger, `EP Event Filters: ${endpointEventFiltersCount}`); + log(`EP Event Filters: ${endpointEventFiltersCount}`); usageCollector?.incrementCounter({ counterName: createUsageCounterLabel(usageLabelPrefix), @@ -153,14 +158,10 @@ export function createTelemetrySecurityListTaskConfig(maxTelemetryBatch: number) if (valueListMetaData?.total_list_count) { await sender.sendOnDemand(TELEMETRY_CHANNEL_LISTS, [valueListMetaData]); } - await sender.sendOnDemand(TASK_METRICS_CHANNEL, [ - createTaskMetric(taskName, true, startTime), - ]); + taskMetricsService.end(trace); return trustedApplicationsCount + endpointExceptionsCount + endpointEventFiltersCount; } catch (err) { - await sender.sendOnDemand(TASK_METRICS_CHANNEL, [ - createTaskMetric(taskName, false, startTime, err.message), - ]); + taskMetricsService.end(trace, err); return 0; } }, diff --git a/x-pack/plugins/security_solution/server/lib/telemetry/tasks/timelines.test.ts b/x-pack/plugins/security_solution/server/lib/telemetry/tasks/timelines.test.ts index 930ef076edd3f..e376de3fceb0f 100644 --- a/x-pack/plugins/security_solution/server/lib/telemetry/tasks/timelines.test.ts +++ b/x-pack/plugins/security_solution/server/lib/telemetry/tasks/timelines.test.ts @@ -7,7 +7,11 @@ import { loggingSystemMock } from '@kbn/core/server/mocks'; import { createTelemetryTimelineTaskConfig } from './timelines'; -import { createMockTelemetryEventsSender, createMockTelemetryReceiver } from '../__mocks__'; +import { + createMockTelemetryEventsSender, + createMockTelemetryReceiver, + createMockTaskMetrics, +} from '../__mocks__'; describe('timeline telemetry task test', () => { let logger: ReturnType; @@ -24,12 +28,14 @@ describe('timeline telemetry task test', () => { const mockTelemetryEventsSender = createMockTelemetryEventsSender(); const mockTelemetryReceiver = createMockTelemetryReceiver(); const telemetryTelemetryTaskConfig = createTelemetryTimelineTaskConfig(); + const mockTaskMetrics = createMockTaskMetrics(); await telemetryTelemetryTaskConfig.runTask( 'test-timeline-task-id', logger, mockTelemetryReceiver, mockTelemetryEventsSender, + mockTaskMetrics, testTaskExecutionPeriod ); @@ -48,12 +54,14 @@ describe('timeline telemetry task test', () => { const mockTelemetryEventsSender = createMockTelemetryEventsSender(); const mockTelemetryReceiver = createMockTelemetryReceiver(null, true); const telemetryTelemetryTaskConfig = createTelemetryTimelineTaskConfig(); + const mockTaskMetrics = createMockTaskMetrics(); await telemetryTelemetryTaskConfig.runTask( 'test-timeline-task-id', logger, mockTelemetryReceiver, mockTelemetryEventsSender, + mockTaskMetrics, testTaskExecutionPeriod ); diff --git a/x-pack/plugins/security_solution/server/lib/telemetry/tasks/timelines.ts b/x-pack/plugins/security_solution/server/lib/telemetry/tasks/timelines.ts index 7e0c41e57eec4..63e483cb77376 100644 --- a/x-pack/plugins/security_solution/server/lib/telemetry/tasks/timelines.ts +++ b/x-pack/plugins/security_solution/server/lib/telemetry/tasks/timelines.ts @@ -9,14 +9,15 @@ import type { Logger } from '@kbn/core/server'; import type { ITelemetryEventsSender } from '../sender'; import type { ITelemetryReceiver } from '../receiver'; import type { TaskExecutionPeriod } from '../task'; -import { TELEMETRY_CHANNEL_TIMELINE, TASK_METRICS_CHANNEL } from '../constants'; -import { createTaskMetric, ranges, TelemetryTimelineFetcher, tlog } from '../helpers'; +import type { ITaskMetricsService } from '../task_metrics.types'; +import { TELEMETRY_CHANNEL_TIMELINE } from '../constants'; +import { ranges, TelemetryTimelineFetcher, newTelemetryLogger } from '../helpers'; export function createTelemetryTimelineTaskConfig() { const taskName = 'Security Solution Timeline telemetry'; - + const taskType = 'security:telemetry-timelines'; return { - type: 'security:telemetry-timelines', + type: taskType, title: taskName, interval: '1h', timeout: '15m', @@ -26,15 +27,17 @@ export function createTelemetryTimelineTaskConfig() { logger: Logger, receiver: ITelemetryReceiver, sender: ITelemetryEventsSender, + taskMetricsService: ITaskMetricsService, taskExecutionPeriod: TaskExecutionPeriod ) => { - tlog( - logger, + const log = newTelemetryLogger(logger.get('timelines')).l; + const fetcher = new TelemetryTimelineFetcher(receiver); + const trace = taskMetricsService.start(taskType); + + log( `Running task: ${taskId} [last: ${taskExecutionPeriod.last} - current: ${taskExecutionPeriod.current}]` ); - const fetcher = new TelemetryTimelineFetcher(receiver); - try { let counter = 0; @@ -46,7 +49,7 @@ export function createTelemetryTimelineTaskConfig() { } const alerts = await receiver.fetchTimelineAlerts(alertsIndex, rangeFrom, rangeTo); - tlog(logger, `found ${alerts.length} alerts to process`); + log(`found ${alerts.length} alerts to process`); for (const alert of alerts) { const result = await fetcher.fetchTimeline(alert); @@ -67,21 +70,17 @@ export function createTelemetryTimelineTaskConfig() { sender.sendOnDemand(TELEMETRY_CHANNEL_TIMELINE, [result.timeline]); counter += 1; } else { - tlog(logger, 'no events in timeline'); + log('no events in timeline'); } } - tlog(logger, `sent ${counter} timelines. Concluding timeline task.`); + log(`sent ${counter} timelines. Concluding timeline task.`); - await sender.sendOnDemand(TASK_METRICS_CHANNEL, [ - createTaskMetric(taskName, true, fetcher.startTime), - ]); + taskMetricsService.end(trace); return counter; } catch (err) { - await sender.sendOnDemand(TASK_METRICS_CHANNEL, [ - createTaskMetric(taskName, false, fetcher.startTime, err.message), - ]); + taskMetricsService.end(trace, err); return 0; } }, diff --git a/x-pack/plugins/security_solution/server/lib/telemetry/tasks/timelines_diagnostic.test.ts b/x-pack/plugins/security_solution/server/lib/telemetry/tasks/timelines_diagnostic.test.ts index 887e0789e7754..491f87fe5a2e5 100644 --- a/x-pack/plugins/security_solution/server/lib/telemetry/tasks/timelines_diagnostic.test.ts +++ b/x-pack/plugins/security_solution/server/lib/telemetry/tasks/timelines_diagnostic.test.ts @@ -7,7 +7,11 @@ import { loggingSystemMock } from '@kbn/core/server/mocks'; import { createTelemetryDiagnosticTimelineTaskConfig } from './timelines_diagnostic'; -import { createMockTelemetryEventsSender, createMockTelemetryReceiver } from '../__mocks__'; +import { + createMockTelemetryEventsSender, + createMockTelemetryReceiver, + createMockTaskMetrics, +} from '../__mocks__'; describe('timeline telemetry diagnostic task test', () => { let logger: ReturnType; @@ -24,12 +28,14 @@ describe('timeline telemetry diagnostic task test', () => { const mockTelemetryEventsSender = createMockTelemetryEventsSender(); const mockTelemetryReceiver = createMockTelemetryReceiver(); const telemetryTelemetryDiagnosticTaskConfig = createTelemetryDiagnosticTimelineTaskConfig(); + const mockTaskMetrics = createMockTaskMetrics(); await telemetryTelemetryDiagnosticTaskConfig.runTask( 'test-timeline-diagnostic-task-id', logger, mockTelemetryReceiver, mockTelemetryEventsSender, + mockTaskMetrics, testTaskExecutionPeriod ); @@ -48,12 +54,14 @@ describe('timeline telemetry diagnostic task test', () => { const mockTelemetryEventsSender = createMockTelemetryEventsSender(); const mockTelemetryReceiver = createMockTelemetryReceiver(null, true); const telemetryTelemetryDiagnosticTaskConfig = createTelemetryDiagnosticTimelineTaskConfig(); + const mockTaskMetrics = createMockTaskMetrics(); await telemetryTelemetryDiagnosticTaskConfig.runTask( 'test-timeline-diagnostic-task-id', logger, mockTelemetryReceiver, mockTelemetryEventsSender, + mockTaskMetrics, testTaskExecutionPeriod ); diff --git a/x-pack/plugins/security_solution/server/lib/telemetry/tasks/timelines_diagnostic.ts b/x-pack/plugins/security_solution/server/lib/telemetry/tasks/timelines_diagnostic.ts index 7148848984b0a..46b248a36f63c 100644 --- a/x-pack/plugins/security_solution/server/lib/telemetry/tasks/timelines_diagnostic.ts +++ b/x-pack/plugins/security_solution/server/lib/telemetry/tasks/timelines_diagnostic.ts @@ -9,18 +9,15 @@ import type { Logger } from '@kbn/core/server'; import type { ITelemetryEventsSender } from '../sender'; import type { ITelemetryReceiver } from '../receiver'; import type { TaskExecutionPeriod } from '../task'; -import { - DEFAULT_DIAGNOSTIC_INDEX, - TELEMETRY_CHANNEL_TIMELINE, - TASK_METRICS_CHANNEL, -} from '../constants'; -import { createTaskMetric, ranges, TelemetryTimelineFetcher, tlog } from '../helpers'; +import type { ITaskMetricsService } from '../task_metrics.types'; +import { DEFAULT_DIAGNOSTIC_INDEX, TELEMETRY_CHANNEL_TIMELINE } from '../constants'; +import { ranges, TelemetryTimelineFetcher, newTelemetryLogger } from '../helpers'; export function createTelemetryDiagnosticTimelineTaskConfig() { const taskName = 'Security Solution Diagnostic Timeline telemetry'; - + const taskType = 'security:telemetry-diagnostic-timelines'; return { - type: 'security:telemetry-diagnostic-timelines', + type: taskType, title: taskName, interval: '1h', timeout: '15m', @@ -30,15 +27,17 @@ export function createTelemetryDiagnosticTimelineTaskConfig() { logger: Logger, receiver: ITelemetryReceiver, sender: ITelemetryEventsSender, + taskMetricsService: ITaskMetricsService, taskExecutionPeriod: TaskExecutionPeriod ) => { - tlog( - logger, + const log = newTelemetryLogger(logger.get('timelines_diagnostic')).l; + const trace = taskMetricsService.start(taskType); + const fetcher = new TelemetryTimelineFetcher(receiver); + + log( `Running task: ${taskId} [last: ${taskExecutionPeriod.last} - current: ${taskExecutionPeriod.current}]` ); - const fetcher = new TelemetryTimelineFetcher(receiver); - try { let counter = 0; @@ -50,7 +49,7 @@ export function createTelemetryDiagnosticTimelineTaskConfig() { rangeTo ); - tlog(logger, `found ${alerts.length} alerts to process`); + log(`found ${alerts.length} alerts to process`); for (const alert of alerts) { const result = await fetcher.fetchTimeline(alert); @@ -71,21 +70,17 @@ export function createTelemetryDiagnosticTimelineTaskConfig() { sender.sendOnDemand(TELEMETRY_CHANNEL_TIMELINE, [result.timeline]); counter += 1; } else { - tlog(logger, 'no events in timeline'); + log('no events in timeline'); } } - tlog(logger, `sent ${counter} timelines. Concluding timeline task.`); + log(`sent ${counter} timelines. Concluding timeline task.`); - await sender.sendOnDemand(TASK_METRICS_CHANNEL, [ - createTaskMetric(taskName, true, fetcher.startTime), - ]); + taskMetricsService.end(trace); return counter; } catch (err) { - await sender.sendOnDemand(TASK_METRICS_CHANNEL, [ - createTaskMetric(taskName, false, fetcher.startTime, err.message), - ]); + taskMetricsService.end(trace, err); return 0; } }, diff --git a/x-pack/plugins/security_solution/server/lib/telemetry/types.ts b/x-pack/plugins/security_solution/server/lib/telemetry/types.ts index cbff7156eaf15..46a8bc21faaef 100644 --- a/x-pack/plugins/security_solution/server/lib/telemetry/types.ts +++ b/x-pack/plugins/security_solution/server/lib/telemetry/types.ts @@ -43,6 +43,7 @@ export interface ESLicense { start_date_in_millis?: number; } +// Telemetry export interface TelemetryEvent { [key: string]: SearchTypes; '@timestamp'?: string; @@ -79,6 +80,30 @@ export interface TelemetryEvent { }; } +/** + * List of supported telemetry channels. + */ +export enum TelemetryChannel { + LISTS = 'security-lists-v2', + ENDPOINT_META = 'endpoint-metadata', + ENDPOINT_ALERTS = 'alerts-endpoint', + DETECTION_ALERTS = 'alerts-detections', + TIMELINE = 'alerts-timeline', + INSIGHTS = 'security-insights-v1', + TASK_METRICS = 'task-metrics', +} + +export enum TelemetryCounter { + DOCS_SENT = 'docs_sent', + DOCS_LOST = 'docs_lost', + DOCS_DROPPED = 'docs_dropped', + HTTP_STATUS = 'http_status', + RUNTIME_ERROR = 'runtime_error', + FATAL_ERROR = 'fatal_error', + TELEMETRY_OPTED_OUT = 'telemetry_opted_out', + TELEMETRY_NOT_REACHABLE = 'telemetry_not_reachable', +} + // EP Policy Response export interface EndpointPolicyResponseAggregation { @@ -428,21 +453,22 @@ export interface ValueListIndicatorMatchResponseAggregation { }; } -export interface TaskMetric { - name: string; - passed: boolean; - time_executed_in_ms: number; - start_time: number; - end_time: number; - error_message?: string; -} - export interface TelemetryConfiguration { telemetry_max_buffer_size: number; max_security_list_telemetry_batch: number; max_endpoint_telemetry_batch: number; max_detection_rule_telemetry_batch: number; max_detection_alerts_batch: number; + use_async_sender: boolean; + sender_channels?: { + [key: string]: TelemetrySenderChannelConfiguration; + }; +} + +export interface TelemetrySenderChannelConfiguration { + buffer_time_span_millis: number; + inflight_events_threshold: number; + max_payload_size_bytes: number; } export interface TelemetryFilterListArtifact { diff --git a/x-pack/plugins/security_solution/server/plugin.ts b/x-pack/plugins/security_solution/server/plugin.ts index 1af0d9171153b..005a42b320fbe 100644 --- a/x-pack/plugins/security_solution/server/plugin.ts +++ b/x-pack/plugins/security_solution/server/plugin.ts @@ -65,7 +65,13 @@ import { initUsageCollectors } from './usage'; import type { SecuritySolutionRequestHandlerContext } from './types'; import { securitySolutionSearchStrategyProvider } from './search_strategy/security_solution'; import type { ITelemetryEventsSender } from './lib/telemetry/sender'; +import { type IAsyncTelemetryEventsSender } from './lib/telemetry/async_sender.types'; import { TelemetryEventsSender } from './lib/telemetry/sender'; +import { + DEFAULT_QUEUE_CONFIG, + DEFAULT_RETRY_CONFIG, + AsyncTelemetryEventsSender, +} from './lib/telemetry/async_sender'; import type { ITelemetryReceiver } from './lib/telemetry/receiver'; import { TelemetryReceiver } from './lib/telemetry/receiver'; import { licenseService } from './lib/license'; @@ -137,6 +143,7 @@ export class Plugin implements ISecuritySolutionPlugin { private readonly endpointAppContextService = new EndpointAppContextService(); private readonly telemetryReceiver: ITelemetryReceiver; private readonly telemetryEventsSender: ITelemetryEventsSender; + private readonly asyncTelemetryEventsSender: IAsyncTelemetryEventsSender; private lists: ListPluginSetup | undefined; // TODO: can we create ListPluginStart? private licensing$!: Observable; @@ -158,6 +165,7 @@ export class Plugin implements ISecuritySolutionPlugin { this.ruleMonitoringService = createRuleMonitoringService(this.config, this.logger); this.telemetryEventsSender = new TelemetryEventsSender(this.logger); + this.asyncTelemetryEventsSender = new AsyncTelemetryEventsSender(this.logger); this.telemetryReceiver = new TelemetryReceiver(this.logger); this.logger.debug('plugin initialized'); @@ -458,11 +466,20 @@ export class Plugin implements ISecuritySolutionPlugin { setIsElasticCloudDeployment(plugins.cloud.isCloudEnabled ?? false); + this.asyncTelemetryEventsSender.setup( + DEFAULT_RETRY_CONFIG, + DEFAULT_QUEUE_CONFIG, + this.telemetryReceiver, + plugins.telemetry, + this.telemetryUsageCounter + ); + this.telemetryEventsSender.setup( this.telemetryReceiver, plugins.telemetry, plugins.taskManager, - this.telemetryUsageCounter + this.telemetryUsageCounter, + this.asyncTelemetryEventsSender ); this.checkMetadataTransformsTask = new CheckMetadataTransformsTask({ @@ -632,6 +649,8 @@ export class Plugin implements ISecuritySolutionPlugin { artifactService.start(this.telemetryReceiver); + this.asyncTelemetryEventsSender.start(plugins.telemetry); + this.telemetryEventsSender.start( plugins.telemetry, plugins.taskManager, @@ -661,6 +680,7 @@ export class Plugin implements ISecuritySolutionPlugin { public stop() { this.logger.debug('Stopping plugin'); + this.asyncTelemetryEventsSender.stop(); this.telemetryEventsSender.stop(); this.endpointAppContextService.stop(); this.policyWatcher?.stop(); diff --git a/x-pack/plugins/security_solution/tsconfig.json b/x-pack/plugins/security_solution/tsconfig.json index 99d6524aa3d7b..57c80161959fd 100644 --- a/x-pack/plugins/security_solution/tsconfig.json +++ b/x-pack/plugins/security_solution/tsconfig.json @@ -185,5 +185,6 @@ "@kbn/core-elasticsearch-server-mocks", "@kbn/lens-embeddable-utils", "@kbn/esql-utils", + "@kbn/core-test-helpers-kbn-server" ] } diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/telemetry/trial_license_complete_tier/task_based/all_types.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/telemetry/trial_license_complete_tier/task_based/all_types.ts index 59da2429f01e8..64b4897dbc8a3 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/telemetry/trial_license_complete_tier/task_based/all_types.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/telemetry/trial_license_complete_tier/task_based/all_types.ts @@ -52,7 +52,7 @@ export default ({ getService }: FtrProviderContext) => { detection_rules: [ [ { - name: 'Security Solution Detection Rule Lists Telemetry', + name: 'security:telemetry-detection-rules', passed: true, }, ], @@ -60,7 +60,7 @@ export default ({ getService }: FtrProviderContext) => { security_lists: [ [ { - name: 'Security Solution Lists Telemetry', + name: 'security:telemetry-lists', passed: true, }, ], @@ -68,7 +68,7 @@ export default ({ getService }: FtrProviderContext) => { endpoints: [ [ { - name: 'Security Solution Telemetry Endpoint Metrics and Info task', + name: 'security:endpoint-meta-telemetry', passed: true, }, ], @@ -76,7 +76,7 @@ export default ({ getService }: FtrProviderContext) => { diagnostics: [ [ { - name: 'Security Solution Telemetry Diagnostics task', + name: 'security:endpoint-diagnostics', passed: true, }, ], diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/telemetry/trial_license_complete_tier/task_based/detection_rules.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/telemetry/trial_license_complete_tier/task_based/detection_rules.ts index 1298e9aeb9eaa..069484a338b3b 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/telemetry/trial_license_complete_tier/task_based/detection_rules.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/telemetry/trial_license_complete_tier/task_based/detection_rules.ts @@ -101,7 +101,7 @@ export default ({ getService }: FtrProviderContext) => { expect(stats.detection_rules).to.eql([ [ { - name: 'Security Solution Detection Rule Lists Telemetry', + name: 'security:telemetry-detection-rules', passed: true, }, ], @@ -157,7 +157,7 @@ export default ({ getService }: FtrProviderContext) => { expect(stats.detection_rules).to.eql([ [ { - name: 'Security Solution Detection Rule Lists Telemetry', + name: 'security:telemetry-detection-rules', passed: true, }, ], @@ -213,7 +213,7 @@ export default ({ getService }: FtrProviderContext) => { expect(stats.detection_rules).to.eql([ [ { - name: 'Security Solution Detection Rule Lists Telemetry', + name: 'security:telemetry-detection-rules', passed: true, }, ], @@ -269,7 +269,7 @@ export default ({ getService }: FtrProviderContext) => { expect(stats.detection_rules).to.eql([ [ { - name: 'Security Solution Detection Rule Lists Telemetry', + name: 'security:telemetry-detection-rules', passed: true, }, ], @@ -325,7 +325,7 @@ export default ({ getService }: FtrProviderContext) => { expect(stats.detection_rules).to.eql([ [ { - name: 'Security Solution Detection Rule Lists Telemetry', + name: 'security:telemetry-detection-rules', passed: true, }, ], @@ -471,7 +471,7 @@ export default ({ getService }: FtrProviderContext) => { rule_version: detectionRules[0].rule_version, }, { - name: 'Security Solution Detection Rule Lists Telemetry', + name: 'security:telemetry-detection-rules', passed: true, }, ]); @@ -548,7 +548,7 @@ export default ({ getService }: FtrProviderContext) => { rule_version: detectionRules[0].rule_version, }, { - name: 'Security Solution Detection Rule Lists Telemetry', + name: 'security:telemetry-detection-rules', passed: true, }, ]); @@ -625,7 +625,7 @@ export default ({ getService }: FtrProviderContext) => { rule_version: detectionRules[0].rule_version, }, { - name: 'Security Solution Detection Rule Lists Telemetry', + name: 'security:telemetry-detection-rules', passed: true, }, ]); @@ -702,7 +702,7 @@ export default ({ getService }: FtrProviderContext) => { rule_version: detectionRules[0].rule_version, }, { - name: 'Security Solution Detection Rule Lists Telemetry', + name: 'security:telemetry-detection-rules', passed: true, }, ]); @@ -779,7 +779,7 @@ export default ({ getService }: FtrProviderContext) => { rule_version: detectionRules[0].rule_version, }, { - name: 'Security Solution Detection Rule Lists Telemetry', + name: 'security:telemetry-detection-rules', passed: true, }, ]); @@ -898,7 +898,7 @@ export default ({ getService }: FtrProviderContext) => { rule_version: detectionRules[1].rule_version, }, { - name: 'Security Solution Detection Rule Lists Telemetry', + name: 'security:telemetry-detection-rules', passed: true, }, ]); From 7f4208ac9bdada419e8f75871e89370274e66ae1 Mon Sep 17 00:00:00 2001 From: Nathan Reese Date: Mon, 12 Feb 2024 14:22:01 -0700 Subject: [PATCH 27/83] [maps][ESQL] fix esql layers do not support spatial filters drawn on map (#176753) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Closes https://github.com/elastic/kibana/issues/176752 ### test instructions 1. install sample web logs 2. create new map 3. add "ES|QL" layer 4. verify drawing tools are enable on map and drawing a filter narrows ES|QL layer. Screenshot 2024-02-12 at 12 38 03 PM --- .../common/descriptor_types/source_descriptor_types.ts | 4 +++- x-pack/plugins/maps/public/classes/layers/layer.tsx | 5 ++++- .../maps/public/classes/sources/es_source/types.ts | 2 +- .../public/classes/sources/esql_source/esql_source.tsx | 10 +++++++++- 4 files changed, 17 insertions(+), 4 deletions(-) diff --git a/x-pack/plugins/maps/common/descriptor_types/source_descriptor_types.ts b/x-pack/plugins/maps/common/descriptor_types/source_descriptor_types.ts index ebbbfd3fde1c1..fe48ce446b5c7 100644 --- a/x-pack/plugins/maps/common/descriptor_types/source_descriptor_types.ts +++ b/x-pack/plugins/maps/common/descriptor_types/source_descriptor_types.ts @@ -51,7 +51,9 @@ export type ESQLSourceDescriptor = AbstractSourceDescriptor & { */ dateField?: string; /* - * Geo field used to narrow ES|QL requests by visible map area + * Geo field used to narrow ES|QL requests by + * 1. by visible map area + * 2. spatial filters drawn on map */ geoField?: string; narrowByGlobalSearch: boolean; diff --git a/x-pack/plugins/maps/public/classes/layers/layer.tsx b/x-pack/plugins/maps/public/classes/layers/layer.tsx index aa39cf017eb0d..9f9cda88c78b2 100644 --- a/x-pack/plugins/maps/public/classes/layers/layer.tsx +++ b/x-pack/plugins/maps/public/classes/layers/layer.tsx @@ -577,7 +577,10 @@ export class AbstractLayer implements ILayer { getGeoFieldNames(): string[] { const source = this.getSource(); - return hasESSourceMethod(source, 'getGeoFieldName') ? [source.getGeoFieldName()] : []; + const geoFieldName = hasESSourceMethod(source, 'getGeoFieldName') + ? source.getGeoFieldName() + : undefined; + return geoFieldName ? [geoFieldName] : []; } async getStyleMetaDescriptorFromLocalFeatures(): Promise { diff --git a/x-pack/plugins/maps/public/classes/sources/es_source/types.ts b/x-pack/plugins/maps/public/classes/sources/es_source/types.ts index 385a00b900488..1f0d698176f7c 100644 --- a/x-pack/plugins/maps/public/classes/sources/es_source/types.ts +++ b/x-pack/plugins/maps/public/classes/sources/es_source/types.ts @@ -49,7 +49,7 @@ export interface IESSource extends IVectorSource { getIndexPatternId(): string; - getGeoFieldName(): string; + getGeoFieldName(): string | undefined; loadStylePropsMeta({ layerName, diff --git a/x-pack/plugins/maps/public/classes/sources/esql_source/esql_source.tsx b/x-pack/plugins/maps/public/classes/sources/esql_source/esql_source.tsx index 53745e7426b70..1f95158c34b2a 100644 --- a/x-pack/plugins/maps/public/classes/sources/esql_source/esql_source.tsx +++ b/x-pack/plugins/maps/public/classes/sources/esql_source/esql_source.tsx @@ -28,6 +28,7 @@ import { isValidStringConfig } from '../../util/valid_string_config'; import type { SourceEditorArgs } from '../source'; import { AbstractVectorSource, getLayerFeaturesRequestName } from '../vector_source'; import type { IVectorSource, GeoJsonWithMeta, SourceStatus } from '../vector_source'; +import type { IESSource } from '../es_source'; import type { IField } from '../../fields/field'; import { InlineField } from '../../fields/inline_field'; import { getData, getUiSettings } from '../../../kibana_services'; @@ -44,7 +45,10 @@ export const sourceTitle = i18n.translate('xpack.maps.source.esqlSearchTitle', { defaultMessage: 'ES|QL', }); -export class ESQLSource extends AbstractVectorSource implements IVectorSource { +export class ESQLSource + extends AbstractVectorSource + implements IVectorSource, Pick +{ readonly _descriptor: ESQLSourceDescriptor; static createDescriptor(descriptor: Partial): ESQLSourceDescriptor { @@ -325,4 +329,8 @@ export class ESQLSource extends AbstractVectorSource implements IVectorSource { getIndexPatternId() { return this._descriptor.dataViewId; } + + getGeoFieldName() { + return this._descriptor.geoField; + } } From 509f9ed4fe7e598e809eeebfe804755043fb0c89 Mon Sep 17 00:00:00 2001 From: Rickyanto Ang Date: Mon, 12 Feb 2024 13:43:13 -0800 Subject: [PATCH 28/83] [Cloud Security] Added Toast message that pops up when user enables / disables rule (#176462) ## Summary This PR adds a toast message that pops up whenever user enables or disables rules. The toast message will show the number of rules that got disabled/enable and detection rules (if there's any) https://github.com/elastic/kibana/assets/8703149/6b394fc4-82ca-4afa-a6b4-e504e8a83f0c --- .../common/utils/detection_rules.test.ts | 59 +++++++++++++++++-- .../common/utils/detection_rules.ts | 33 ++++++++++- .../api/use_fetch_detection_rules_by_tags.ts | 39 ++++++++---- .../public/components/take_action.tsx | 44 ++++++++++++-- .../public/pages/rules/rules_flyout.tsx | 18 +++++- .../public/pages/rules/rules_table.tsx | 33 +++++++++-- .../public/pages/rules/rules_table_header.tsx | 15 +++++ .../benchmark_rules/bulk_action/utils.ts | 4 +- 8 files changed, 212 insertions(+), 33 deletions(-) diff --git a/x-pack/plugins/cloud_security_posture/common/utils/detection_rules.test.ts b/x-pack/plugins/cloud_security_posture/common/utils/detection_rules.test.ts index f2a35944f0825..a067ef4e1871a 100644 --- a/x-pack/plugins/cloud_security_posture/common/utils/detection_rules.test.ts +++ b/x-pack/plugins/cloud_security_posture/common/utils/detection_rules.test.ts @@ -7,25 +7,45 @@ import { CspBenchmarkRuleMetadata } from '../types'; import { - convertRuleTagsToKQL, + convertRuleTagsToMatchAllKQL, + convertRuleTagsToMatchAnyKQL, generateBenchmarkRuleTags, getFindingsDetectionRuleSearchTags, + getFindingsDetectionRuleSearchTagsFromArrayOfRules, } from './detection_rules'; describe('Detection rules utils', () => { - it('should convert tags to KQL format', () => { + it('should convert tags to KQL format with AND operator', () => { const inputTags = ['tag1', 'tag2', 'tag3']; - const result = convertRuleTagsToKQL(inputTags); + const result = convertRuleTagsToMatchAllKQL(inputTags); const expectedKQL = 'alert.attributes.tags:("tag1" AND "tag2" AND "tag3")'; expect(result).toBe(expectedKQL); }); - it('Should convert tags to KQL format', () => { + it('Should convert tags to KQL format with AND Operator (empty array)', () => { const inputTags = [] as string[]; - const result = convertRuleTagsToKQL(inputTags); + const result = convertRuleTagsToMatchAllKQL(inputTags); + + const expectedKQL = 'alert.attributes.tags:()'; + expect(result).toBe(expectedKQL); + }); + + it('should convert tags to KQL format with OR Operator', () => { + const inputTags = ['tag1', 'tag2', 'tag3']; + + const result = convertRuleTagsToMatchAnyKQL(inputTags); + + const expectedKQL = 'alert.attributes.tags:("tag1" OR "tag2" OR "tag3")'; + expect(result).toBe(expectedKQL); + }); + + it('Should convert tags to KQL format with OR Operator (empty array)', () => { + const inputTags = [] as string[]; + + const result = convertRuleTagsToMatchAnyKQL(inputTags); const expectedKQL = 'alert.attributes.tags:()'; expect(result).toBe(expectedKQL); @@ -63,6 +83,35 @@ describe('Detection rules utils', () => { expect(result).toEqual(expectedTags); }); + it('Should generate search tags for a CSP benchmark rule given an array of Benchmarks', () => { + const cspBenchmarkRule = [ + { + benchmark: { + id: 'cis_gcp', + rule_number: '1.1', + }, + }, + { + benchmark: { + id: 'cis_gcp', + rule_number: '1.2', + }, + }, + ] as unknown as CspBenchmarkRuleMetadata[]; + + const result = getFindingsDetectionRuleSearchTagsFromArrayOfRules(cspBenchmarkRule); + + const expectedTags = ['CIS GCP 1.1', 'CIS GCP 1.2']; + expect(result).toEqual(expectedTags); + }); + + it('Should handle undefined benchmark object gracefully given an array of empty benchmark', () => { + const cspBenchmarkRule = [{ benchmark: {} }] as any; + const expectedTags: string[] = []; + const result = getFindingsDetectionRuleSearchTagsFromArrayOfRules(cspBenchmarkRule); + expect(result).toEqual(expectedTags); + }); + it('Should generate tags for a CSPM benchmark rule', () => { const cspBenchmarkRule = { benchmark: { diff --git a/x-pack/plugins/cloud_security_posture/common/utils/detection_rules.ts b/x-pack/plugins/cloud_security_posture/common/utils/detection_rules.ts index 42ea7561286c1..f178d412675e6 100644 --- a/x-pack/plugins/cloud_security_posture/common/utils/detection_rules.ts +++ b/x-pack/plugins/cloud_security_posture/common/utils/detection_rules.ts @@ -13,9 +13,14 @@ const CSP_RULE_TAG_DATA_SOURCE_PREFIX = 'Data Source: '; const STATIC_RULE_TAGS = [CSP_RULE_TAG, CSP_RULE_TAG_USE_CASE]; -export const convertRuleTagsToKQL = (tags: string[]): string => { +export const convertRuleTagsToMatchAllKQL = (tags: string[]): string => { const TAGS_FIELD = 'alert.attributes.tags'; - return `${TAGS_FIELD}:(${tags.map((tag) => `"${tag}"`).join(' AND ')})`; + return `${TAGS_FIELD}:(${tags.map((tag) => `"${tag}"`).join(` AND `)})`; +}; + +export const convertRuleTagsToMatchAnyKQL = (tags: string[]): string => { + const TAGS_FIELD = 'alert.attributes.tags'; + return `${TAGS_FIELD}:(${tags.map((tag) => `"${tag}"`).join(` OR `)})`; }; /* @@ -42,6 +47,30 @@ export const getFindingsDetectionRuleSearchTags = ( return benchmarkIdTags.concat([benchmarkRuleNumberTag]); }; +export const getFindingsDetectionRuleSearchTagsFromArrayOfRules = ( + cspBenchmarkRules: CspBenchmarkRuleMetadata[] +): string[] => { + if ( + !cspBenchmarkRules || + !cspBenchmarkRules.some((rule) => rule.benchmark) || + !cspBenchmarkRules.some((rule) => rule.benchmark.id) + ) { + return []; + } + + // we can just take the first benchmark id because we Know that the array will ONLY contain 1 kind of id + const benchmarkIds = cspBenchmarkRules.map((rule) => rule.benchmark.id); + if (benchmarkIds.length === 0) return []; + const benchmarkId = benchmarkIds[0]; + const benchmarkRuleNumbers = cspBenchmarkRules.map((rule) => rule.benchmark.rule_number); + if (benchmarkRuleNumbers.length === 0) return []; + const benchmarkTagArray = benchmarkRuleNumbers.map( + (tag) => benchmarkId.replace('_', ' ').toUpperCase() + ' ' + tag + ); + // we want the tags to only consist of a format like this CIS AWS 1.1.0 + return benchmarkTagArray; +}; + export const generateBenchmarkRuleTags = (rule: CspBenchmarkRuleMetadata) => { return [STATIC_RULE_TAGS] .concat(getFindingsDetectionRuleSearchTags(rule)) diff --git a/x-pack/plugins/cloud_security_posture/public/common/api/use_fetch_detection_rules_by_tags.ts b/x-pack/plugins/cloud_security_posture/public/common/api/use_fetch_detection_rules_by_tags.ts index dfd6f13e38692..da95711cbb383 100644 --- a/x-pack/plugins/cloud_security_posture/public/common/api/use_fetch_detection_rules_by_tags.ts +++ b/x-pack/plugins/cloud_security_posture/public/common/api/use_fetch_detection_rules_by_tags.ts @@ -5,13 +5,16 @@ * 2.0. */ -import { CoreStart } from '@kbn/core/public'; +import { CoreStart, HttpSetup } from '@kbn/core/public'; import { useKibana } from '@kbn/kibana-react-plugin/public'; import { useQuery } from '@tanstack/react-query'; import { DETECTION_RULE_RULES_API_CURRENT_VERSION } from '../../../common/constants'; import { RuleResponse } from '../types'; import { DETECTION_ENGINE_RULES_KEY } from '../constants'; -import { convertRuleTagsToKQL } from '../../../common/utils/detection_rules'; +import { + convertRuleTagsToMatchAllKQL, + convertRuleTagsToMatchAnyKQL, +} from '../../../common/utils/detection_rules'; /* * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one @@ -31,20 +34,32 @@ const DETECTION_ENGINE_URL = '/api/detection_engine' as const; const DETECTION_ENGINE_RULES_URL = `${DETECTION_ENGINE_URL}/rules` as const; export const DETECTION_ENGINE_RULES_URL_FIND = `${DETECTION_ENGINE_RULES_URL}/_find` as const; -export const useFetchDetectionRulesByTags = (tags: string[]) => { +export const useFetchDetectionRulesByTags = ( + tags: string[], + option: { match: 'all' | 'any' } = { match: 'all' } +) => { const { http } = useKibana().services; + return useQuery([DETECTION_ENGINE_RULES_KEY, tags, option], () => + fetchDetectionRulesByTags(tags, option, http) + ); +}; +export const fetchDetectionRulesByTags = ( + tags: string[], + option: { match: 'all' | 'any' } = { match: 'all' }, + http: HttpSetup +) => { const query = { page: 1, per_page: 1, - filter: convertRuleTagsToKQL(tags), + filter: + option.match === 'all' + ? convertRuleTagsToMatchAllKQL(tags) + : convertRuleTagsToMatchAnyKQL(tags), }; - - return useQuery([DETECTION_ENGINE_RULES_KEY, tags], () => - http.fetch(DETECTION_ENGINE_RULES_URL_FIND, { - method: 'GET', - version: DETECTION_RULE_RULES_API_CURRENT_VERSION, - query, - }) - ); + return http.fetch(DETECTION_ENGINE_RULES_URL_FIND, { + method: 'GET', + version: DETECTION_RULE_RULES_API_CURRENT_VERSION, + query, + }); }; diff --git a/x-pack/plugins/cloud_security_posture/public/components/take_action.tsx b/x-pack/plugins/cloud_security_posture/public/components/take_action.tsx index caf7f34651997..054fc9f3759ff 100644 --- a/x-pack/plugins/cloud_security_posture/public/components/take_action.tsx +++ b/x-pack/plugins/cloud_security_posture/public/components/take_action.tsx @@ -80,7 +80,11 @@ export const showCreateDetectionRuleSuccessToast = ( export const showChangeBenchmarkRuleStatesSuccessToast = ( notifications: NotificationsStart, - isBenchmarkRuleMuted: boolean + isBenchmarkRuleMuted: boolean, + data: { + numberOfRules: number; + numberOfDetectionRules: number; + } ) => { return notifications.toasts.addSuccess({ toastLifeTimeMs: 10000, @@ -101,9 +105,23 @@ export const showChangeBenchmarkRuleStatesSuccessToast = ( + {data.numberOfDetectionRules > 0 ? ( + + + + ) : undefined} ) : ( <> @@ -115,10 +133,26 @@ export const showChangeBenchmarkRuleStatesSuccessToast = ( /> + + + {data.numberOfDetectionRules > 0 ? ( + + + + ) : undefined} )} diff --git a/x-pack/plugins/cloud_security_posture/public/pages/rules/rules_flyout.tsx b/x-pack/plugins/cloud_security_posture/public/pages/rules/rules_flyout.tsx index 333f958de6fb1..5026884173b6e 100644 --- a/x-pack/plugins/cloud_security_posture/public/pages/rules/rules_flyout.tsx +++ b/x-pack/plugins/cloud_security_posture/public/pages/rules/rules_flyout.tsx @@ -21,15 +21,20 @@ import { EuiToolTip, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; - import { FormattedMessage } from '@kbn/i18n-react'; +import { useKibana } from '../../common/hooks/use_kibana'; +import { getFindingsDetectionRuleSearchTags } from '../../../common/utils/detection_rules'; import { CspBenchmarkRuleMetadata } from '../../../common/types/latest'; import { getRuleList } from '../configurations/findings_flyout/rule_tab'; import { getRemediationList } from '../configurations/findings_flyout/overview_tab'; import * as TEST_SUBJECTS from './test_subjects'; import { useChangeCspRuleState } from './change_csp_rule_state'; import { CspBenchmarkRulesWithStates } from './rules_container'; -import { TakeAction } from '../../components/take_action'; +import { + showChangeBenchmarkRuleStatesSuccessToast, + TakeAction, +} from '../../components/take_action'; +import { useFetchDetectionRulesByTags } from '../../common/api/use_fetch_detection_rules_by_tags'; export const RULES_FLYOUT_SWITCH_BUTTON = 'rule-flyout-switch-button'; @@ -61,8 +66,11 @@ type RuleTab = typeof tabs[number]['id']; export const RuleFlyout = ({ onClose, rule, refetchRulesStates }: RuleFlyoutProps) => { const [tab, setTab] = useState('overview'); const postRequestChangeRulesStates = useChangeCspRuleState(); + const { data: rulesData } = useFetchDetectionRulesByTags( + getFindingsDetectionRuleSearchTags(rule.metadata) + ); const isRuleMuted = rule?.state === 'muted'; - + const { notifications } = useKibana().services; const switchRuleStates = async () => { if (rule.metadata.benchmark.rule_number) { const rulesObjectRequest = { @@ -74,6 +82,10 @@ export const RuleFlyout = ({ onClose, rule, refetchRulesStates }: RuleFlyoutProp const nextRuleStates = isRuleMuted ? 'unmute' : 'mute'; await postRequestChangeRulesStates(nextRuleStates, [rulesObjectRequest]); await refetchRulesStates(); + await showChangeBenchmarkRuleStatesSuccessToast(notifications, isRuleMuted, { + numberOfRules: 1, + numberOfDetectionRules: rulesData?.total || 0, + }); } }; diff --git a/x-pack/plugins/cloud_security_posture/public/pages/rules/rules_table.tsx b/x-pack/plugins/cloud_security_posture/public/pages/rules/rules_table.tsx index 81a04de69f174..586f0b9dee0cf 100644 --- a/x-pack/plugins/cloud_security_posture/public/pages/rules/rules_table.tsx +++ b/x-pack/plugins/cloud_security_posture/public/pages/rules/rules_table.tsx @@ -20,10 +20,15 @@ import { } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { uniqBy } from 'lodash'; +import { CoreStart, HttpSetup, NotificationsStart } from '@kbn/core/public'; +import { useKibana } from '@kbn/kibana-react-plugin/public'; +import { getFindingsDetectionRuleSearchTags } from '../../../common/utils/detection_rules'; import { ColumnNameWithTooltip } from '../../components/column_name_with_tooltip'; import type { CspBenchmarkRulesWithStates, RulesState } from './rules_container'; import * as TEST_SUBJECTS from './test_subjects'; import { RuleStateAttributesWithoutStates, useChangeCspRuleState } from './change_csp_rule_state'; +import { showChangeBenchmarkRuleStatesSuccessToast } from '../../components/take_action'; +import { fetchDetectionRulesByTags } from '../../common/api/use_fetch_detection_rules_by_tags'; export const RULES_ROWS_ENABLE_SWITCH_BUTTON = 'rules-row-enable-switch-button'; export const RULES_ROW_SELECT_ALL_CURRENT_PAGE = 'cloud-security-fields-selector-item-all'; @@ -56,6 +61,8 @@ type GetColumnProps = Pick< currentPageRulesArray: CspBenchmarkRulesWithStates[], selectedRulesArray: CspBenchmarkRulesWithStates[] ) => boolean; + notifications: NotificationsStart; + http: HttpSetup; }; export const RulesTable = ({ @@ -87,7 +94,6 @@ export const RulesTable = ({ direction: sortDirection, }, }; - const onTableChange = ({ page: pagination, sort: sortOrder, @@ -108,6 +114,7 @@ export const RulesTable = ({ }); const [isAllRulesSelectedThisPage, setIsAllRulesSelectedThisPage] = useState(false); + const postRequestChangeRulesStates = useChangeCspRuleState(); const isCurrentPageRulesASubset = ( @@ -125,6 +132,7 @@ export const RulesTable = ({ return true; }; + const { http, notifications } = useKibana().services; useEffect(() => { if (selectedRules.length >= items.length && items.length > 0 && selectedRules.length > 0) setIsAllRulesSelectedThisPage(true); @@ -143,6 +151,8 @@ export const RulesTable = ({ isAllRulesSelectedThisPage, isCurrentPageRulesASubset, onRuleClick, + notifications, + http, }), [ refetchRulesStates, @@ -152,6 +162,8 @@ export const RulesTable = ({ items, isAllRulesSelectedThisPage, onRuleClick, + notifications, + http, ] ); @@ -182,6 +194,8 @@ const getColumns = ({ isAllRulesSelectedThisPage, isCurrentPageRulesASubset, onRuleClick, + notifications, + http, }: GetColumnProps): Array> => [ { field: 'action', @@ -296,11 +310,22 @@ const getColumns = ({ }; const isRuleMuted = rule?.state === 'muted'; const nextRuleState = isRuleMuted ? 'unmute' : 'mute'; - - const useChangeCspRuleStateFn = async () => { + const changeCspRuleStateFn = async () => { if (rule?.metadata.benchmark.rule_number) { + // Calling this function this way to make sure it didn't get called on every single row render, its only being called when user click on the switch button + const detectionRuleCount = ( + await fetchDetectionRulesByTags( + getFindingsDetectionRuleSearchTags(rule.metadata), + { match: 'all' }, + http + ) + ).total; await postRequestChangeRulesStates(nextRuleState, [rulesObjectRequest]); await refetchRulesStates(); + await showChangeBenchmarkRuleStatesSuccessToast(notifications, isRuleMuted, { + numberOfRules: 1, + numberOfDetectionRules: detectionRuleCount || 0, + }); } }; return ( @@ -309,7 +334,7 @@ const getColumns = ({ !e); }; + const { data: rulesData } = useFetchDetectionRulesByTags( + getFindingsDetectionRuleSearchTagsFromArrayOfRules(selectedRules.map((rule) => rule.metadata)), + { match: 'any' } + ); + + const { notifications } = useKibana().services; + const postRequestChangeRulesState = useChangeCspRuleState(); const changeRulesState = async (state: 'mute' | 'unmute') => { const bulkSelectedRules: RuleStateAttributesWithoutStates[] = selectedRules.map( @@ -283,6 +294,10 @@ const CurrentPageOfTotal = ({ await postRequestChangeRulesState(state, bulkSelectedRules); await refetchRulesStates(); await setIsPopoverOpen(false); + await showChangeBenchmarkRuleStatesSuccessToast(notifications, state !== 'mute', { + numberOfRules: bulkSelectedRules.length, + numberOfDetectionRules: rulesData?.total || 0, + }); } }; const changeCspRuleStateMute = async () => { diff --git a/x-pack/plugins/cloud_security_posture/server/routes/benchmark_rules/bulk_action/utils.ts b/x-pack/plugins/cloud_security_posture/server/routes/benchmark_rules/bulk_action/utils.ts index 9efb4267a385d..5b592b6b9926c 100644 --- a/x-pack/plugins/cloud_security_posture/server/routes/benchmark_rules/bulk_action/utils.ts +++ b/x-pack/plugins/cloud_security_posture/server/routes/benchmark_rules/bulk_action/utils.ts @@ -15,7 +15,7 @@ import type { CspSettings, } from '../../../../common/types/rules/v4'; import { - convertRuleTagsToKQL, + convertRuleTagsToMatchAllKQL, generateBenchmarkRuleTags, } from '../../../../common/utils/detection_rules'; @@ -55,7 +55,7 @@ export const getDetectionRules = async ( return detectionRulesClient.find({ excludeFromPublicApi: false, options: { - filter: convertRuleTagsToKQL(ruleTags), + filter: convertRuleTagsToMatchAllKQL(ruleTags), searchFields: ['tags'], page: 1, per_page: 1, From 028b99332aae2a12f09975236bb19a25322a17be Mon Sep 17 00:00:00 2001 From: Nicolas Chaulet Date: Mon, 12 Feb 2024 17:21:41 -0500 Subject: [PATCH 29/83] [Fleet] Allow to skip package verification with force flag when creating a policy (#176738) --- .../plugins/fleet/common/openapi/bundled.json | 22 +- .../plugins/fleet/common/openapi/bundled.yaml | 203 +++++++++--------- .../schemas/agent_policy_create_request.yaml | 3 + .../schemas/agent_policy_update_request.yaml | 3 + .../server/routes/agent_policy/handlers.ts | 2 + .../fleet/server/services/agent_policy.ts | 1 + .../server/services/agent_policy_create.ts | 3 + .../fleet/server/types/models/agent_policy.ts | 1 + .../apis/agent_policy/agent_policy.ts | 5 +- 9 files changed, 134 insertions(+), 109 deletions(-) diff --git a/x-pack/plugins/fleet/common/openapi/bundled.json b/x-pack/plugins/fleet/common/openapi/bundled.json index 127ed8a877354..9418cca89888f 100644 --- a/x-pack/plugins/fleet/common/openapi/bundled.json +++ b/x-pack/plugins/fleet/common/openapi/bundled.json @@ -1,6 +1,5 @@ { "openapi": "3.0.0", - "tags": [], "info": { "title": "Fleet", "description": "OpenAPI schema for Fleet API endpoints", @@ -19,6 +18,12 @@ "description": "local" } ], + "security": [ + { + "basicAuth": [] + } + ], + "tags": [], "paths": { "/health_check": { "post": { @@ -7385,6 +7390,10 @@ }, "is_protected": { "type": "boolean" + }, + "force": { + "type": "boolean", + "description": "Force agent policy creation even if packages are not verified." } }, "required": [ @@ -7457,6 +7466,10 @@ }, "is_protected": { "type": "boolean" + }, + "force": { + "type": "boolean", + "description": "Force agent policy creation even if packages are not verified." } }, "required": [ @@ -9066,10 +9079,5 @@ ] } } - }, - "security": [ - { - "basicAuth": [] - } - ] + } } \ No newline at end of file diff --git a/x-pack/plugins/fleet/common/openapi/bundled.yaml b/x-pack/plugins/fleet/common/openapi/bundled.yaml index 849c22b47069a..04f7dc77e184f 100644 --- a/x-pack/plugins/fleet/common/openapi/bundled.yaml +++ b/x-pack/plugins/fleet/common/openapi/bundled.yaml @@ -1,5 +1,4 @@ openapi: 3.0.0 -tags: [] info: title: Fleet description: OpenAPI schema for Fleet API endpoints @@ -12,6 +11,9 @@ info: servers: - url: http://localhost:5601/api/fleet description: local +security: + - basicAuth: [] +tags: [] paths: /health_check: post: @@ -180,9 +182,7 @@ paths: id: type: string nullable: true - description: >- - the key ID of the GPG key used to verify package - signatures + description: the key ID of the GPG key used to verify package signatures statusCode: type: number headers: @@ -245,9 +245,7 @@ paths: schema: type: boolean default: false - description: >- - Whether to include prerelease packages in categories count (e.g. beta, - rc, preview) + description: Whether to include prerelease packages in categories count (e.g. beta, rc, preview) - in: query name: experimental deprecated: true @@ -301,20 +299,13 @@ paths: schema: type: boolean default: false - description: >- - Whether to exclude the install status of each package. Enabling this - option will opt in to caching for the response via `cache-control` - headers. If you don't need up-to-date installation info for a - package, and are querying for a list of available packages, - providing this flag can improve performance substantially. + description: Whether to exclude the install status of each package. Enabling this option will opt in to caching for the response via `cache-control` headers. If you don't need up-to-date installation info for a package, and are querying for a list of available packages, providing this flag can improve performance substantially. - in: query name: prerelease schema: type: boolean default: false - description: >- - Whether to return prerelease versions of packages (e.g. beta, rc, - preview) + description: Whether to return prerelease versions of packages (e.g. beta, rc, preview) - in: query name: experimental deprecated: true @@ -379,9 +370,7 @@ paths: schema: type: boolean default: false - description: >- - skip data stream rollover during index template mapping or settings - update + description: skip data stream rollover during index template mapping or settings update requestBody: content: application/zip: @@ -413,9 +402,7 @@ paths: schema: type: boolean default: false - description: >- - Whether to return prerelease versions of packages (e.g. beta, rc, - preview) + description: Whether to return prerelease versions of packages (e.g. beta, rc, preview) requestBody: content: application/json: @@ -487,9 +474,7 @@ paths: schema: type: boolean default: false - description: >- - Whether to return prerelease versions of packages (e.g. beta, rc, - preview) + description: Whether to return prerelease versions of packages (e.g. beta, rc, preview) deprecated: true post: summary: Install package @@ -541,9 +526,7 @@ paths: schema: type: boolean default: false - description: >- - skip data stream rollover during index template mapping or settings - update + description: skip data stream rollover during index template mapping or settings update requestBody: content: application/json: @@ -662,18 +645,14 @@ paths: - schema: type: boolean name: full - description: >- - Return all fields from the package manifest, not just those supported - by the Elastic Package Registry + description: Return all fields from the package manifest, not just those supported by the Elastic Package Registry in: query - in: query name: prerelease schema: type: boolean default: false - description: >- - Whether to return prerelease versions of packages (e.g. beta, rc, - preview) + description: Whether to return prerelease versions of packages (e.g. beta, rc, preview) post: summary: Install package tags: @@ -728,9 +707,7 @@ paths: schema: type: boolean default: false - description: >- - skip data stream rollover during index template mapping or settings - update + description: skip data stream rollover during index template mapping or settings update requestBody: content: application/json: @@ -828,6 +805,70 @@ paths: properties: force: type: boolean + /epm/packages/{pkgName}/{pkgVersion}/transforms/authorize: + post: + summary: Authorize transforms + tags: + - Elastic Package Manager (EPM) + responses: + '200': + description: OK + content: + application/json: + schema: + type: object + properties: + items: + type: array + items: + type: object + properties: + transformId: + type: string + success: + type: boolean + error: + type: string + required: + - transformId + - error + required: + - items + '400': + $ref: '#/components/responses/error' + operationId: reauthorize-transforms + description: '' + parameters: + - $ref: '#/components/parameters/kbn_xsrf' + - schema: + type: string + name: pkgName + in: path + required: true + - schema: + type: string + name: pkgVersion + in: path + required: true + - in: query + name: prerelease + schema: + type: boolean + default: false + description: Whether to include prerelease packages in categories count (e.g. beta, rc, preview) + requestBody: + content: + application/json: + schema: + type: object + properties: + transforms: + type: array + items: + type: object + properties: + transformId: + type: string /epm/packages/{pkgName}/{pkgVersion}/{filePath}: get: summary: Get package file @@ -1301,9 +1342,7 @@ paths: description: creation time of action latestErrors: type: array - description: >- - latest errors that happened when the agents executed - the action + description: latest errors that happened when the agents executed the action items: type: object properties: @@ -1771,9 +1810,7 @@ paths: description: Unenrolls hosted agents too includeInactive: type: boolean - description: >- - When passing agents by KQL query, unenrolls inactive agents - too + description: When passing agents by KQL query, unenrolls inactive agents too required: - agents example: @@ -1956,18 +1993,12 @@ paths: type: boolean in: query name: full - description: >- - When set to true, retrieve the related package policies for each - agent policy. + description: When set to true, retrieve the related package policies for each agent policy. - schema: type: boolean in: query name: noAgentCount - description: >- - When set to true, do not count how many agents are in the agent - policy, this can improve performance if you are searching over a - large number of agent policies. The "agents" property will always be - 0 if set to true. + description: When set to true, do not count how many agents are in the agent policy, this can improve performance if you are searching over a large number of agent policies. The "agents" property will always be 0 if set to true. description: '' post: summary: Create agent policy @@ -2547,9 +2578,7 @@ paths: '409': $ref: '#/components/responses/error' requestBody: - description: >- - You should use inputs as an object and not use the deprecated inputs - array. + description: You should use inputs as an object and not use the deprecated inputs array. content: application/json: schema: @@ -4013,9 +4042,7 @@ components: release: type: string deprecated: true - description: >- - release label is deprecated, derive from the version instead - (packages follow semver) + description: release label is deprecated, derive from the version instead (packages follow semver) enum: - experimental - beta @@ -4294,9 +4321,7 @@ components: properties: cpu_avg: type: number - description: >- - Average agent CPU usage during the last 5 minutes, number - between 0-1 + description: Average agent CPU usage during the last 5 minutes, number between 0-1 memory_size_byte_avg: type: number description: Average agent memory consumption during the last 5 minutes @@ -4557,9 +4582,7 @@ components: - metrics - logs keep_monitoring_alive: - description: >- - When set to true, monitoring will be enabled but logs/metrics - collection will be disabled + description: When set to true, monitoring will be enabled but logs/metrics collection will be disabled type: boolean nullable: true data_output_id: @@ -4579,10 +4602,7 @@ components: inactivity_timeout: type: integer package_policies: - description: >- - This field is present only when retrieving a single agent policy, or - when retrieving a list of agent policies with the ?full=true - parameter + description: This field is present only when retrieving a single agent policy, or when retrieving a list of agent policies with the ?full=true parameter type: array items: $ref: '#/components/schemas/package_policy' @@ -4608,16 +4628,11 @@ components: - name - enabled is_protected: - description: >- - Indicates whether the agent policy has tamper protection enabled. - Default false. + description: Indicates whether the agent policy has tamper protection enabled. Default false. type: boolean overrides: type: object - description: >- - Override settings that are defined in the agent policy. Input - settings cannot be overridden. The override option should be used - only in unusual circumstances and not as a routine procedure. + description: Override settings that are defined in the agent policy. Input settings cannot be overridden. The override option should be used only in unusual circumstances and not as a routine procedure. nullable: true required: - id @@ -4673,6 +4688,9 @@ components: - enabled is_protected: type: boolean + force: + type: boolean + description: Force agent policy creation even if packages are not verified. required: - name - namespace @@ -4723,6 +4741,9 @@ components: - enabled is_protected: type: boolean + force: + type: boolean + description: Force agent policy creation even if packages are not verified. required: - name - namespace @@ -4936,9 +4957,7 @@ components: example: my description namespace: type: string - description: >- - The package policy namespace. Leave blank to inherit the agent - policy's namespace. + description: The package policy namespace. Leave blank to inherit the agent policy's namespace. example: customnamespace policy_id: type: string @@ -4960,14 +4979,10 @@ components: - version vars: type: object - description: >- - Package root level variable (see integration documentation for more - information) + description: Package root level variable (see integration documentation for more information) inputs: type: object - description: >- - Package policy inputs (see integration documentation to know what - inputs are available) + description: Package policy inputs (see integration documentation to know what inputs are available) example: nginx-logfile: enabled: true @@ -4989,14 +5004,10 @@ components: description: enable or disable that input, (default to true) vars: type: object - description: >- - Input level variable (see integration documentation for more - information) + description: Input level variable (see integration documentation for more information) streams: type: object - description: >- - Input streams (see integration documentation to know what - streams are available) + description: Input streams (see integration documentation to know what streams are available) additionalProperties: type: object properties: @@ -5005,14 +5016,10 @@ components: description: enable or disable that stream, (default to true) vars: type: object - description: >- - Stream level variable (see integration documentation for - more information) + description: Stream level variable (see integration documentation for more information) force: type: boolean - description: >- - Force package policy creation even if package is not verified, or if - the agent policy is managed. + description: Force package policy creation even if package is not verified, or if the agent policy is managed. required: - name - policy_id @@ -5747,9 +5754,7 @@ components: host: type: string proxy_id: - description: >- - The ID of the proxy to use for this download source. See the proxies - API for more information. + description: The ID of the proxy to use for this download source. See the proxies API for more information. type: string nullable: true required: @@ -5801,5 +5806,3 @@ components: required: - name - url -security: - - basicAuth: [] diff --git a/x-pack/plugins/fleet/common/openapi/components/schemas/agent_policy_create_request.yaml b/x-pack/plugins/fleet/common/openapi/components/schemas/agent_policy_create_request.yaml index e1d94c69a0d24..d2b69e37672e8 100644 --- a/x-pack/plugins/fleet/common/openapi/components/schemas/agent_policy_create_request.yaml +++ b/x-pack/plugins/fleet/common/openapi/components/schemas/agent_policy_create_request.yaml @@ -46,6 +46,9 @@ properties: - enabled is_protected: type: boolean + force: + type: boolean + description: Force agent policy creation even if packages are not verified. required: - name - namespace diff --git a/x-pack/plugins/fleet/common/openapi/components/schemas/agent_policy_update_request.yaml b/x-pack/plugins/fleet/common/openapi/components/schemas/agent_policy_update_request.yaml index 0500c94871192..7fb5581aa79e4 100644 --- a/x-pack/plugins/fleet/common/openapi/components/schemas/agent_policy_update_request.yaml +++ b/x-pack/plugins/fleet/common/openapi/components/schemas/agent_policy_update_request.yaml @@ -44,6 +44,9 @@ properties: - enabled is_protected: type: boolean + force: + type: boolean + description: Force agent policy creation even if packages are not verified. required: - name - namespace diff --git a/x-pack/plugins/fleet/server/routes/agent_policy/handlers.ts b/x-pack/plugins/fleet/server/routes/agent_policy/handlers.ts index 68237bb4e0ac9..259314c0a8c9e 100644 --- a/x-pack/plugins/fleet/server/routes/agent_policy/handlers.ts +++ b/x-pack/plugins/fleet/server/routes/agent_policy/handlers.ts @@ -172,6 +172,7 @@ export const createAgentPolicyHandler: FleetRequestHandler< const user = (await appContextService.getSecurity()?.authc.getCurrentUser(request)) || undefined; const withSysMonitoring = request.query.sys_monitoring ?? false; const monitoringEnabled = request.body.monitoring_enabled; + const force = request.body.force; const { has_fleet_server: hasFleetServer, ...newPolicy } = request.body; const spaceId = fleetContext.spaceId; const authorizationHeader = HTTPAuthorizationHeader.parseFromRequest(request, user?.username); @@ -188,6 +189,7 @@ export const createAgentPolicyHandler: FleetRequestHandler< spaceId, user, authorizationHeader, + force, }), }; diff --git a/x-pack/plugins/fleet/server/services/agent_policy.ts b/x-pack/plugins/fleet/server/services/agent_policy.ts index a532aab68b228..5907d07b4da38 100644 --- a/x-pack/plugins/fleet/server/services/agent_policy.ts +++ b/x-pack/plugins/fleet/server/services/agent_policy.ts @@ -603,6 +603,7 @@ class AgentPolicyService { packagesToInstall, spaceId: options?.spaceId || DEFAULT_SPACE_ID, authorizationHeader: options?.authorizationHeader, + force: options?.force, }); } diff --git a/x-pack/plugins/fleet/server/services/agent_policy_create.ts b/x-pack/plugins/fleet/server/services/agent_policy_create.ts index 9d1b3a9f01a5f..a55541c621f83 100644 --- a/x-pack/plugins/fleet/server/services/agent_policy_create.ts +++ b/x-pack/plugins/fleet/server/services/agent_policy_create.ts @@ -91,6 +91,7 @@ interface CreateAgentPolicyParams { spaceId: string; user?: AuthenticatedUser; authorizationHeader?: HTTPAuthorizationHeader | null; + force?: boolean; } export async function createAgentPolicyWithPackages({ @@ -103,6 +104,7 @@ export async function createAgentPolicyWithPackages({ spaceId, user, authorizationHeader, + force, }: CreateAgentPolicyParams) { let agentPolicyId = newPolicy.id; const packagesToInstall = []; @@ -128,6 +130,7 @@ export async function createAgentPolicyWithPackages({ packagesToInstall, spaceId, authorizationHeader, + force, }); } diff --git a/x-pack/plugins/fleet/server/types/models/agent_policy.ts b/x-pack/plugins/fleet/server/types/models/agent_policy.ts index 8fbad6d90fdaa..518510f0b8454 100644 --- a/x-pack/plugins/fleet/server/types/models/agent_policy.ts +++ b/x-pack/plugins/fleet/server/types/models/agent_policy.ts @@ -85,6 +85,7 @@ export const AgentPolicyBaseSchema = { export const NewAgentPolicySchema = schema.object({ ...AgentPolicyBaseSchema, + force: schema.maybe(schema.boolean()), }); export const AgentPolicySchema = schema.object({ diff --git a/x-pack/test/fleet_api_integration/apis/agent_policy/agent_policy.ts b/x-pack/test/fleet_api_integration/apis/agent_policy/agent_policy.ts index e27dff4e9d081..a8a22bd66ed0c 100644 --- a/x-pack/test/fleet_api_integration/apis/agent_policy/agent_policy.ts +++ b/x-pack/test/fleet_api_integration/apis/agent_policy/agent_policy.ts @@ -972,8 +972,7 @@ export default function (providerContext: FtrProviderContext) { ); }); - // Skipped as cannot force install the system and agent integrations as part of policy creation https://github.com/elastic/kibana/issues/137450 - it.skip('should return a 200 if updating monitoring_enabled on a policy', async () => { + it('should return a 200 if updating monitoring_enabled on a policy', async () => { const fetchPackageList = async () => { const response = await supertest .get('/api/fleet/epm/packages') @@ -1017,6 +1016,7 @@ export default function (providerContext: FtrProviderContext) { description: 'Updated description', namespace: 'default', monitoring_enabled: ['logs', 'metrics'], + force: true, }) .expect(200); // eslint-disable-next-line @typescript-eslint/naming-convention @@ -1029,6 +1029,7 @@ export default function (providerContext: FtrProviderContext) { description: 'Updated description', namespace: 'default', is_managed: false, + is_protected: false, revision: 2, schema_version: FLEET_AGENT_POLICIES_SCHEMA_VERSION, updated_by: 'elastic', From ed2b987b3ca9cc68d3c8953aab44e25295c40aa9 Mon Sep 17 00:00:00 2001 From: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Date: Mon, 12 Feb 2024 17:22:00 -0500 Subject: [PATCH 30/83] skip failing test suite (#176757) --- .../cypress/e2e/explore/navigation/navigation.cy.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/explore/navigation/navigation.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/explore/navigation/navigation.cy.ts index 1920bd01668f6..3be9029bcb2d4 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/explore/navigation/navigation.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/explore/navigation/navigation.cy.ts @@ -88,7 +88,8 @@ import { THREAT_INTELLIGENCE_PAGE, } from '../../../screens/kibana_navigation'; -describe('top-level navigation common to all pages in the Security app', { tags: '@ess' }, () => { +// Failing: See https://github.com/elastic/kibana/issues/176757 +describe.skip('top-level navigation common to all pages in the Security app', { tags: '@ess' }, () => { beforeEach(() => { login(); visitWithTimeRange(TIMELINES_URL); From c4936521eeb9242a0230adc84969a7c5f2862488 Mon Sep 17 00:00:00 2001 From: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Date: Mon, 12 Feb 2024 17:22:24 -0500 Subject: [PATCH 31/83] skip failing test suite (#176759) --- .../cypress/e2e/explore/navigation/navigation.cy.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/explore/navigation/navigation.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/explore/navigation/navigation.cy.ts index 3be9029bcb2d4..13cb9a3dcae32 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/explore/navigation/navigation.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/explore/navigation/navigation.cy.ts @@ -89,6 +89,7 @@ import { } from '../../../screens/kibana_navigation'; // Failing: See https://github.com/elastic/kibana/issues/176757 +// Failing: See https://github.com/elastic/kibana/issues/176759 describe.skip('top-level navigation common to all pages in the Security app', { tags: '@ess' }, () => { beforeEach(() => { login(); From 105138c60178eb60bf79d8a6dd876c06ddd01a28 Mon Sep 17 00:00:00 2001 From: Jonathan Budzenski Date: Mon, 12 Feb 2024 16:23:21 -0600 Subject: [PATCH 32/83] skip failing test suite (#176600) --- .../public/components/custom_fields/text/configure.test.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/x-pack/plugins/cases/public/components/custom_fields/text/configure.test.tsx b/x-pack/plugins/cases/public/components/custom_fields/text/configure.test.tsx index 455163f225a2b..29d3f4268443a 100644 --- a/x-pack/plugins/cases/public/components/custom_fields/text/configure.test.tsx +++ b/x-pack/plugins/cases/public/components/custom_fields/text/configure.test.tsx @@ -12,7 +12,8 @@ import userEvent from '@testing-library/user-event'; import { FormTestComponent } from '../../../common/test_utils'; import { Configure } from './configure'; -describe('Configure ', () => { +// Failing: See https://github.com/elastic/kibana/issues/176600 +describe.skip('Configure ', () => { const onSubmit = jest.fn(); beforeEach(() => { From 5562cf4d8f4bb6abd3093a3734de57edf405b0d7 Mon Sep 17 00:00:00 2001 From: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Date: Mon, 12 Feb 2024 17:24:44 -0500 Subject: [PATCH 33/83] skip failing test suite (#176758) --- .../cypress/e2e/explore/navigation/navigation.cy.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/explore/navigation/navigation.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/explore/navigation/navigation.cy.ts index 13cb9a3dcae32..60b48e02e6774 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/explore/navigation/navigation.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/explore/navigation/navigation.cy.ts @@ -90,6 +90,7 @@ import { // Failing: See https://github.com/elastic/kibana/issues/176757 // Failing: See https://github.com/elastic/kibana/issues/176759 +// Failing: See https://github.com/elastic/kibana/issues/176758 describe.skip('top-level navigation common to all pages in the Security app', { tags: '@ess' }, () => { beforeEach(() => { login(); From 3b08e74e58d9fa6b65a5fcf9c26d5de6b0278af4 Mon Sep 17 00:00:00 2001 From: Jonathan Budzenski Date: Mon, 12 Feb 2024 16:31:26 -0600 Subject: [PATCH 34/83] fix lint error --- .../e2e/explore/navigation/navigation.cy.ts | 246 +++++++++--------- 1 file changed, 125 insertions(+), 121 deletions(-) diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/explore/navigation/navigation.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/explore/navigation/navigation.cy.ts index 60b48e02e6774..70ecbc771eb3a 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/explore/navigation/navigation.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/explore/navigation/navigation.cy.ts @@ -91,127 +91,131 @@ import { // Failing: See https://github.com/elastic/kibana/issues/176757 // Failing: See https://github.com/elastic/kibana/issues/176759 // Failing: See https://github.com/elastic/kibana/issues/176758 -describe.skip('top-level navigation common to all pages in the Security app', { tags: '@ess' }, () => { - beforeEach(() => { - login(); - visitWithTimeRange(TIMELINES_URL); - }); - - it('navigates to the Dashboards landing page', () => { - navigateFromHeaderTo(DASHBOARDS); - cy.url().should('include', DASHBOARDS_URL); - }); - - it('navigates to the Overview page', () => { - navigateFromHeaderTo(OVERVIEW); - cy.url().should('include', OVERVIEW_URL); - }); - - it('navigates to the Detection & Response page', () => { - navigateFromHeaderTo(DETECTION_RESPONSE); - cy.url().should('include', DETECTION_AND_RESPONSE_URL); - }); - - it('navigates to the Entity Analytics page', () => { - navigateFromHeaderTo(ENTITY_ANALYTICS); - cy.url().should('include', ENTITY_ANALYTICS_URL); - }); - - it('navigates to the Kubernetes page', () => { - navigateFromHeaderTo(KUBERNETES); - cy.url().should('include', KUBERNETES_URL); - }); - - it('navigates to the CSP dashboard page', () => { - navigateFromHeaderTo(CSP_DASHBOARD); - cy.url().should('include', CSP_DASHBOARD_URL); - }); - - it('navigates to the Alerts page', () => { - navigateFromHeaderTo(ALERTS); - cy.url().should('include', ALERTS_URL); - }); - - it('navigates to the Findings page', () => { - navigateFromHeaderTo(CSP_FINDINGS); - cy.url().should('include', CSP_FINDINGS_URL); - }); - - it('navigates to the Timelines page', () => { - navigateFromHeaderTo(TIMELINES); - cy.url().should('include', TIMELINES_URL); - }); - - it('navigates to the Explore landing page', () => { - navigateFromHeaderTo(EXPLORE); - cy.url().should('include', EXPLORE_URL); - }); - - it('navigates to the Hosts page', () => { - navigateFromHeaderTo(HOSTS); - cy.url().should('include', HOSTS_URL); - }); - - it('navigates to the Network page', () => { - navigateFromHeaderTo(NETWORK); - cy.url().should('include', NETWORK_URL); - }); - - it('navigates to the Users page', () => { - navigateFromHeaderTo(USERS); - cy.url().should('include', USERS_URL); - }); - - it('navigates to the Indicators page', () => { - navigateFromHeaderTo(INDICATORS); - cy.url().should('include', INDICATORS_URL); - }); - - it('navigates to the Rules page', () => { - navigateFromHeaderTo(RULES); - cy.url().should('include', RULES_MANAGEMENT_URL); - }); - - it('navigates to the Exceptions page', () => { - navigateFromHeaderTo(EXCEPTIONS); - cy.url().should('include', EXCEPTIONS_URL); - }); - - it('navigates to the Cases page', () => { - navigateFromHeaderTo(CASES); - cy.url().should('include', CASES_URL); - }); - - it('navigates to the Manage landing page', () => { - navigateFromHeaderTo(SETTINGS); - cy.url().should('include', MANAGE_URL); - }); - - it('navigates to the Endpoints page', () => { - navigateFromHeaderTo(ENDPOINTS); - cy.url().should('include', ENDPOINTS_URL); - }); - it('navigates to the Policies page', () => { - navigateFromHeaderTo(POLICIES); - cy.url().should('include', POLICIES_URL); - }); - it('navigates to the Trusted Apps page', () => { - navigateFromHeaderTo(TRUSTED_APPS); - cy.url().should('include', TRUSTED_APPS_URL); - }); - it('navigates to the Event Filters page', () => { - navigateFromHeaderTo(EVENT_FILTERS); - cy.url().should('include', EVENT_FILTERS_URL); - }); - it('navigates to the Blocklist page', () => { - navigateFromHeaderTo(BLOCKLIST); - cy.url().should('include', BLOCKLIST_URL); - }); - it('navigates to the CSP Benchmarks page', () => { - navigateFromHeaderTo(CSP_BENCHMARKS); - cy.url().should('include', CSP_BENCHMARKS_URL); - }); -}); +describe.skip( + 'top-level navigation common to all pages in the Security app', + { tags: '@ess' }, + () => { + beforeEach(() => { + login(); + visitWithTimeRange(TIMELINES_URL); + }); + + it('navigates to the Dashboards landing page', () => { + navigateFromHeaderTo(DASHBOARDS); + cy.url().should('include', DASHBOARDS_URL); + }); + + it('navigates to the Overview page', () => { + navigateFromHeaderTo(OVERVIEW); + cy.url().should('include', OVERVIEW_URL); + }); + + it('navigates to the Detection & Response page', () => { + navigateFromHeaderTo(DETECTION_RESPONSE); + cy.url().should('include', DETECTION_AND_RESPONSE_URL); + }); + + it('navigates to the Entity Analytics page', () => { + navigateFromHeaderTo(ENTITY_ANALYTICS); + cy.url().should('include', ENTITY_ANALYTICS_URL); + }); + + it('navigates to the Kubernetes page', () => { + navigateFromHeaderTo(KUBERNETES); + cy.url().should('include', KUBERNETES_URL); + }); + + it('navigates to the CSP dashboard page', () => { + navigateFromHeaderTo(CSP_DASHBOARD); + cy.url().should('include', CSP_DASHBOARD_URL); + }); + + it('navigates to the Alerts page', () => { + navigateFromHeaderTo(ALERTS); + cy.url().should('include', ALERTS_URL); + }); + + it('navigates to the Findings page', () => { + navigateFromHeaderTo(CSP_FINDINGS); + cy.url().should('include', CSP_FINDINGS_URL); + }); + + it('navigates to the Timelines page', () => { + navigateFromHeaderTo(TIMELINES); + cy.url().should('include', TIMELINES_URL); + }); + + it('navigates to the Explore landing page', () => { + navigateFromHeaderTo(EXPLORE); + cy.url().should('include', EXPLORE_URL); + }); + + it('navigates to the Hosts page', () => { + navigateFromHeaderTo(HOSTS); + cy.url().should('include', HOSTS_URL); + }); + + it('navigates to the Network page', () => { + navigateFromHeaderTo(NETWORK); + cy.url().should('include', NETWORK_URL); + }); + + it('navigates to the Users page', () => { + navigateFromHeaderTo(USERS); + cy.url().should('include', USERS_URL); + }); + + it('navigates to the Indicators page', () => { + navigateFromHeaderTo(INDICATORS); + cy.url().should('include', INDICATORS_URL); + }); + + it('navigates to the Rules page', () => { + navigateFromHeaderTo(RULES); + cy.url().should('include', RULES_MANAGEMENT_URL); + }); + + it('navigates to the Exceptions page', () => { + navigateFromHeaderTo(EXCEPTIONS); + cy.url().should('include', EXCEPTIONS_URL); + }); + + it('navigates to the Cases page', () => { + navigateFromHeaderTo(CASES); + cy.url().should('include', CASES_URL); + }); + + it('navigates to the Manage landing page', () => { + navigateFromHeaderTo(SETTINGS); + cy.url().should('include', MANAGE_URL); + }); + + it('navigates to the Endpoints page', () => { + navigateFromHeaderTo(ENDPOINTS); + cy.url().should('include', ENDPOINTS_URL); + }); + it('navigates to the Policies page', () => { + navigateFromHeaderTo(POLICIES); + cy.url().should('include', POLICIES_URL); + }); + it('navigates to the Trusted Apps page', () => { + navigateFromHeaderTo(TRUSTED_APPS); + cy.url().should('include', TRUSTED_APPS_URL); + }); + it('navigates to the Event Filters page', () => { + navigateFromHeaderTo(EVENT_FILTERS); + cy.url().should('include', EVENT_FILTERS_URL); + }); + it('navigates to the Blocklist page', () => { + navigateFromHeaderTo(BLOCKLIST); + cy.url().should('include', BLOCKLIST_URL); + }); + it('navigates to the CSP Benchmarks page', () => { + navigateFromHeaderTo(CSP_BENCHMARKS); + cy.url().should('include', CSP_BENCHMARKS_URL); + }); + } +); describe('Kibana navigation to all pages in the Security app ', { tags: '@ess' }, () => { beforeEach(() => { From 6010b64204e19dae73766d150279a8b998027143 Mon Sep 17 00:00:00 2001 From: "Quynh Nguyen (Quinn)" <43350163+qn895@users.noreply.github.com> Date: Mon, 12 Feb 2024 17:04:46 -0600 Subject: [PATCH 35/83] [ML] Enhance support for ES|QL Data visualizer (#176515) ## Summary This PR enhances support for ES|QL data visualizer. Changes include: - Add an Update button that when clicked, will update and run the query. This is to complement the current cmd + Enter keyboard short cut. https://github.com/elastic/kibana/assets/43350163/5ca3ac0b-782e-404c-a04b-330c8eea6ab7 - Improve logic to no longer fetch total count & document count if only the limit size is updated (so changing the limit size, but not the query or time, will not refresh the count chart again) - Remove dependency from data view's field format - Refactor into a data fetching & processing into common hook to be used for embeddable - Support ES|QL in Field stats embeddable - Fix count % of documents where field exists is > 100% when there are multi-field values. (E.g. when row is an array of values like ["a", "b", "c"], the count is much higher than the total number of rows) Screenshot 2024-02-09 at 12 48 13 - Add support for `geo_point` and `geo_shape` field types Screenshot 2024-02-09 at 12 47 37 ### 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 - [ ] [Flaky Test Runner](https://ci-stats.kibana.dev/trigger_flaky_test_runner/1) was used on any tests changed - [ ] 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) --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> --- .../src/components/date_picker_wrapper.tsx | 40 +- .../geo_point_content_with_map.tsx | 63 +- .../expanded_row/index_based_expanded_row.tsx | 11 +- .../field_data_row/action_menu/actions.ts | 10 +- .../field_data_row/document_stats.tsx | 2 +- .../field_data_row/number_content_preview.tsx | 2 +- .../components/top_values/top_values.tsx | 2 +- .../index_data_visualizer_esql.tsx | 702 +++--------------- .../index_data_visualizer_view.tsx | 39 +- .../search_panel/esql/limit_size.tsx | 5 +- .../constants/index_data_visualizer_viewer.ts | 21 + .../embeddable_esql_field_stats_table.tsx | 74 ++ .../embeddable_field_stats_no_results.tsx | 31 + .../embeddable_field_stats_table.tsx | 96 +++ .../grid_embeddable/grid_embeddable.tsx | 167 +---- .../grid_embeddable_factory.tsx | 6 +- .../embeddables/grid_embeddable/types.ts | 59 ++ .../esql/use_data_visualizer_esql_data.tsx | 628 ++++++++++++++++ .../hooks/esql/use_esql_field_stats_data.ts | 15 +- .../hooks/esql/use_esql_overall_stats_data.ts | 68 +- .../hooks/use_data_visualizer_grid_data.ts | 8 +- .../hooks/use_overall_stats.ts | 6 +- .../esql_requests/get_boolean_field_stats.ts | 2 +- .../get_count_and_cardinality.ts | 102 ++- .../esql_requests/get_text_field_stats.ts | 2 +- .../types/index_data_visualizer_state.ts | 13 + .../index_data_visualizer/types/storage.ts | 2 +- 27 files changed, 1314 insertions(+), 862 deletions(-) create mode 100644 x-pack/plugins/data_visualizer/public/application/index_data_visualizer/embeddables/grid_embeddable/embeddable_esql_field_stats_table.tsx create mode 100644 x-pack/plugins/data_visualizer/public/application/index_data_visualizer/embeddables/grid_embeddable/embeddable_field_stats_no_results.tsx create mode 100644 x-pack/plugins/data_visualizer/public/application/index_data_visualizer/embeddables/grid_embeddable/embeddable_field_stats_table.tsx create mode 100644 x-pack/plugins/data_visualizer/public/application/index_data_visualizer/embeddables/grid_embeddable/types.ts create mode 100644 x-pack/plugins/data_visualizer/public/application/index_data_visualizer/hooks/esql/use_data_visualizer_esql_data.tsx diff --git a/x-pack/packages/ml/date_picker/src/components/date_picker_wrapper.tsx b/x-pack/packages/ml/date_picker/src/components/date_picker_wrapper.tsx index d16f74561984c..bc1f025309233 100644 --- a/x-pack/packages/ml/date_picker/src/components/date_picker_wrapper.tsx +++ b/x-pack/packages/ml/date_picker/src/components/date_picker_wrapper.tsx @@ -94,6 +94,15 @@ interface DatePickerWrapperProps { * Boolean flag to disable the date picker */ isDisabled?: boolean; + /** + * Boolean flag to force change from 'Refresh' to 'Update' state + */ + needsUpdate?: boolean; + /** + * Callback function that gets called + * when EuiSuperDatePicker's 'Refresh'|'Update' button is clicked + */ + onRefresh?: () => void; } /** @@ -111,6 +120,8 @@ export const DatePickerWrapper: FC = (props) => { width, flexGroup = true, isDisabled = false, + needsUpdate, + onRefresh, } = props; const { data, @@ -285,6 +296,12 @@ export const DatePickerWrapper: FC = (props) => { setRefreshInterval({ pause, value }); } + const handleRefresh = useCallback(() => { + updateLastRefresh(); + if (onRefresh) { + onRefresh(); + } + }, [onRefresh]); const flexItems = ( <> @@ -296,12 +313,16 @@ export const DatePickerWrapper: FC = (props) => { isAutoRefreshOnly={!isTimeRangeSelectorEnabled || isAutoRefreshOnly} refreshInterval={refreshInterval.value || DEFAULT_REFRESH_INTERVAL_MS} onTimeChange={updateTimeFilter} - onRefresh={updateLastRefresh} + onRefresh={handleRefresh} onRefreshChange={updateInterval} recentlyUsedRanges={recentlyUsedRanges} dateFormat={dateFormat} commonlyUsedRanges={commonlyUsedRanges} - updateButtonProps={{ iconOnly: isWithinLBreakpoint, fill: false }} + updateButtonProps={{ + iconOnly: isWithinLBreakpoint, + fill: false, + ...(needsUpdate ? { needsUpdate } : {}), + }} width={width} isDisabled={isDisabled} /> @@ -310,13 +331,20 @@ export const DatePickerWrapper: FC = (props) => { updateLastRefresh()} + color={needsUpdate ? 'success' : 'primary'} + iconType={needsUpdate ? 'kqlFunction' : 'refresh'} + onClick={handleRefresh} data-test-subj={`mlDatePickerRefreshPageButton${isLoading ? ' loading' : ' loaded'}`} isLoading={isLoading} > - + {needsUpdate ? ( + + ) : ( + + )} ) : null} diff --git a/x-pack/plugins/data_visualizer/public/application/common/components/expanded_row/geo_point_content_with_map/geo_point_content_with_map.tsx b/x-pack/plugins/data_visualizer/public/application/common/components/expanded_row/geo_point_content_with_map/geo_point_content_with_map.tsx index f12b65569be1c..768afda241792 100644 --- a/x-pack/plugins/data_visualizer/public/application/common/components/expanded_row/geo_point_content_with_map/geo_point_content_with_map.tsx +++ b/x-pack/plugins/data_visualizer/public/application/common/components/expanded_row/geo_point_content_with_map/geo_point_content_with_map.tsx @@ -5,13 +5,13 @@ * 2.0. */ import React, { FC, useEffect, useState } from 'react'; -import { DataView } from '@kbn/data-views-plugin/public'; +import type { DataView } from '@kbn/data-views-plugin/public'; import { ES_GEO_FIELD_TYPE, LayerDescriptor } from '@kbn/maps-plugin/common'; -import { CombinedQuery } from '../../../../index_data_visualizer/types/combined_query'; +import type { CombinedQuery } from '../../../../index_data_visualizer/types/combined_query'; import { ExpandedRowContent } from '../../stats_table/components/field_data_expanded_row/expanded_row_content'; import { DocumentStatsTable } from '../../stats_table/components/field_data_expanded_row/document_stats'; import { ExamplesList } from '../../examples_list'; -import { FieldVisConfig } from '../../stats_table/types'; +import type { FieldVisConfig } from '../../stats_table/types'; import { useDataVisualizerKibana } from '../../../../kibana_context'; import { SUPPORTED_FIELD_TYPES } from '../../../../../../common/constants'; import { EmbeddedMapComponent } from '../../embedded_map'; @@ -20,8 +20,9 @@ import { ExpandedRowPanel } from '../../stats_table/components/field_data_expand export const GeoPointContentWithMap: FC<{ config: FieldVisConfig; dataView: DataView | undefined; - combinedQuery: CombinedQuery; -}> = ({ config, dataView, combinedQuery }) => { + combinedQuery?: CombinedQuery; + esql?: string; +}> = ({ config, dataView, combinedQuery, esql }) => { const { stats } = config; const [layerList, setLayerList] = useState([]); const { @@ -43,22 +44,60 @@ export const GeoPointContentWithMap: FC<{ geoFieldName: config.fieldName, geoFieldType: config.type as ES_GEO_FIELD_TYPE, filters: data.query.filterManager.getFilters() ?? [], - query: { - query: combinedQuery.searchString, - language: combinedQuery.searchQueryLanguage, - }, + + ...(typeof esql === 'string' ? { esql, type: 'ESQL' } : {}), + ...(combinedQuery + ? { + query: { + query: combinedQuery.searchString, + language: combinedQuery.searchQueryLanguage, + }, + } + : {}), }; const searchLayerDescriptor = mapsPlugin ? await mapsPlugin.createLayerDescriptors.createESSearchSourceLayerDescriptor(params) : null; - if (searchLayerDescriptor) { - setLayerList([...layerList, searchLayerDescriptor]); + + if (searchLayerDescriptor?.sourceDescriptor) { + if (esql !== undefined) { + // Currently, createESSearchSourceLayerDescriptor doesn't support ES|QL yet + // but we can manually override the source descriptor with the ES|QL ESQLSourceDescriptor + const esqlSourceDescriptor = { + columns: [ + { + name: config.fieldName, + type: config.type, + }, + ], + dataViewId: dataView.id, + dateField: dataView.timeFieldName, + geoField: config.fieldName, + esql, + narrowByGlobalSearch: true, + narrowByGlobalTime: true, + narrowByMapBounds: true, + id: searchLayerDescriptor.sourceDescriptor.id, + type: 'ESQL', + applyForceRefresh: true, + }; + + setLayerList([ + ...layerList, + { + ...searchLayerDescriptor, + sourceDescriptor: esqlSourceDescriptor, + }, + ]); + } else { + setLayerList([...layerList, searchLayerDescriptor]); + } } } } updateIndexPatternSearchLayer(); // eslint-disable-next-line react-hooks/exhaustive-deps - }, [dataView, combinedQuery, config, mapsPlugin, data.query]); + }, [dataView, combinedQuery, esql, config, mapsPlugin, data.query]); if (stats?.examples === undefined) return null; return ( diff --git a/x-pack/plugins/data_visualizer/public/application/common/components/expanded_row/index_based_expanded_row.tsx b/x-pack/plugins/data_visualizer/public/application/common/components/expanded_row/index_based_expanded_row.tsx index f921d66e2cd1e..da8db22fb93df 100644 --- a/x-pack/plugins/data_visualizer/public/application/common/components/expanded_row/index_based_expanded_row.tsx +++ b/x-pack/plugins/data_visualizer/public/application/common/components/expanded_row/index_based_expanded_row.tsx @@ -6,7 +6,7 @@ */ import React from 'react'; -import { DataView, DataViewField } from '@kbn/data-views-plugin/public'; +import type { DataView, DataViewField } from '@kbn/data-views-plugin/public'; import { useExpandedRowCss } from './use_expanded_row_css'; import { GeoPointContentWithMap } from './geo_point_content_with_map'; import { SUPPORTED_FIELD_TYPES } from '../../../../../common/constants'; @@ -20,8 +20,8 @@ import { TextContent, } from '../stats_table/components/field_data_expanded_row'; import { NotInDocsContent } from '../not_in_docs_content'; -import { FieldVisConfig } from '../stats_table/types'; -import { CombinedQuery } from '../../../index_data_visualizer/types/combined_query'; +import type { FieldVisConfig } from '../stats_table/types'; +import type { CombinedQuery } from '../../../index_data_visualizer/types/combined_query'; import { LoadingIndicator } from '../loading_indicator'; import { ErrorMessageContent } from '../stats_table/components/field_data_expanded_row/error_message'; @@ -30,12 +30,14 @@ export const IndexBasedDataVisualizerExpandedRow = ({ dataView, combinedQuery, onAddFilter, + esql, totalDocuments, typeAccessor = 'type', }: { item: FieldVisConfig; dataView: DataView | undefined; - combinedQuery: CombinedQuery; + combinedQuery?: CombinedQuery; + esql?: string; totalDocuments?: number; typeAccessor?: 'type' | 'secondaryType'; /** @@ -74,6 +76,7 @@ export const IndexBasedDataVisualizerExpandedRow = ({ config={config} dataView={dataView} combinedQuery={combinedQuery} + esql={esql} /> ); diff --git a/x-pack/plugins/data_visualizer/public/application/common/components/field_data_row/action_menu/actions.ts b/x-pack/plugins/data_visualizer/public/application/common/components/field_data_row/action_menu/actions.ts index 09a3e1b8ffb45..ef1bb63246ac8 100644 --- a/x-pack/plugins/data_visualizer/public/application/common/components/field_data_row/action_menu/actions.ts +++ b/x-pack/plugins/data_visualizer/public/application/common/components/field_data_row/action_menu/actions.ts @@ -7,14 +7,14 @@ import { i18n } from '@kbn/i18n'; import { Action } from '@elastic/eui/src/components/basic_table/action_types'; -import { MutableRefObject } from 'react'; -import { DataView } from '@kbn/data-views-plugin/public'; +import type { MutableRefObject } from 'react'; +import type { DataView } from '@kbn/data-views-plugin/public'; import { VISUALIZE_GEO_FIELD_TRIGGER } from '@kbn/ui-actions-plugin/public'; import { mlTimefilterRefresh$, Refresh } from '@kbn/ml-date-picker'; import { getCompatibleLensDataType, getLensAttributes } from './lens_utils'; -import { CombinedQuery } from '../../../../index_data_visualizer/types/combined_query'; -import { FieldVisConfig } from '../../stats_table/types'; -import { DataVisualizerKibanaReactContextValue } from '../../../../kibana_context'; +import type { CombinedQuery } from '../../../../index_data_visualizer/types/combined_query'; +import type { FieldVisConfig } from '../../stats_table/types'; +import type { DataVisualizerKibanaReactContextValue } from '../../../../kibana_context'; import { SUPPORTED_FIELD_TYPES } from '../../../../../../common/constants'; import { APP_ID } from '../../../../../../common/constants'; diff --git a/x-pack/plugins/data_visualizer/public/application/common/components/stats_table/components/field_data_row/document_stats.tsx b/x-pack/plugins/data_visualizer/public/application/common/components/stats_table/components/field_data_row/document_stats.tsx index 2a7ca2fedc0bd..4d2a61435b025 100644 --- a/x-pack/plugins/data_visualizer/public/application/common/components/stats_table/components/field_data_row/document_stats.tsx +++ b/x-pack/plugins/data_visualizer/public/application/common/components/stats_table/components/field_data_row/document_stats.tsx @@ -32,7 +32,7 @@ export const DocumentStat = ({ config, showIcon, totalCount }: Props) => { const { count, sampleCount } = stats; - const total = sampleCount ?? totalCount; + const total = Math.min(sampleCount ?? Infinity, totalCount ?? Infinity); // If field exists is docs but we don't have count stats then don't show // Otherwise if field doesn't appear in docs at all, show 0% diff --git a/x-pack/plugins/data_visualizer/public/application/common/components/stats_table/components/field_data_row/number_content_preview.tsx b/x-pack/plugins/data_visualizer/public/application/common/components/stats_table/components/field_data_row/number_content_preview.tsx index 54633d4c6c74c..4becf188f1639 100644 --- a/x-pack/plugins/data_visualizer/public/application/common/components/stats_table/components/field_data_row/number_content_preview.tsx +++ b/x-pack/plugins/data_visualizer/public/application/common/components/stats_table/components/field_data_row/number_content_preview.tsx @@ -8,7 +8,7 @@ import React, { FC, useMemo } from 'react'; import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; import { MetricDistributionChart, buildChartDataFromStats } from '../metric_distribution_chart'; -import { FieldVisConfig } from '../../types'; +import type { FieldVisConfig } from '../../types'; import { kibanaFieldFormat, formatSingleValue } from '../../../utils'; const METRIC_DISTRIBUTION_CHART_WIDTH = 100; diff --git a/x-pack/plugins/data_visualizer/public/application/common/components/top_values/top_values.tsx b/x-pack/plugins/data_visualizer/public/application/common/components/top_values/top_values.tsx index 9ea517d45eea1..a9a49f5038b1b 100644 --- a/x-pack/plugins/data_visualizer/public/application/common/components/top_values/top_values.tsx +++ b/x-pack/plugins/data_visualizer/public/application/common/components/top_values/top_values.tsx @@ -126,7 +126,7 @@ export const TopValues: FC = ({ stats, fieldFormat, barColor, compressed, max={1} color={barColor} size="xs" - label={kibanaFieldFormat(value.key, fieldFormat)} + label={value.key ? kibanaFieldFormat(value.key, fieldFormat) : fieldValue} className={classNames('eui-textTruncate', 'topValuesValueLabelContainer')} valueText={`${value.doc_count}${ totalDocuments !== undefined diff --git a/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/components/index_data_visualizer_view/index_data_visualizer_esql.tsx b/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/components/index_data_visualizer_view/index_data_visualizer_esql.tsx index 2f91dce01b456..7af011586d77c 100644 --- a/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/components/index_data_visualizer_view/index_data_visualizer_esql.tsx +++ b/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/components/index_data_visualizer_view/index_data_visualizer_esql.tsx @@ -6,20 +6,13 @@ */ /* eslint-disable react-hooks/exhaustive-deps */ - import { css } from '@emotion/react'; -import React, { FC, useEffect, useMemo, useState, useCallback, useRef } from 'react'; -import type { Required } from 'utility-types'; -import { - FullTimeRangeSelector, - mlTimefilterRefresh$, - useTimefilter, - DatePickerWrapper, -} from '@kbn/ml-date-picker'; +import React, { FC, useCallback, useEffect, useMemo, useState } from 'react'; +import { usePageUrlState } from '@kbn/ml-url-state'; + +import { FullTimeRangeSelector, DatePickerWrapper } from '@kbn/ml-date-picker'; import { TextBasedLangEditor } from '@kbn/text-based-languages/public'; import type { AggregateQuery } from '@kbn/es-query'; -import { merge } from 'rxjs'; -import { Comparators } from '@elastic/eui'; import { useEuiBreakpoint, @@ -31,112 +24,28 @@ import { EuiProgress, EuiSpacer, } from '@elastic/eui'; -import { usePageUrlState, useUrlState } from '@kbn/ml-url-state'; -import { SEARCH_QUERY_LANGUAGE } from '@kbn/ml-query-utils'; -import { getIndexPatternFromSQLQuery, getIndexPatternFromESQLQuery } from '@kbn/esql-utils'; import type { DataView } from '@kbn/data-views-plugin/common'; -import { KBN_FIELD_TYPES } from '@kbn/field-types'; -import type { QueryDslQueryContainer } from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; -import { getFieldType } from '@kbn/field-utils'; -import { UI_SETTINGS } from '@kbn/data-service'; -import type { SupportedFieldType } from '../../../../../common/types'; +import { getIndexPatternFromESQLQuery } from '@kbn/esql-utils'; +import { getOrCreateDataViewByIndexPattern } from '../../search_strategy/requests/get_data_view_by_index_pattern'; import { useCurrentEuiTheme } from '../../../common/hooks/use_current_eui_theme'; import type { FieldVisConfig } from '../../../common/components/stats_table/types'; import { DATA_VISUALIZER_INDEX_VIEWER } from '../../constants/index_data_visualizer_viewer'; -import type { DataVisualizerIndexBasedAppState } from '../../types/index_data_visualizer_state'; import { useDataVisualizerKibana } from '../../../kibana_context'; import { GetAdditionalLinks } from '../../../common/components/results_links'; import { DocumentCountContent } from '../../../common/components/document_count_content'; -import { useTimeBuckets } from '../../../common/hooks/use_time_buckets'; -import { - DataVisualizerTable, - ItemIdToExpandedRowMap, -} from '../../../common/components/stats_table'; -import type { - MetricFieldsStats, - TotalFieldsStats, -} from '../../../common/components/stats_table/components/field_count_stats'; -import { filterFields } from '../../../common/components/fields_stats_grid/filter_fields'; -import { IndexBasedDataVisualizerExpandedRow } from '../../../common/components/expanded_row/index_based_expanded_row'; -import { getOrCreateDataViewByIndexPattern } from '../../search_strategy/requests/get_data_view_by_index_pattern'; +import { DataVisualizerTable } from '../../../common/components/stats_table'; import { FieldCountPanel } from '../../../common/components/field_count_panel'; -import { useESQLFieldStatsData } from '../../hooks/esql/use_esql_field_stats_data'; -import type { NonAggregatableField, OverallStats } from '../../types/overall_stats'; -import { isESQLQuery } from '../../search_strategy/requests/esql_utils'; -import { DEFAULT_BAR_TARGET } from '../../../common/constants'; +import { ESQLDefaultLimitSizeSelect } from '../search_panel/esql/limit_size'; import { - type ESQLDefaultLimitSizeOption, - ESQLDefaultLimitSizeSelect, -} from '../search_panel/esql/limit_size'; -import { type Column, useESQLOverallStatsData } from '../../hooks/esql/use_esql_overall_stats_data'; -import { type AggregatableField } from '../../types/esql_data_visualizer'; - -const defaults = getDefaultPageState(); - -interface DataVisualizerPageState { - overallStats: OverallStats; - metricConfigs: FieldVisConfig[]; - totalMetricFieldCount: number; - populatedMetricFieldCount: number; - metricsLoaded: boolean; - nonMetricConfigs: FieldVisConfig[]; - nonMetricsLoaded: boolean; - documentCountStats?: FieldVisConfig; -} - -const defaultSearchQuery = { - match_all: {}, -}; - -export function getDefaultPageState(): DataVisualizerPageState { - return { - overallStats: { - totalCount: 0, - aggregatableExistsFields: [], - aggregatableNotExistsFields: [], - nonAggregatableExistsFields: [], - nonAggregatableNotExistsFields: [], - }, - metricConfigs: [], - totalMetricFieldCount: 0, - populatedMetricFieldCount: 0, - metricsLoaded: false, - nonMetricConfigs: [], - nonMetricsLoaded: false, - documentCountStats: undefined, - }; -} - -interface ESQLDataVisualizerIndexBasedAppState extends DataVisualizerIndexBasedAppState { - limitSize: ESQLDefaultLimitSizeOption; -} - -export interface ESQLDataVisualizerIndexBasedPageUrlState { - pageKey: typeof DATA_VISUALIZER_INDEX_VIEWER; - pageUrlState: Required; -} - -export const getDefaultDataVisualizerListState = ( - overrides?: Partial -): Required => ({ - pageIndex: 0, - pageSize: 25, - sortField: 'fieldName', - sortDirection: 'asc', - visibleFieldTypes: [], - visibleFieldNames: [], - limitSize: '10000', - searchString: '', - searchQuery: defaultSearchQuery, - searchQueryLanguage: SEARCH_QUERY_LANGUAGE.KUERY, - filters: [], - showDistributions: true, - showAllFields: false, - showEmptyFields: false, - probability: null, - rndSamplerPref: 'off', - ...overrides, -}); + getDefaultESQLDataVisualizerListState, + useESQLDataVisualizerData, +} from '../../hooks/esql/use_data_visualizer_esql_data'; +import type { + DataVisualizerGridInput, + ESQLDataVisualizerIndexBasedPageUrlState, + ESQLDefaultLimitSizeOption, +} from '../../embeddables/grid_embeddable/types'; +import { ESQLQuery, isESQLQuery } from '../../search_strategy/requests/esql_utils'; export interface IndexDataVisualizerESQLProps { getAdditionalLinks?: GetAdditionalLinks; @@ -144,31 +53,49 @@ export interface IndexDataVisualizerESQLProps { export const IndexDataVisualizerESQL: FC = (dataVisualizerProps) => { const { services } = useDataVisualizerKibana(); - const { data, fieldFormats, uiSettings } = services; + const { data } = services; const euiTheme = useCurrentEuiTheme(); - const [query, setQuery] = useState({ esql: '' }); + const [query, setQuery] = useState({ esql: '' }); const [currentDataView, setCurrentDataView] = useState(); + const toggleShowEmptyFields = () => { + setDataVisualizerListState({ + ...dataVisualizerListState, + showEmptyFields: !dataVisualizerListState.showEmptyFields, + }); + }; + const updateLimitSize = (newLimitSize: ESQLDefaultLimitSizeOption) => { + setDataVisualizerListState({ + ...dataVisualizerListState, + limitSize: newLimitSize, + }); + }; + + const restorableDefaults = useMemo( + () => getDefaultESQLDataVisualizerListState({}), + // We just need to load the saved preference when the page is first loaded + + [] + ); + + const [dataVisualizerListState, setDataVisualizerListState] = + usePageUrlState( + DATA_VISUALIZER_INDEX_VIEWER, + restorableDefaults + ); const updateDataView = (dv: DataView) => { if (dv.id !== currentDataView?.id) { setCurrentDataView(dv); } }; - const [lastRefresh, setLastRefresh] = useState(0); - const _timeBuckets = useTimeBuckets(); - const timefilter = useTimefilter({ - timeRangeSelector: true, - autoRefreshSelector: true, - }); + // Query that has been typed, but has not submitted with cmd + enter + const [localQuery, setLocalQuery] = useState({ esql: '' }); const indexPattern = useMemo(() => { let indexPatternFromQuery = ''; - if ('sql' in query) { - indexPatternFromQuery = getIndexPatternFromSQLQuery(query.sql); - } - if ('esql' in query) { + if (isESQLQuery(query)) { indexPatternFromQuery = getIndexPatternFromESQLQuery(query.esql); } // we should find a better way to work with ESQL queries which dont need a dataview @@ -178,38 +105,6 @@ export const IndexDataVisualizerESQL: FC = (dataVi return indexPatternFromQuery; }, [query]); - const restorableDefaults = useMemo( - () => getDefaultDataVisualizerListState({}), - // We just need to load the saved preference when the page is first loaded - - [] - ); - - const [dataVisualizerListState, setDataVisualizerListState] = - usePageUrlState( - DATA_VISUALIZER_INDEX_VIEWER, - restorableDefaults - ); - const [globalState, setGlobalState] = useUrlState('_g'); - - const showEmptyFields = - dataVisualizerListState.showEmptyFields ?? restorableDefaults.showEmptyFields; - const toggleShowEmptyFields = () => { - setDataVisualizerListState({ - ...dataVisualizerListState, - showEmptyFields: !dataVisualizerListState.showEmptyFields, - }); - }; - - const limitSize = dataVisualizerListState.limitSize ?? restorableDefaults.limitSize; - - const updateLimitSize = (newLimitSize: ESQLDefaultLimitSizeOption) => { - setDataVisualizerListState({ - ...dataVisualizerListState, - limitSize: newLimitSize, - }); - }; - useEffect( function updateAdhocDataViewFromQuery() { let unmounted = false; @@ -239,471 +134,67 @@ export const IndexDataVisualizerESQL: FC = (dataVi [indexPattern, data.dataViews, currentDataView] ); - /** Search strategy **/ - const fieldStatsRequest = useMemo(() => { - // Obtain the interval to use for date histogram aggregations - // (such as the document count chart). Aim for 75 bars. - const buckets = _timeBuckets; - - const tf = timefilter; - - if (!buckets || !tf || (isESQLQuery(query) && query.esql === '')) return; - const activeBounds = tf.getActiveBounds(); - - let earliest: number | undefined; - let latest: number | undefined; - if (activeBounds !== undefined && currentDataView?.timeFieldName !== undefined) { - earliest = activeBounds.min?.valueOf(); - latest = activeBounds.max?.valueOf(); - } - - const bounds = tf.getActiveBounds(); - const barTarget = uiSettings.get(UI_SETTINGS.HISTOGRAM_BAR_TARGET) ?? DEFAULT_BAR_TARGET; - buckets.setInterval('auto'); - - if (bounds) { - buckets.setBounds(bounds); - buckets.setBarTarget(barTarget); - } - - const aggInterval = buckets.getInterval(); - - const filter = currentDataView?.timeFieldName - ? ({ - bool: { - must: [], - filter: [ - { - range: { - [currentDataView.timeFieldName]: { - format: 'strict_date_optional_time', - gte: timefilter.getTime().from, - lte: timefilter.getTime().to, - }, - }, - }, - ], - should: [], - must_not: [], - }, - } as QueryDslQueryContainer) - : undefined; + const input: DataVisualizerGridInput = useMemo(() => { return { - earliest, - latest, - aggInterval, - intervalMs: aggInterval?.asMilliseconds(), - searchQuery: query, - limitSize, + dataView: currentDataView, + query, + savedSearch: undefined, sessionId: undefined, + visibleFieldNames: undefined, + allowEditDataView: true, + id: 'esql_data_visualizer', indexPattern, - timeFieldName: currentDataView?.timeFieldName, - runtimeFieldMap: currentDataView?.getRuntimeMappings(), - lastRefresh, - filter, }; - }, [ - _timeBuckets, - timefilter, - currentDataView?.id, - JSON.stringify(query), - indexPattern, - lastRefresh, - limitSize, - ]); - - useEffect(() => { - // Force refresh on index pattern change - setLastRefresh(Date.now()); - }, [setLastRefresh]); + }, [currentDataView, query?.esql]); - useEffect(() => { - if (globalState?.time !== undefined) { - timefilter.setTime({ - from: globalState.time.from, - to: globalState.time.to, - }); - } - }, [JSON.stringify(globalState?.time), timefilter]); - - useEffect(() => { - const timeUpdateSubscription = merge( - timefilter.getTimeUpdate$(), - timefilter.getAutoRefreshFetch$(), - mlTimefilterRefresh$ - ).subscribe(() => { - setGlobalState({ - time: timefilter.getTime(), - refreshInterval: timefilter.getRefreshInterval(), - }); - setLastRefresh(Date.now()); - }); - return () => { - timeUpdateSubscription.unsubscribe(); - }; - }, []); + const dvPageHeader = css({ + [useEuiBreakpoint(['xs', 's', 'm', 'l'])]: { + flexDirection: 'column', + alignItems: 'flex-start', + }, + }); - useEffect(() => { - if (globalState?.refreshInterval !== undefined) { - timefilter.setRefreshInterval(globalState.refreshInterval); - } - }, [JSON.stringify(globalState?.refreshInterval), timefilter]); + const isWithinLargeBreakpoint = useIsWithinMaxBreakpoint('l'); const { - documentCountStats, totalCount, - overallStats, + progress: combinedProgress, overallStatsProgress, - columns, - cancelOverallStatsRequest, - } = useESQLOverallStatsData(fieldStatsRequest); - - const [metricConfigs, setMetricConfigs] = useState(defaults.metricConfigs); - const [metricsLoaded] = useState(defaults.metricsLoaded); - const [metricsStats, setMetricsStats] = useState(); - - const [nonMetricConfigs, setNonMetricConfigs] = useState(defaults.nonMetricConfigs); - const [nonMetricsLoaded] = useState(defaults.nonMetricsLoaded); - - const [fieldStatFieldsToFetch, setFieldStatFieldsToFetch] = useState(); - - const visibleFieldTypes = - dataVisualizerListState.visibleFieldTypes ?? restorableDefaults.visibleFieldTypes; - - const visibleFieldNames = - dataVisualizerListState.visibleFieldNames ?? restorableDefaults.visibleFieldNames; - - useEffect( - function updateFieldStatFieldsToFetch() { - const { sortField, sortDirection } = dataVisualizerListState; - - // Otherwise, sort the list of fields by the initial sort field and sort direction - // Then divide into chunks by the initial page size - - const itemsSorter = Comparators.property( - sortField as string, - Comparators.default(sortDirection as 'asc' | 'desc' | undefined) - ); - - const preslicedSortedConfigs = [...nonMetricConfigs, ...metricConfigs] - .map((c) => ({ - ...c, - name: c.fieldName, - docCount: c.stats?.count, - cardinality: c.stats?.cardinality, - })) - .sort(itemsSorter); - - const filteredItems = filterFields( - preslicedSortedConfigs, - dataVisualizerListState.visibleFieldNames, - dataVisualizerListState.visibleFieldTypes - ); - - const { pageIndex, pageSize } = dataVisualizerListState; - - const pageOfConfigs = filteredItems.filteredFields - ?.slice(pageIndex * pageSize, (pageIndex + 1) * pageSize) - .filter((d) => d.existsInDocs === true); - - setFieldStatFieldsToFetch(pageOfConfigs); - }, - [ - dataVisualizerListState.pageIndex, - dataVisualizerListState.pageSize, - dataVisualizerListState.sortField, - dataVisualizerListState.sortDirection, - nonMetricConfigs, - metricConfigs, - ] - ); - - const { fieldStats, fieldStatsProgress, cancelFieldStatsRequest } = useESQLFieldStatsData({ - searchQuery: fieldStatsRequest?.searchQuery, - columns: fieldStatFieldsToFetch, - filter: fieldStatsRequest?.filter, - limitSize: fieldStatsRequest?.limitSize, - }); - - const createMetricCards = useCallback(() => { - if (!columns || !overallStats) return; - const configs: FieldVisConfig[] = []; - const aggregatableExistsFields: AggregatableField[] = - overallStats.aggregatableExistsFields || []; - - const allMetricFields = columns.filter((f) => { - return f.secondaryType === KBN_FIELD_TYPES.NUMBER; - }); - - const metricExistsFields = allMetricFields.filter((f) => { - return aggregatableExistsFields.find((existsF) => { - return existsF.fieldName === f.name; - }); - }); - - let _aggregatableFields: AggregatableField[] = overallStats.aggregatableExistsFields; - if (allMetricFields.length !== metricExistsFields.length && metricsLoaded === true) { - _aggregatableFields = _aggregatableFields.concat(overallStats.aggregatableNotExistsFields); - } - - const metricFieldsToShow = - metricsLoaded === true && showEmptyFields === true ? allMetricFields : metricExistsFields; - - metricFieldsToShow.forEach((field) => { - const fieldData = _aggregatableFields.find((f) => { - return f.fieldName === field.name; - }); - if (!fieldData) return; - - const metricConfig: FieldVisConfig = { - ...field, - ...fieldData, - loading: fieldData?.existsInDocs ?? true, - fieldFormat: - currentDataView?.getFormatterForFieldNoDefault(field.name) ?? - fieldFormats.deserialize({ id: field.secondaryType }), - aggregatable: true, - deletable: false, - type: getFieldType(field) as SupportedFieldType, - }; - - configs.push(metricConfig); - }); - - setMetricsStats({ - totalMetricFieldsCount: allMetricFields.length, - visibleMetricsCount: metricFieldsToShow.length, - }); - setMetricConfigs(configs); - }, [metricsLoaded, overallStats, showEmptyFields, columns, currentDataView?.id]); - - const createNonMetricCards = useCallback(() => { - if (!columns || !overallStats) return; - - const allNonMetricFields = columns.filter((f) => { - return f.secondaryType !== KBN_FIELD_TYPES.NUMBER; - }); - // Obtain the list of all non-metric fields which appear in documents - // (aggregatable or not aggregatable). - const populatedNonMetricFields: Column[] = []; // Kibana index pattern non metric fields. - let nonMetricFieldData: Array = []; // Basic non metric field data loaded from requesting overall stats. - const aggregatableExistsFields: AggregatableField[] = - overallStats.aggregatableExistsFields || []; - const nonAggregatableExistsFields: NonAggregatableField[] = - overallStats.nonAggregatableExistsFields || []; - - allNonMetricFields.forEach((f) => { - const checkAggregatableField = aggregatableExistsFields.find( - (existsField) => existsField.fieldName === f.name - ); - - if (checkAggregatableField !== undefined) { - populatedNonMetricFields.push(f); - nonMetricFieldData.push(checkAggregatableField); - } else { - const checkNonAggregatableField = nonAggregatableExistsFields.find( - (existsField) => existsField.fieldName === f.name - ); - - if (checkNonAggregatableField !== undefined) { - populatedNonMetricFields.push(f); - nonMetricFieldData.push(checkNonAggregatableField); - } - } - }); - - if (allNonMetricFields.length !== nonMetricFieldData.length && showEmptyFields === true) { - // Combine the field data obtained from Elasticsearch into a single array. - nonMetricFieldData = nonMetricFieldData.concat( - overallStats.aggregatableNotExistsFields, - overallStats.nonAggregatableNotExistsFields - ); - } - - const nonMetricFieldsToShow = showEmptyFields ? allNonMetricFields : populatedNonMetricFields; - - const configs: FieldVisConfig[] = []; - - nonMetricFieldsToShow.forEach((field) => { - const fieldData = nonMetricFieldData.find((f) => f.fieldName === field.name); - const nonMetricConfig: Partial = { - ...(fieldData ? fieldData : {}), - secondaryType: getFieldType(field) as SupportedFieldType, - loading: fieldData?.existsInDocs ?? true, - deletable: false, - fieldFormat: - currentDataView?.getFormatterForFieldNoDefault(field.name) ?? - fieldFormats.deserialize({ id: field.secondaryType }), - }; - - // Map the field type from the Kibana index pattern to the field type - // used in the data visualizer. - const dataVisualizerType = getFieldType(field) as SupportedFieldType; - if (dataVisualizerType !== undefined) { - nonMetricConfig.type = dataVisualizerType; - } else { - // Add a flag to indicate that this is one of the 'other' Kibana - // field types that do not yet have a specific card type. - nonMetricConfig.type = field.type as SupportedFieldType; - nonMetricConfig.isUnsupportedType = true; - } - - if (field.name !== nonMetricConfig.fieldName) { - nonMetricConfig.displayName = field.name; - } - - configs.push(nonMetricConfig as FieldVisConfig); - }); - - setNonMetricConfigs(configs); - }, [columns, nonMetricsLoaded, overallStats, showEmptyFields, currentDataView?.id]); - - const fieldsCountStats: TotalFieldsStats | undefined = useMemo(() => { - if (!overallStats) return; - - let _visibleFieldsCount = 0; - let _totalFieldsCount = 0; - Object.keys(overallStats).forEach((key) => { - const fieldsGroup = overallStats[key as keyof typeof overallStats]; - if (Array.isArray(fieldsGroup) && fieldsGroup.length > 0) { - _totalFieldsCount += fieldsGroup.length; - } - }); - - if (showEmptyFields === true) { - _visibleFieldsCount = _totalFieldsCount; - } else { - _visibleFieldsCount = - overallStats.aggregatableExistsFields.length + - overallStats.nonAggregatableExistsFields.length; - } - return { visibleFieldsCount: _visibleFieldsCount, totalFieldsCount: _totalFieldsCount }; - }, [overallStats, showEmptyFields]); - - useEffect(() => { - createMetricCards(); - createNonMetricCards(); - }, [overallStats, showEmptyFields]); - - const configs = useMemo(() => { - let combinedConfigs = [...nonMetricConfigs, ...metricConfigs]; - - combinedConfigs = filterFields( - combinedConfigs, - visibleFieldNames, - visibleFieldTypes - ).filteredFields; - - if (fieldStatsProgress.loaded === 100 && fieldStats) { - combinedConfigs = combinedConfigs.map((c) => { - const loadedFullStats = fieldStats.get(c.fieldName) ?? {}; - return loadedFullStats - ? { - ...c, - loading: false, - stats: { ...c.stats, ...loadedFullStats }, - } - : c; - }); - } - return combinedConfigs; - }, [ - nonMetricConfigs, - metricConfigs, - visibleFieldTypes, - visibleFieldNames, - fieldStatsProgress.loaded, - dataVisualizerListState.pageIndex, - dataVisualizerListState.pageSize, - ]); - - // Some actions open up fly-out or popup - // This variable is used to keep track of them and clean up when unmounting - const actionFlyoutRef = useRef<() => void | undefined>(); - useEffect(() => { - const ref = actionFlyoutRef; - return () => { - // Clean up any of the flyout/editor opened from the actions - if (ref.current) { - ref.current(); - } - }; - }, []); - - const getItemIdToExpandedRowMap = useCallback( - function (itemIds: string[], items: FieldVisConfig[]): ItemIdToExpandedRowMap { - return itemIds.reduce((m: ItemIdToExpandedRowMap, fieldName: string) => { - const item = items.find((fieldVisConfig) => fieldVisConfig.fieldName === fieldName); - if (item !== undefined) { - m[fieldName] = ( - - ); - } - return m; - }, {} as ItemIdToExpandedRowMap); - }, - [currentDataView, totalCount] - ); + configs, + documentCountStats, + metricsStats, + timefilter, + getItemIdToExpandedRowMap, + onQueryUpdate, + limitSize, + showEmptyFields, + fieldsCountStats, + } = useESQLDataVisualizerData(input, dataVisualizerListState, setQuery); const hasValidTimeField = useMemo( - () => - currentDataView && - currentDataView.timeFieldName !== undefined && - currentDataView.timeFieldName !== '', - [currentDataView] + () => currentDataView?.timeFieldName !== undefined, + [currentDataView?.timeFieldName] ); - const isWithinLargeBreakpoint = useIsWithinMaxBreakpoint('l'); - const dvPageHeader = css({ - [useEuiBreakpoint(['xs', 's', 'm', 'l'])]: { - flexDirection: 'column', - alignItems: 'flex-start', - }, - }); - - const combinedProgress = useMemo( - () => overallStatsProgress.loaded * 0.3 + fieldStatsProgress.loaded * 0.7, - [overallStatsProgress.loaded, fieldStatsProgress.loaded] + const queryNeedsUpdate = useMemo( + () => (localQuery.esql !== query.esql ? true : undefined), + [localQuery.esql, query.esql] ); - // Query that has been typed, but has not submitted with cmd + enter - const [localQuery, setLocalQuery] = useState({ esql: '' }); - - const onQueryUpdate = async (q?: AggregateQuery) => { - // When user submits a new query - // resets all current requests and other data - if (cancelOverallStatsRequest) { - cancelOverallStatsRequest(); - } - if (cancelFieldStatsRequest) { - cancelFieldStatsRequest(); - } - // Reset field stats to fetch state - setFieldStatFieldsToFetch(undefined); - setMetricConfigs(defaults.metricConfigs); - setNonMetricConfigs(defaults.nonMetricConfigs); - if (q) { - setQuery(q); + const handleRefresh = useCallback(() => { + // The page is already autoamtically updating when time range is changed + // via the url state + // so we just need to force update if the query is outdated + if (queryNeedsUpdate) { + setQuery(localQuery); } - }; - - useEffect( - function resetFieldStatsFieldToFetch() { - // If query returns 0 document, no need to do more work here - if (totalCount === undefined || totalCount === 0) { - setFieldStatFieldsToFetch(undefined); - return; - } - }, - [totalCount] - ); + }, [queryNeedsUpdate, localQuery.esql]); + const onTextLangQueryChange = useCallback((q: AggregateQuery) => { + if (isESQLQuery(q)) { + setLocalQuery(q); + } + }, []); return ( = (dataVi ) : null} @@ -754,7 +247,7 @@ export const IndexDataVisualizerESQL: FC = (dataVi false} isCodeEditorExpanded={true} @@ -777,9 +270,9 @@ export const IndexDataVisualizerESQL: FC = (dataVi showSettings={false} /> + )} - = (dataVi onChangeLimitSize={updateLimitSize} /> - + + items={configs} diff --git a/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/components/index_data_visualizer_view/index_data_visualizer_view.tsx b/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/components/index_data_visualizer_view/index_data_visualizer_view.tsx index 5d8ebe9e44d57..a00a126d517f6 100644 --- a/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/components/index_data_visualizer_view/index_data_visualizer_view.tsx +++ b/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/components/index_data_visualizer_view/index_data_visualizer_view.tsx @@ -48,9 +48,9 @@ import { DataVisualizerTable, ItemIdToExpandedRowMap, } from '../../../common/components/stats_table'; -import { FieldVisConfig } from '../../../common/components/stats_table/types'; +import type { FieldVisConfig } from '../../../common/components/stats_table/types'; import type { TotalFieldsStats } from '../../../common/components/stats_table/components/field_count_stats'; -import { OverallStats } from '../../types/overall_stats'; +import type { OverallStats } from '../../types/overall_stats'; import { IndexBasedDataVisualizerExpandedRow } from '../../../common/components/expanded_row/index_based_expanded_row'; import { DATA_VISUALIZER_INDEX_VIEWER } from '../../constants/index_data_visualizer_viewer'; import { @@ -66,46 +66,17 @@ import { ActionsPanel } from '../actions_panel'; import { DataVisualizerDataViewManagement } from '../data_view_management'; import type { GetAdditionalLinks } from '../../../common/components/results_links'; import { useDataVisualizerGridData } from '../../hooks/use_data_visualizer_grid_data'; -import type { DataVisualizerGridInput } from '../../embeddables/grid_embeddable/grid_embeddable'; import { MIN_SAMPLER_PROBABILITY, RANDOM_SAMPLER_OPTION, - RandomSamplerOption, + type RandomSamplerOption, } from '../../constants/random_sampler'; - -interface DataVisualizerPageState { - overallStats: OverallStats; - metricConfigs: FieldVisConfig[]; - totalMetricFieldCount: number; - populatedMetricFieldCount: number; - metricsLoaded: boolean; - nonMetricConfigs: FieldVisConfig[]; - nonMetricsLoaded: boolean; - documentCountStats?: FieldVisConfig; -} +import type { DataVisualizerGridInput } from '../../embeddables/grid_embeddable/types'; const defaultSearchQuery = { match_all: {}, }; -export function getDefaultPageState(): DataVisualizerPageState { - return { - overallStats: { - totalCount: 0, - aggregatableExistsFields: [], - aggregatableNotExistsFields: [], - nonAggregatableExistsFields: [], - nonAggregatableNotExistsFields: [], - }, - metricConfigs: [], - totalMetricFieldCount: 0, - populatedMetricFieldCount: 0, - metricsLoaded: false, - nonMetricConfigs: [], - nonMetricsLoaded: false, - documentCountStats: undefined, - }; -} export const getDefaultDataVisualizerListState = ( overrides?: Partial ): Required => ({ @@ -244,7 +215,7 @@ export const IndexDataVisualizerView: FC = (dataVi }); }; - const input: DataVisualizerGridInput = useMemo(() => { + const input: Required = useMemo(() => { return { dataView: currentDataView, savedSearch: currentSavedSearch, diff --git a/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/components/search_panel/esql/limit_size.tsx b/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/components/search_panel/esql/limit_size.tsx index bcdf3241f5ee3..2e84191521bdb 100644 --- a/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/components/search_panel/esql/limit_size.tsx +++ b/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/components/search_panel/esql/limit_size.tsx @@ -8,6 +8,7 @@ import React, { type ChangeEvent } from 'react'; import { i18n } from '@kbn/i18n'; import { EuiSelect, EuiText, useGeneratedHtmlId } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n-react'; +import type { ESQLDefaultLimitSizeOption } from '../../../embeddables/grid_embeddable/types'; const options = [ { @@ -46,8 +47,6 @@ const options = [ }, ]; -export type ESQLDefaultLimitSizeOption = '5000' | '10000' | '100000' | '1000000' | 'none'; - export const ESQLDefaultLimitSizeSelect = ({ limitSize, onChangeLimitSize, @@ -71,7 +70,7 @@ export const ESQLDefaultLimitSizeSelect = ({ defaultMessage: 'Limit size', })} prepend={ - + void; +}) => { + const [dataVisualizerListState, setDataVisualizerListState] = + useState>(restorableDefaults); + + const onTableChange = useCallback( + (update: DataVisualizerTableState) => { + setDataVisualizerListState({ ...dataVisualizerListState, ...update }); + if (onOutputChange) { + onOutputChange(update); + } + }, + [dataVisualizerListState, onOutputChange] + ); + + const { + configs, + extendedColumns, + progress, + overallStatsProgress, + setLastRefresh, + getItemIdToExpandedRowMap, + } = useESQLDataVisualizerData(input, dataVisualizerListState); + + useEffect(() => { + setLastRefresh(Date.now()); + }, [input?.lastReloadRequestTime, setLastRefresh]); + + if (progress === 100 && configs.length === 0) { + return ; + } + return ( + + items={configs} + pageState={dataVisualizerListState} + updatePageState={onTableChange} + getItemIdToExpandedRowMap={getItemIdToExpandedRowMap} + extendedColumns={extendedColumns} + showPreviewByDefault={input?.showPreviewByDefault} + onChange={onOutputChange} + loading={progress < 100} + overallStatsRunning={overallStatsProgress.isRunning} + /> + ); +}; diff --git a/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/embeddables/grid_embeddable/embeddable_field_stats_no_results.tsx b/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/embeddables/grid_embeddable/embeddable_field_stats_no_results.tsx new file mode 100644 index 0000000000000..8424b8ec5d8a0 --- /dev/null +++ b/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/embeddables/grid_embeddable/embeddable_field_stats_no_results.tsx @@ -0,0 +1,31 @@ +/* + * 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 { FormattedMessage } from '@kbn/i18n-react'; +import { css } from '@emotion/react'; +import { EuiIcon, EuiSpacer, EuiText } from '@elastic/eui'; + +export const EmbeddableNoResultsEmptyPrompt = () => ( +
+ + + + + +
+); diff --git a/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/embeddables/grid_embeddable/embeddable_field_stats_table.tsx b/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/embeddables/grid_embeddable/embeddable_field_stats_table.tsx new file mode 100644 index 0000000000000..e2202e44482c5 --- /dev/null +++ b/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/embeddables/grid_embeddable/embeddable_field_stats_table.tsx @@ -0,0 +1,96 @@ +/* + * 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, useEffect, useState } from 'react'; +import type { Required } from 'utility-types'; +import type { DataVisualizerGridEmbeddableInput } from './types'; +import { + DataVisualizerTable, + ItemIdToExpandedRowMap, +} from '../../../common/components/stats_table'; +import type { FieldVisConfig } from '../../../common/components/stats_table/types'; +import { getDefaultDataVisualizerListState } from '../../components/index_data_visualizer_view/index_data_visualizer_view'; +import type { DataVisualizerTableState } from '../../../../../common/types'; +import type { DataVisualizerIndexBasedAppState } from '../../types/index_data_visualizer_state'; +import { IndexBasedDataVisualizerExpandedRow } from '../../../common/components/expanded_row/index_based_expanded_row'; +import { useDataVisualizerGridData } from '../../hooks/use_data_visualizer_grid_data'; +import { EmbeddableNoResultsEmptyPrompt } from './embeddable_field_stats_no_results'; + +const restorableDefaults = getDefaultDataVisualizerListState(); + +export const EmbeddableFieldStatsTableWrapper = ({ + input, + onOutputChange, +}: { + input: Required; + onOutputChange?: (ouput: any) => void; +}) => { + const [dataVisualizerListState, setDataVisualizerListState] = + useState>(restorableDefaults); + + const onTableChange = useCallback( + (update: DataVisualizerTableState) => { + setDataVisualizerListState({ ...dataVisualizerListState, ...update }); + if (onOutputChange) { + onOutputChange(update); + } + }, + [dataVisualizerListState, onOutputChange] + ); + + const { + configs, + searchQueryLanguage, + searchString, + extendedColumns, + progress, + overallStatsProgress, + setLastRefresh, + } = useDataVisualizerGridData(input, dataVisualizerListState); + + useEffect(() => { + setLastRefresh(Date.now()); + }, [input?.lastReloadRequestTime, setLastRefresh]); + + const getItemIdToExpandedRowMap = useCallback( + function (itemIds: string[], items: FieldVisConfig[]): ItemIdToExpandedRowMap { + return itemIds.reduce((m: ItemIdToExpandedRowMap, fieldName: string) => { + const item = items.find((fieldVisConfig) => fieldVisConfig.fieldName === fieldName); + if (item !== undefined) { + m[fieldName] = ( + + ); + } + return m; + }, {} as ItemIdToExpandedRowMap); + }, + [input, searchQueryLanguage, searchString] + ); + + if (progress === 100 && configs.length === 0) { + return ; + } + return ( + + items={configs} + pageState={dataVisualizerListState} + updatePageState={onTableChange} + getItemIdToExpandedRowMap={getItemIdToExpandedRowMap} + extendedColumns={extendedColumns} + showPreviewByDefault={input?.showPreviewByDefault} + onChange={onOutputChange} + loading={progress < 100} + overallStatsRunning={overallStatsProgress.isRunning} + /> + ); +}; diff --git a/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/embeddables/grid_embeddable/grid_embeddable.tsx b/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/embeddables/grid_embeddable/grid_embeddable.tsx index 47dac89aa9c1f..681f3d994aeef 100644 --- a/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/embeddables/grid_embeddable/grid_embeddable.tsx +++ b/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/embeddables/grid_embeddable/grid_embeddable.tsx @@ -9,156 +9,41 @@ import { pick } from 'lodash'; import { Observable, Subject } from 'rxjs'; import { CoreStart } from '@kbn/core/public'; import ReactDOM from 'react-dom'; -import React, { Suspense, useCallback, useEffect, useState } from 'react'; +import React, { Suspense } from 'react'; import useObservable from 'react-use/lib/useObservable'; -import { EuiEmptyPrompt, EuiIcon, EuiSpacer, EuiText } from '@elastic/eui'; -import { Filter } from '@kbn/es-query'; +import { EuiEmptyPrompt } from '@elastic/eui'; import { Required } from 'utility-types'; import { FormattedMessage } from '@kbn/i18n-react'; -import { - Embeddable, - EmbeddableInput, - EmbeddableOutput, - IContainer, -} from '@kbn/embeddable-plugin/public'; +import { Embeddable, IContainer } from '@kbn/embeddable-plugin/public'; import { UI_SETTINGS } from '@kbn/data-plugin/common'; import { KibanaContextProvider, KibanaThemeProvider } from '@kbn/kibana-react-plugin/public'; -import type { Query } from '@kbn/es-query'; -import { DataView, DataViewField } from '@kbn/data-views-plugin/public'; import { DatePickerContextProvider } from '@kbn/ml-date-picker'; -import type { SavedSearch } from '@kbn/saved-search-plugin/public'; -import type { SamplingOption } from '../../../../../common/types/field_stats'; +import { isPopulatedObject } from '@kbn/ml-is-populated-object'; +import type { DataVisualizerStartDependencies } from '../../../../plugin'; import { DATA_VISUALIZER_GRID_EMBEDDABLE_TYPE } from './constants'; import { EmbeddableLoading } from './embeddable_loading_fallback'; -import { DataVisualizerStartDependencies } from '../../../../plugin'; -import { - DataVisualizerTable, - ItemIdToExpandedRowMap, -} from '../../../common/components/stats_table'; -import { FieldVisConfig } from '../../../common/components/stats_table/types'; -import { getDefaultDataVisualizerListState } from '../../components/index_data_visualizer_view/index_data_visualizer_view'; -import type { DataVisualizerTableState } from '../../../../../common/types'; -import type { DataVisualizerIndexBasedAppState } from '../../types/index_data_visualizer_state'; -import { IndexBasedDataVisualizerExpandedRow } from '../../../common/components/expanded_row/index_based_expanded_row'; -import { useDataVisualizerGridData } from '../../hooks/use_data_visualizer_grid_data'; +import { EmbeddableESQLFieldStatsTableWrapper } from './embeddable_esql_field_stats_table'; +import { EmbeddableFieldStatsTableWrapper } from './embeddable_field_stats_table'; +import type { + DataVisualizerGridEmbeddableInput, + ESQLDataVisualizerGridEmbeddableInput, + DataVisualizerGridEmbeddableOutput, +} from './types'; export type DataVisualizerGridEmbeddableServices = [CoreStart, DataVisualizerStartDependencies]; -export interface DataVisualizerGridInput { - dataView: DataView; - savedSearch?: SavedSearch | null; - query?: Query; - visibleFieldNames?: string[]; - filters?: Filter[]; - showPreviewByDefault?: boolean; - allowEditDataView?: boolean; - id?: string; - /** - * Callback to add a filter to filter bar - */ - onAddFilter?: (field: DataViewField | string, value: string, type: '+' | '-') => void; - sessionId?: string; - fieldsToFetch?: string[]; - totalDocuments?: number; - samplingOption?: SamplingOption; -} -export type DataVisualizerGridEmbeddableInput = EmbeddableInput & DataVisualizerGridInput; -export type DataVisualizerGridEmbeddableOutput = EmbeddableOutput; - export type IDataVisualizerGridEmbeddable = typeof DataVisualizerGridEmbeddable; -const restorableDefaults = getDefaultDataVisualizerListState(); - -export const EmbeddableWrapper = ({ - input, - onOutputChange, -}: { - input: DataVisualizerGridEmbeddableInput; - onOutputChange?: (ouput: any) => void; -}) => { - const [dataVisualizerListState, setDataVisualizerListState] = - useState>(restorableDefaults); - - const onTableChange = useCallback( - (update: DataVisualizerTableState) => { - setDataVisualizerListState({ ...dataVisualizerListState, ...update }); - if (onOutputChange) { - onOutputChange(update); - } - }, - [dataVisualizerListState, onOutputChange] - ); - - const { - configs, - searchQueryLanguage, - searchString, - extendedColumns, - progress, - overallStatsProgress, - setLastRefresh, - } = useDataVisualizerGridData(input, dataVisualizerListState); - - useEffect(() => { - setLastRefresh(Date.now()); - }, [input?.lastReloadRequestTime, setLastRefresh]); - - const getItemIdToExpandedRowMap = useCallback( - function (itemIds: string[], items: FieldVisConfig[]): ItemIdToExpandedRowMap { - return itemIds.reduce((m: ItemIdToExpandedRowMap, fieldName: string) => { - const item = items.find((fieldVisConfig) => fieldVisConfig.fieldName === fieldName); - if (item !== undefined) { - m[fieldName] = ( - - ); - } - return m; - }, {} as ItemIdToExpandedRowMap); - }, - [input, searchQueryLanguage, searchString] - ); +function isESQLDataVisualizerEmbeddableInput( + input: unknown +): input is ESQLDataVisualizerGridEmbeddableInput { + return isPopulatedObject(input, ['esql']) && input.esql === true; +} - if (progress === 100 && configs.length === 0) { - return ( -
- - - - - -
- ); - } - return ( - - items={configs} - pageState={dataVisualizerListState} - updatePageState={onTableChange} - getItemIdToExpandedRowMap={getItemIdToExpandedRowMap} - extendedColumns={extendedColumns} - showPreviewByDefault={input?.showPreviewByDefault} - onChange={onOutputChange} - loading={progress < 100} - overallStatsRunning={overallStatsProgress.isRunning} - /> - ); -}; +function isDataVisualizerEmbeddableInput( + input: unknown +): input is Required { + return isPopulatedObject(input, ['dataView']); +} export const IndexDataVisualizerViewWrapper = (props: { id: string; @@ -169,8 +54,12 @@ export const IndexDataVisualizerViewWrapper = (props: { const { embeddableInput, onOutputChange } = props; const input = useObservable(embeddableInput); - if (input && input.dataView) { - return ; + + if (isESQLDataVisualizerEmbeddableInput(input)) { + return ; + } + if (isDataVisualizerEmbeddableInput(input)) { + return ; } else { return ( diff --git a/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/embeddables/grid_embeddable/types.ts b/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/embeddables/grid_embeddable/types.ts new file mode 100644 index 0000000000000..4d0165adb70d1 --- /dev/null +++ b/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/embeddables/grid_embeddable/types.ts @@ -0,0 +1,59 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { Filter } from '@kbn/es-query'; +import type { EmbeddableInput, EmbeddableOutput } from '@kbn/embeddable-plugin/public'; +import type { Query } from '@kbn/es-query'; +import { DataView, DataViewField } from '@kbn/data-views-plugin/public'; +import type { SavedSearch } from '@kbn/saved-search-plugin/public'; +import type { SamplingOption } from '../../../../../common/types/field_stats'; +import { DATA_VISUALIZER_INDEX_VIEWER } from '../../constants/index_data_visualizer_viewer'; +import { DataVisualizerIndexBasedAppState } from '../../types/index_data_visualizer_state'; +import type { ESQLQuery } from '../../search_strategy/requests/esql_utils'; + +export interface DataVisualizerGridInput { + dataView?: DataView; + savedSearch?: SavedSearch | null; + query?: T; + visibleFieldNames?: string[]; + filters?: Filter[]; + showPreviewByDefault?: boolean; + allowEditDataView?: boolean; + id?: string; + /** + * Callback to add a filter to filter bar + */ + onAddFilter?: (field: DataViewField | string, value: string, type: '+' | '-') => void; + sessionId?: string; + fieldsToFetch?: string[]; + totalDocuments?: number; + samplingOption?: SamplingOption; + /** + * If esql:true, switch table to ES|QL mode + */ + esql?: boolean; + /** + * If esql:true, the index pattern is used to validate time field + */ + indexPattern?: string; +} + +export type ESQLDataVisualizerGridEmbeddableInput = DataVisualizerGridInput; + +export type DataVisualizerGridEmbeddableInput = EmbeddableInput & DataVisualizerGridInput; +export type DataVisualizerGridEmbeddableOutput = EmbeddableOutput; + +export type ESQLDefaultLimitSizeOption = '5000' | '10000' | '100000' | '1000000' | 'none'; + +export interface ESQLDataVisualizerIndexBasedAppState extends DataVisualizerIndexBasedAppState { + limitSize: ESQLDefaultLimitSizeOption; +} + +export interface ESQLDataVisualizerIndexBasedPageUrlState { + pageKey: typeof DATA_VISUALIZER_INDEX_VIEWER; + pageUrlState: Required; +} diff --git a/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/hooks/esql/use_data_visualizer_esql_data.tsx b/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/hooks/esql/use_data_visualizer_esql_data.tsx new file mode 100644 index 0000000000000..00fe3db89c36c --- /dev/null +++ b/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/hooks/esql/use_data_visualizer_esql_data.tsx @@ -0,0 +1,628 @@ +/* + * 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, { useEffect, useMemo, useState, useCallback } from 'react'; +import { mlTimefilterRefresh$, useTimefilter } from '@kbn/ml-date-picker'; +import { merge } from 'rxjs'; +import { Comparators } from '@elastic/eui'; +import { useUrlState } from '@kbn/ml-url-state'; +import { KBN_FIELD_TYPES } from '@kbn/field-types'; +import type { QueryDslQueryContainer } from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; +import { getFieldType } from '@kbn/field-utils'; +import { UI_SETTINGS } from '@kbn/data-service'; +import useObservable from 'react-use/lib/useObservable'; +import { SEARCH_QUERY_LANGUAGE } from '@kbn/ml-query-utils'; +import { KibanaExecutionContext } from '@kbn/core-execution-context-common'; +import { useExecutionContext } from '@kbn/kibana-react-plugin/public'; +import type { AggregateQuery } from '@kbn/es-query'; +import type { SamplingOption } from '../../../../../common/types/field_stats'; +import type { FieldVisConfig } from '../../../../../common/types/field_vis_config'; +import type { SupportedFieldType } from '../../../../../common/types/job_field_type'; +import { useTimeBuckets } from '../../../common/hooks/use_time_buckets'; +import { ItemIdToExpandedRowMap } from '../../../common/components/stats_table'; +import type { + MetricFieldsStats, + TotalFieldsStats, +} from '../../../common/components/stats_table/components/field_count_stats'; +import { filterFields } from '../../../common/components/fields_stats_grid/filter_fields'; +import { IndexBasedDataVisualizerExpandedRow } from '../../../common/components/expanded_row/index_based_expanded_row'; +import { useESQLFieldStatsData } from './use_esql_field_stats_data'; +import type { NonAggregatableField } from '../../types/overall_stats'; +import { ESQLQuery, isESQLQuery } from '../../search_strategy/requests/esql_utils'; +import { DEFAULT_BAR_TARGET } from '../../../common/constants'; +import { type Column, useESQLOverallStatsData } from './use_esql_overall_stats_data'; +import { type AggregatableField } from '../../types/esql_data_visualizer'; +import { useDataVisualizerKibana } from '../../../kibana_context'; +import { DATA_VISUALIZER_GRID_EMBEDDABLE_TYPE } from '../../embeddables/grid_embeddable/constants'; +import type { + ESQLDataVisualizerGridEmbeddableInput, + ESQLDataVisualizerIndexBasedAppState, +} from '../../embeddables/grid_embeddable/types'; +import { getDefaultPageState } from '../../constants/index_data_visualizer_viewer'; + +const defaultSearchQuery = { + match_all: {}, +}; + +const FALLBACK_ESQL_QUERY: ESQLQuery = { esql: '' }; +const DEFAULT_SAMPLING_OPTION: SamplingOption = { + mode: 'random_sampling', + seed: '', + probability: 0, +}; +const DEFAULT_LIMIT_SIZE = '10000'; + +const defaults = getDefaultPageState(); + +export const getDefaultESQLDataVisualizerListState = ( + overrides?: Partial +): Required => ({ + pageIndex: 0, + pageSize: 25, + sortField: 'fieldName', + sortDirection: 'asc', + visibleFieldTypes: [], + visibleFieldNames: [], + limitSize: DEFAULT_LIMIT_SIZE, + searchString: '', + searchQuery: defaultSearchQuery, + searchQueryLanguage: SEARCH_QUERY_LANGUAGE.KUERY, + filters: [], + showDistributions: true, + showAllFields: false, + showEmptyFields: false, + probability: null, + rndSamplerPref: 'off', + ...overrides, +}); +export const useESQLDataVisualizerData = ( + input: ESQLDataVisualizerGridEmbeddableInput, + dataVisualizerListState: ESQLDataVisualizerIndexBasedAppState, + setQuery?: React.Dispatch> +) => { + const [lastRefresh, setLastRefresh] = useState(0); + const { services } = useDataVisualizerKibana(); + const { uiSettings, fieldFormats, executionContext } = services; + + const parentExecutionContext = useObservable(executionContext?.context$); + + const embeddableExecutionContext: KibanaExecutionContext = useMemo(() => { + const child: KibanaExecutionContext = { + type: 'visualization', + name: DATA_VISUALIZER_GRID_EMBEDDABLE_TYPE, + id: input.id, + }; + + return { + ...(parentExecutionContext ? parentExecutionContext : {}), + child, + }; + }, [parentExecutionContext, input.id]); + + useExecutionContext(executionContext, embeddableExecutionContext); + + const _timeBuckets = useTimeBuckets(); + const timefilter = useTimefilter({ + timeRangeSelector: true, + autoRefreshSelector: true, + }); + + const { currentDataView, query, visibleFieldNames, indexPattern } = useMemo( + () => ({ + currentSavedSearch: input?.savedSearch, + currentDataView: input.dataView, + query: input?.query ?? FALLBACK_ESQL_QUERY, + visibleFieldNames: input?.visibleFieldNames ?? [], + currentFilters: input?.filters, + fieldsToFetch: input?.fieldsToFetch, + /** By default, use random sampling **/ + samplingOption: input?.samplingOption ?? DEFAULT_SAMPLING_OPTION, + indexPattern: input?.indexPattern, + }), + [input] + ); + + const restorableDefaults = useMemo( + () => getDefaultESQLDataVisualizerListState(dataVisualizerListState), + // We just need to load the saved preference when the page is first loaded + // eslint-disable-next-line react-hooks/exhaustive-deps + [] + ); + + const [globalState, setGlobalState] = useUrlState('_g'); + + const showEmptyFields = + dataVisualizerListState.showEmptyFields ?? restorableDefaults.showEmptyFields; + const limitSize = dataVisualizerListState.limitSize ?? restorableDefaults.limitSize; + + /** Search strategy **/ + const fieldStatsRequest = useMemo( + () => { + // Obtain the interval to use for date histogram aggregations + // (such as the document count chart). Aim for 75 bars. + const buckets = _timeBuckets; + + const tf = timefilter; + + if (!buckets || !tf || (isESQLQuery(query) && query.esql === '')) return; + const activeBounds = tf.getActiveBounds(); + + let earliest: number | undefined; + let latest: number | undefined; + if (activeBounds !== undefined && currentDataView?.timeFieldName !== undefined) { + earliest = activeBounds.min?.valueOf(); + latest = activeBounds.max?.valueOf(); + } + + const bounds = tf.getActiveBounds(); + const barTarget = uiSettings.get(UI_SETTINGS.HISTOGRAM_BAR_TARGET) ?? DEFAULT_BAR_TARGET; + buckets.setInterval('auto'); + + if (bounds) { + buckets.setBounds(bounds); + buckets.setBarTarget(barTarget); + } + + const aggInterval = buckets.getInterval(); + + const filter = currentDataView?.timeFieldName + ? ({ + bool: { + must: [], + filter: [ + { + range: { + [currentDataView.timeFieldName]: { + format: 'strict_date_optional_time', + gte: timefilter.getTime().from, + lte: timefilter.getTime().to, + }, + }, + }, + ], + should: [], + must_not: [], + }, + } as QueryDslQueryContainer) + : undefined; + return { + earliest, + latest, + aggInterval, + intervalMs: aggInterval?.asMilliseconds(), + searchQuery: query, + limitSize, + sessionId: undefined, + indexPattern, + timeFieldName: currentDataView?.timeFieldName, + runtimeFieldMap: currentDataView?.getRuntimeMappings(), + lastRefresh, + filter, + }; + }, + // eslint-disable-next-line react-hooks/exhaustive-deps + [ + _timeBuckets, + timefilter, + currentDataView?.id, + // eslint-disable-next-line react-hooks/exhaustive-deps + JSON.stringify(query), + indexPattern, + lastRefresh, + limitSize, + ] + ); + + useEffect(() => { + // Force refresh on index pattern change + setLastRefresh(Date.now()); + }, [setLastRefresh]); + + useEffect( + () => { + if (globalState?.time !== undefined) { + timefilter.setTime({ + from: globalState.time.from, + to: globalState.time.to, + }); + } + }, + // eslint-disable-next-line react-hooks/exhaustive-deps + [JSON.stringify(globalState?.time), timefilter] + ); + + useEffect( + () => { + const timeUpdateSubscription = merge( + timefilter.getTimeUpdate$(), + timefilter.getAutoRefreshFetch$(), + mlTimefilterRefresh$ + ).subscribe(() => { + setGlobalState({ + time: timefilter.getTime(), + refreshInterval: timefilter.getRefreshInterval(), + }); + setLastRefresh(Date.now()); + }); + return () => { + timeUpdateSubscription.unsubscribe(); + }; + }, + // eslint-disable-next-line react-hooks/exhaustive-deps + [] + ); + + useEffect( + () => { + if (globalState?.refreshInterval !== undefined) { + timefilter.setRefreshInterval(globalState.refreshInterval); + } + }, // eslint-disable-next-line react-hooks/exhaustive-deps + [JSON.stringify(globalState?.refreshInterval), timefilter] + ); + + const { + documentCountStats, + totalCount, + overallStats, + overallStatsProgress, + columns, + cancelOverallStatsRequest, + timeFieldName, + } = useESQLOverallStatsData(fieldStatsRequest); + + const [metricConfigs, setMetricConfigs] = useState(defaults.metricConfigs); + const [metricsLoaded] = useState(defaults.metricsLoaded); + const [metricsStats, setMetricsStats] = useState(); + + const [nonMetricConfigs, setNonMetricConfigs] = useState(defaults.nonMetricConfigs); + const [nonMetricsLoaded] = useState(defaults.nonMetricsLoaded); + + const [fieldStatFieldsToFetch, setFieldStatFieldsToFetch] = useState(); + + const visibleFieldTypes = + dataVisualizerListState.visibleFieldTypes ?? restorableDefaults.visibleFieldTypes; + + useEffect( + function updateFieldStatFieldsToFetch() { + const { sortField, sortDirection } = dataVisualizerListState; + + // Otherwise, sort the list of fields by the initial sort field and sort direction + // Then divide into chunks by the initial page size + + const itemsSorter = Comparators.property( + sortField as string, + Comparators.default(sortDirection as 'asc' | 'desc' | undefined) + ); + + const preslicedSortedConfigs = [...nonMetricConfigs, ...metricConfigs] + .map((c) => ({ + ...c, + name: c.fieldName, + docCount: c.stats?.count, + cardinality: c.stats?.cardinality, + })) + .sort(itemsSorter); + + const filteredItems = filterFields( + preslicedSortedConfigs, + dataVisualizerListState.visibleFieldNames, + dataVisualizerListState.visibleFieldTypes + ); + + const { pageIndex, pageSize } = dataVisualizerListState; + + const pageOfConfigs = filteredItems.filteredFields + ?.slice(pageIndex * pageSize, (pageIndex + 1) * pageSize) + .filter((d) => d.existsInDocs === true); + + setFieldStatFieldsToFetch(pageOfConfigs); + }, + // eslint-disable-next-line react-hooks/exhaustive-deps + [ + dataVisualizerListState.pageIndex, + dataVisualizerListState.pageSize, + dataVisualizerListState.sortField, + dataVisualizerListState.sortDirection, + nonMetricConfigs, + metricConfigs, + ] + ); + + const { fieldStats, fieldStatsProgress, cancelFieldStatsRequest } = useESQLFieldStatsData({ + searchQuery: fieldStatsRequest?.searchQuery, + columns: fieldStatFieldsToFetch, + filter: fieldStatsRequest?.filter, + limitSize: fieldStatsRequest?.limitSize, + }); + + useEffect( + function resetFieldStatsFieldToFetch() { + // If query returns 0 document, no need to do more work here + if (totalCount === undefined) { + setFieldStatFieldsToFetch(undefined); + } + + if (totalCount === 0) { + setMetricConfigs(defaults.metricConfigs); + setNonMetricConfigs(defaults.nonMetricConfigs); + setMetricsStats(undefined); + setFieldStatFieldsToFetch(undefined); + } + }, + [totalCount] + ); + + const createMetricCards = useCallback( + () => { + if (!columns || !overallStats) return; + const configs: FieldVisConfig[] = []; + const aggregatableExistsFields: AggregatableField[] = + overallStats.aggregatableExistsFields || []; + + const allMetricFields = columns.filter((f) => { + return f.secondaryType === KBN_FIELD_TYPES.NUMBER; + }); + + const metricExistsFields = allMetricFields.filter((f) => { + return aggregatableExistsFields.find((existsF) => { + return existsF.fieldName === f.name; + }); + }); + + let _aggregatableFields: AggregatableField[] = overallStats.aggregatableExistsFields; + if (allMetricFields.length !== metricExistsFields.length && metricsLoaded === true) { + _aggregatableFields = _aggregatableFields.concat(overallStats.aggregatableNotExistsFields); + } + + const metricFieldsToShow = + metricsLoaded === true && showEmptyFields === true ? allMetricFields : metricExistsFields; + + metricFieldsToShow.forEach((field) => { + const fieldData = _aggregatableFields.find((f) => { + return f.fieldName === field.name; + }); + if (!fieldData) return; + + const metricConfig: FieldVisConfig = { + ...field, + ...fieldData, + loading: fieldData?.existsInDocs ?? true, + fieldFormat: fieldFormats.deserialize({ id: field.secondaryType }), + aggregatable: true, + deletable: false, + type: getFieldType(field) as SupportedFieldType, + }; + + configs.push(metricConfig); + }); + + setMetricsStats({ + totalMetricFieldsCount: allMetricFields.length, + visibleMetricsCount: metricFieldsToShow.length, + }); + setMetricConfigs(configs); + }, // eslint-disable-next-line react-hooks/exhaustive-deps + [metricsLoaded, overallStats, showEmptyFields, columns, currentDataView?.id] + ); + + const createNonMetricCards = useCallback( + () => { + if (!columns || !overallStats) return; + + const allNonMetricFields = columns.filter((f) => { + return f.secondaryType !== KBN_FIELD_TYPES.NUMBER; + }); + // Obtain the list of all non-metric fields which appear in documents + // (aggregatable or not aggregatable). + const populatedNonMetricFields: Column[] = []; // Kibana index pattern non metric fields. + let nonMetricFieldData: Array = []; // Basic non metric field data loaded from requesting overall stats. + const aggregatableExistsFields: AggregatableField[] = + overallStats.aggregatableExistsFields || []; + const nonAggregatableExistsFields: NonAggregatableField[] = + overallStats.nonAggregatableExistsFields || []; + + allNonMetricFields.forEach((f) => { + const checkAggregatableField = aggregatableExistsFields.find( + (existsField) => existsField.fieldName === f.name + ); + + if (checkAggregatableField !== undefined) { + populatedNonMetricFields.push(f); + nonMetricFieldData.push(checkAggregatableField); + } else { + const checkNonAggregatableField = nonAggregatableExistsFields.find( + (existsField) => existsField.fieldName === f.name + ); + + if (checkNonAggregatableField !== undefined) { + populatedNonMetricFields.push(f); + nonMetricFieldData.push(checkNonAggregatableField); + } + } + }); + + if (allNonMetricFields.length !== nonMetricFieldData.length && showEmptyFields === true) { + // Combine the field data obtained from Elasticsearch into a single array. + nonMetricFieldData = nonMetricFieldData.concat( + overallStats.aggregatableNotExistsFields, + overallStats.nonAggregatableNotExistsFields + ); + } + + const nonMetricFieldsToShow = showEmptyFields ? allNonMetricFields : populatedNonMetricFields; + + const configs: FieldVisConfig[] = []; + + nonMetricFieldsToShow.forEach((field) => { + const fieldData = nonMetricFieldData.find((f) => f.fieldName === field.name); + const nonMetricConfig: Partial = { + ...(fieldData ? fieldData : {}), + secondaryType: getFieldType(field) as SupportedFieldType, + loading: fieldData?.existsInDocs ?? true, + deletable: false, + fieldFormat: fieldFormats.deserialize({ id: field.secondaryType }), + }; + + // Map the field type from the Kibana index pattern to the field type + // used in the data visualizer. + const dataVisualizerType = getFieldType(field) as SupportedFieldType; + if (dataVisualizerType !== undefined) { + nonMetricConfig.type = dataVisualizerType; + } else { + // Add a flag to indicate that this is one of the 'other' Kibana + // field types that do not yet have a specific card type. + nonMetricConfig.type = field.type as SupportedFieldType; + nonMetricConfig.isUnsupportedType = true; + } + + if (field.name !== nonMetricConfig.fieldName) { + nonMetricConfig.displayName = field.name; + } + + configs.push(nonMetricConfig as FieldVisConfig); + }); + + setNonMetricConfigs(configs); + }, // eslint-disable-next-line react-hooks/exhaustive-deps + [columns, nonMetricsLoaded, overallStats, showEmptyFields] + ); + + const fieldsCountStats: TotalFieldsStats | undefined = useMemo(() => { + if (!overallStats) return; + + let _visibleFieldsCount = 0; + let _totalFieldsCount = 0; + Object.keys(overallStats).forEach((key) => { + const fieldsGroup = overallStats[key as keyof typeof overallStats]; + if (Array.isArray(fieldsGroup) && fieldsGroup.length > 0) { + _totalFieldsCount += fieldsGroup.length; + } + }); + + if (showEmptyFields === true) { + _visibleFieldsCount = _totalFieldsCount; + } else { + _visibleFieldsCount = + overallStats.aggregatableExistsFields.length + + overallStats.nonAggregatableExistsFields.length; + } + return { visibleFieldsCount: _visibleFieldsCount, totalFieldsCount: _totalFieldsCount }; + }, [overallStats, showEmptyFields]); + + useEffect( + () => { + createMetricCards(); + createNonMetricCards(); + }, // eslint-disable-next-line react-hooks/exhaustive-deps + [overallStats, showEmptyFields] + ); + + const configs = useMemo( + () => { + let combinedConfigs = [...nonMetricConfigs, ...metricConfigs]; + + combinedConfigs = filterFields( + combinedConfigs, + visibleFieldNames, + visibleFieldTypes + ).filteredFields; + + if (fieldStatsProgress.loaded === 100 && fieldStats) { + combinedConfigs = combinedConfigs.map((c) => { + const loadedFullStats = fieldStats.get(c.fieldName) ?? {}; + return loadedFullStats + ? { + ...c, + loading: false, + stats: { ...c.stats, ...loadedFullStats }, + } + : c; + }); + } + return combinedConfigs; + }, // eslint-disable-next-line react-hooks/exhaustive-deps + [ + nonMetricConfigs, + metricConfigs, + visibleFieldTypes, + visibleFieldNames, + fieldStatsProgress.loaded, + dataVisualizerListState.pageIndex, + dataVisualizerListState.pageSize, + ] + ); + + const getItemIdToExpandedRowMap = useCallback( + function (itemIds: string[], items: FieldVisConfig[]): ItemIdToExpandedRowMap { + return itemIds.reduce((map: ItemIdToExpandedRowMap, fieldName: string) => { + const item = items.find((fieldVisConfig) => fieldVisConfig.fieldName === fieldName); + if (item !== undefined) { + map[fieldName] = ( + + ); + } + return map; + }, {} as ItemIdToExpandedRowMap); + }, + [currentDataView, totalCount, query.esql] + ); + + const combinedProgress = useMemo( + () => + totalCount === 0 + ? overallStatsProgress.loaded + : overallStatsProgress.loaded * 0.3 + fieldStatsProgress.loaded * 0.7, + [totalCount, overallStatsProgress.loaded, fieldStatsProgress.loaded] + ); + + const onQueryUpdate = async (q?: AggregateQuery) => { + // When user submits a new query + // resets all current requests and other data + if (cancelOverallStatsRequest) { + cancelOverallStatsRequest(); + } + if (cancelFieldStatsRequest) { + cancelFieldStatsRequest(); + } + // Reset field stats to fetch state + setFieldStatFieldsToFetch(undefined); + setMetricConfigs(defaults.metricConfigs); + setNonMetricConfigs(defaults.nonMetricConfigs); + if (isESQLQuery(q) && setQuery) { + setQuery(q); + } + }; + + return { + totalCount, + progress: combinedProgress, + overallStatsProgress, + configs, + // Column with action to lens, data view editor, etc + // set to nothing for now + extendedColumns: undefined, + documentCountStats, + metricsStats, + overallStats, + timefilter, + setLastRefresh, + getItemIdToExpandedRowMap, + cancelOverallStatsRequest, + cancelFieldStatsRequest, + onQueryUpdate, + limitSize, + showEmptyFields, + fieldsCountStats, + timeFieldName, + }; +}; diff --git a/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/hooks/esql/use_esql_field_stats_data.ts b/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/hooks/esql/use_esql_field_stats_data.ts index 5c034bf82ebf7..95a179024ab3d 100644 --- a/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/hooks/esql/use_esql_field_stats_data.ts +++ b/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/hooks/esql/use_esql_field_stats_data.ts @@ -20,7 +20,7 @@ import { getESQLNumericFieldStats } from '../../search_strategy/esql_requests/ge import { getESQLKeywordFieldStats } from '../../search_strategy/esql_requests/get_keyword_fields'; import { getESQLDateFieldStats } from '../../search_strategy/esql_requests/get_date_field_stats'; import { getESQLBooleanFieldStats } from '../../search_strategy/esql_requests/get_boolean_field_stats'; -import { getESQLTextFieldStats } from '../../search_strategy/esql_requests/get_text_field_stats'; +import { getESQLExampleFieldValues } from '../../search_strategy/esql_requests/get_text_field_stats'; export const useESQLFieldStatsData = ({ searchQuery, @@ -56,13 +56,13 @@ export const useESQLFieldStatsData = ({ const fetchFieldStats = async () => { cancelRequest(); - if (!isESQLQuery(searchQuery) || !allColumns) return; - setFetchState({ ...getInitialProgress(), isRunning: true, error: undefined, }); + if (!isESQLQuery(searchQuery) || !allColumns) return; + try { // By default, limit the source data to 100,000 rows const esqlBaseQuery = searchQuery.esql + getSafeESQLLimitSize(limitSize); @@ -114,8 +114,13 @@ export const useESQLFieldStatsData = ({ }).then(addToProcessedFieldStats); // GETTING STATS FOR TEXT FIELDS - await getESQLTextFieldStats({ - columns: columns.filter((f) => f.secondaryType === 'text'), + await getESQLExampleFieldValues({ + columns: columns.filter( + (f) => + f.secondaryType === 'text' || + f.secondaryType === 'geo_point' || + f.secondaryType === 'geo_shape' + ), filter, runRequest, esqlBaseQuery, diff --git a/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/hooks/esql/use_esql_overall_stats_data.ts b/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/hooks/esql/use_esql_overall_stats_data.ts index c23a75e2e3bac..d2f8f31bc1a4d 100644 --- a/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/hooks/esql/use_esql_overall_stats_data.ts +++ b/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/hooks/esql/use_esql_overall_stats_data.ts @@ -9,7 +9,7 @@ import { ESQL_SEARCH_STRATEGY, KBN_FIELD_TYPES } from '@kbn/data-plugin/common'; import type { QueryDslQueryContainer } from '@kbn/data-views-plugin/common/types'; import type { AggregateQuery } from '@kbn/es-query'; import { i18n } from '@kbn/i18n'; -import { useCallback, useEffect, useMemo, useReducer } from 'react'; +import { useCallback, useEffect, useMemo, useReducer, useRef } from 'react'; import { type UseCancellableSearch, useCancellableSearch } from '@kbn/ml-cancellable-search'; import type { estypes } from '@elastic/elasticsearch'; import type { ISearchOptions } from '@kbn/data-plugin/common'; @@ -29,13 +29,13 @@ import { } from '../../search_strategy/requests/esql_utils'; import type { NonAggregatableField } from '../../types/overall_stats'; import { getESQLSupportedAggs } from '../../utils/get_supported_aggs'; -import type { ESQLDefaultLimitSizeOption } from '../../components/search_panel/esql/limit_size'; import { getESQLOverallStats } from '../../search_strategy/esql_requests/get_count_and_cardinality'; import type { AggregatableField } from '../../types/esql_data_visualizer'; import { handleError, type HandleErrorCallback, } from '../../search_strategy/esql_requests/handle_error'; +import type { ESQLDefaultLimitSizeOption } from '../../embeddables/grid_embeddable/types'; export interface Column { type: string; @@ -66,7 +66,7 @@ const getESQLDocumentCountStats = async ( intervalMs?: number, searchOptions?: ISearchOptions, onError?: HandleErrorCallback -): Promise<{ documentCountStats?: DocumentCountStats; totalCount: number }> => { +): Promise<{ documentCountStats?: DocumentCountStats; totalCount: number; request?: object }> => { if (!isESQLQuery(query)) { throw Error( i18n.translate('xpack.dataVisualizer.esql.noQueryProvided', { @@ -116,7 +116,7 @@ const getESQLDocumentCountStats = async ( buckets: _buckets, totalCount, }; - return { documentCountStats: result, totalCount }; + return { documentCountStats: result, totalCount, request }; } catch (error) { handleError({ request, @@ -139,6 +139,7 @@ const getESQLDocumentCountStats = async ( try { const esqlResults = await runRequest(request, { ...(searchOptions ?? {}), strategy: 'esql' }); return { + request, documentCountStats: undefined, totalCount: esqlResults?.rawResponse.values[0][0], }; @@ -188,6 +189,7 @@ export const useESQLOverallStatsData = ( lastRefresh: number; filter?: QueryDslQueryContainer; limitSize?: ESQLDefaultLimitSizeOption; + totalCount?: number; } | undefined ) => { @@ -198,6 +200,7 @@ export const useESQLOverallStatsData = ( }, } = useDataVisualizerKibana(); + const previousDocCountRequest = useRef(''); const { runRequest, cancelRequest } = useCancellableSearch(data); const [tableData, setTableData] = useReducer(getReducer(), getInitialData()); @@ -226,9 +229,14 @@ export const useESQLOverallStatsData = ( isRunning: true, error: undefined, }); - setTableData({ totalCount: undefined, documentCountStats: undefined }); - const { searchQuery, intervalMs, filter, limitSize } = fieldStatsRequest; + const { + searchQuery, + intervalMs, + filter: filter, + limitSize, + totalCount: knownTotalCount, + } = fieldStatsRequest; if (!isESQLQuery(searchQuery)) { return; @@ -276,17 +284,45 @@ export const useESQLOverallStatsData = ( setTableData({ columns, timeFieldName }); - const { totalCount, documentCountStats } = await getESQLDocumentCountStats( - runRequest, + // We don't need to fetch the doc count stats again if only the limit size is changed + // so return the previous totalCount, documentCountStats if available + const hashedDocCountParams = JSON.stringify({ searchQuery, filter, timeFieldName, intervalInMs, - undefined, - onError - ); + }); + let { totalCount, documentCountStats } = tableData; + if (knownTotalCount !== undefined) { + totalCount = knownTotalCount; + } + if ( + knownTotalCount === undefined && + (totalCount === undefined || + documentCountStats === undefined || + hashedDocCountParams !== previousDocCountRequest.current) + ) { + setTableData({ totalCount: undefined, documentCountStats: undefined }); + + previousDocCountRequest.current = hashedDocCountParams; + const results = await getESQLDocumentCountStats( + runRequest, + searchQuery, + filter, + timeFieldName, + intervalInMs, + undefined, + onError + ); + + totalCount = results.totalCount; + documentCountStats = results.documentCountStats; + setTableData({ totalCount, documentCountStats }); + } - setTableData({ totalCount, documentCountStats }); + if (totalCount === undefined) { + totalCount = 0; + } setOverallStatsProgress({ loaded: 50, }); @@ -342,6 +378,14 @@ export const useESQLOverallStatsData = ( const esqlBaseQueryWithLimit = searchQuery.esql + getSafeESQLLimitSize(limitSize); if (totalCount === 0) { + setTableData({ + aggregatableFields: undefined, + nonAggregatableFields: undefined, + overallStats: undefined, + columns: undefined, + timeFieldName: undefined, + }); + setOverallStatsProgress({ loaded: 100, isRunning: false, diff --git a/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/hooks/use_data_visualizer_grid_data.ts b/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/hooks/use_data_visualizer_grid_data.ts index 4570a2019af26..8eb7df9805b34 100644 --- a/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/hooks/use_data_visualizer_grid_data.ts +++ b/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/hooks/use_data_visualizer_grid_data.ts @@ -37,14 +37,14 @@ import { import type { FieldRequestConfig, SupportedFieldType } from '../../../../common/types'; import { kbnTypeToSupportedType } from '../../common/util/field_types_utils'; import { getActions } from '../../common/components/field_data_row/action_menu'; -import type { DataVisualizerGridInput } from '../embeddables/grid_embeddable/grid_embeddable'; -import { getDefaultPageState } from '../components/index_data_visualizer_view/index_data_visualizer_view'; import { useFieldStatsSearchStrategy } from './use_field_stats'; import { useOverallStats } from './use_overall_stats'; import type { OverallStatsSearchStrategyParams } from '../../../../common/types/field_stats'; import type { AggregatableField, NonAggregatableField } from '../types/overall_stats'; import { getSupportedAggs } from '../utils/get_supported_aggs'; import { DEFAULT_BAR_TARGET } from '../../common/constants'; +import { DataVisualizerGridInput } from '../embeddables/grid_embeddable/types'; +import { getDefaultPageState } from '../constants/index_data_visualizer_viewer'; const defaults = getDefaultPageState(); @@ -58,7 +58,8 @@ const DEFAULT_SAMPLING_OPTION: SamplingOption = { probability: 0, }; export const useDataVisualizerGridData = ( - input: DataVisualizerGridInput, + // Data view is required for non-ES|QL queries like kuery or lucene + input: Required, dataVisualizerListState: Required, savedRandomSamplerPreference?: RandomSamplerOption, onUpdate?: (params: Dictionary) => void @@ -571,6 +572,7 @@ export const useDataVisualizerGridData = ( // Inject custom action column for the index based visualizer // Hide the column completely if no access to any of the plugins const extendedColumns = useMemo(() => { + if (!input.dataView) return undefined; const actions = getActions( input.dataView, services, diff --git a/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/hooks/use_overall_stats.ts b/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/hooks/use_overall_stats.ts index ff6030c45f96e..713ee17e621e6 100644 --- a/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/hooks/use_overall_stats.ts +++ b/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/hooks/use_overall_stats.ts @@ -30,7 +30,6 @@ import { processNonAggregatableFieldsExistResponse, } from '../search_strategy/requests/overall_stats'; import type { OverallStats } from '../types/overall_stats'; -import { getDefaultPageState } from '../components/index_data_visualizer_view/index_data_visualizer_view'; import { DataStatsFetchProgress, isRandomSamplingOption, @@ -38,7 +37,10 @@ import { } from '../../../../common/types/field_stats'; import { getDocumentCountStats } from '../search_strategy/requests/get_document_stats'; import { getInitialProgress, getReducer } from '../progress_utils'; -import { MAX_CONCURRENT_REQUESTS } from '../constants/index_data_visualizer_viewer'; +import { + getDefaultPageState, + MAX_CONCURRENT_REQUESTS, +} from '../constants/index_data_visualizer_viewer'; import { displayError } from '../../common/util/display_error'; /** diff --git a/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/search_strategy/esql_requests/get_boolean_field_stats.ts b/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/search_strategy/esql_requests/get_boolean_field_stats.ts index b4adcd4ee4f05..865d2c87bc02b 100644 --- a/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/search_strategy/esql_requests/get_boolean_field_stats.ts +++ b/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/search_strategy/esql_requests/get_boolean_field_stats.ts @@ -76,7 +76,7 @@ export const getESQLBooleanFieldStats = async ({ trueCount = row[0]; } return { - key_as_string: row[1]?.toString(), + key_as_string: row[1] === false ? 'false' : 'true', doc_count: row[0], percent: row[0] / topValuesSampleSize, }; diff --git a/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/search_strategy/esql_requests/get_count_and_cardinality.ts b/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/search_strategy/esql_requests/get_count_and_cardinality.ts index 72e67db1fafae..832a75aa4cc61 100644 --- a/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/search_strategy/esql_requests/get_count_and_cardinality.ts +++ b/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/search_strategy/esql_requests/get_count_and_cardinality.ts @@ -16,10 +16,10 @@ import { getSafeESQLName } from '../requests/esql_utils'; import { MAX_CONCURRENT_REQUESTS } from '../../constants/index_data_visualizer_viewer'; import type { NonAggregatableField } from '../../types/overall_stats'; import { isFulfilled } from '../../../common/util/promise_all_settled_utils'; -import type { ESQLDefaultLimitSizeOption } from '../../components/search_panel/esql/limit_size'; import type { Column } from '../../hooks/esql/use_esql_overall_stats_data'; import { AggregatableField } from '../../types/esql_data_visualizer'; import { handleError, HandleErrorCallback } from './handle_error'; +import type { ESQLDefaultLimitSizeOption } from '../../embeddables/grid_embeddable/types'; interface Field extends Column { aggregatable?: boolean; @@ -42,25 +42,72 @@ const getESQLOverallStatsInChunk = async ({ onError?: HandleErrorCallback; }) => { if (fields.length > 0) { - const aggregatableFieldsToQuery = fields.filter((f) => f.aggregatable); + const aggToIndex = { count: 0, cardinality: 1 }; + // Track what's the starting index for the next field + // For aggregatable field, we are getting count(EVAL MV_MIN()) and count_disticnt + // For non-aggregatable field, we are getting only count() + let startIndex = 0; + /** Example query: + * from {indexPattern} | LIMIT {limitSize} + * | EVAL `ne_{aggregableField}` = MV_MIN({aggregableField}), + * | STATs `{aggregableField}_count` = COUNT(`ne_{aggregableField}`), + * `{aggregableField}_cardinality` = COUNT_DISTINCT({aggregableField}), + * `{nonAggregableField}_count` = COUNT({nonAggregableField}) + */ + const fieldsToFetch: Array = + fields.map((field) => { + if (field.aggregatable) { + const result = { + ...field, + startIndex, + // Field values can be an array of values (fieldName = ['a', 'b', 'c']) + // and count(fieldName) will count all the field values in the array + // Ex: for 2 docs, count(fieldName) might return 5 + // So we need to do count(EVAL(MV_MIN(fieldName))) instead + // to get accurate % of rows where field value exists + evalQuery: `${getSafeESQLName(`ne_${field.name}`)} = MV_MIN(${getSafeESQLName( + `${field.name}` + )})`, + query: `${getSafeESQLName(`${field.name}_count`)} = COUNT(${getSafeESQLName( + `ne_${field.name}` + )}), + ${getSafeESQLName(`${field.name}_cardinality`)} = COUNT_DISTINCT(${getSafeESQLName( + field.name + )})`, + }; + // +2 for count, and count_dictinct + startIndex += 2; + return result; + } else { + const result = { + ...field, + startIndex, + query: `${getSafeESQLName(`${field.name}_count`)} = COUNT(${getSafeESQLName( + field.name + )})`, + }; + // +1 for count for non-aggregatable field + startIndex += 1; + return result; + } + }); - let countQuery = aggregatableFieldsToQuery.length > 0 ? '| STATS ' : ''; - countQuery += aggregatableFieldsToQuery - .map((field) => { - // count idx = 0, cardinality idx = 1 - return `${getSafeESQLName(`${field.name}_count`)} = COUNT(${getSafeESQLName(field.name)}), - ${getSafeESQLName(`${field.name}_cardinality`)} = COUNT_DISTINCT(${getSafeESQLName( - field.name - )})`; - }) + const evalQuery = fieldsToFetch + .map((field) => field.evalQuery) + .filter(isDefined) .join(','); + let countQuery = fieldsToFetch.length > 0 ? '| STATS ' : ''; + countQuery += fieldsToFetch.map((field) => field.query).join(','); + + const query = esqlBaseQueryWithLimit + (evalQuery ? ' | EVAL ' + evalQuery : '') + countQuery; const request = { params: { - query: esqlBaseQueryWithLimit + countQuery, + query, ...(filter ? { filter } : {}), }, }; + try { const esqlResults = await runRequest(request, { strategy: ESQL_SEARCH_STRATEGY }); const stats = { @@ -77,11 +124,14 @@ const getESQLOverallStatsInChunk = async ({ const sampleCount = limitSize === 'none' || !isDefined(limitSize) ? totalCount : parseInt(limitSize, 10); - aggregatableFieldsToQuery.forEach((field, idx) => { - const count = esqlResultsResp.values[0][idx * 2] as number; - const cardinality = esqlResultsResp.values[0][idx * 2 + 1] as number; + fieldsToFetch.forEach((field, idx) => { + const count = esqlResultsResp.values[0][field.startIndex + aggToIndex.count] as number; if (field.aggregatable === true) { + const cardinality = esqlResultsResp.values[0][ + field.startIndex + aggToIndex.cardinality + ] as number; + if (count > 0) { stats.aggregatableExistsFields.push({ ...field, @@ -102,14 +152,20 @@ const getESQLOverallStatsInChunk = async ({ }); } } else { - const fieldData = { - fieldName: field.name, - existsInDocs: true, - }; if (count > 0) { - stats.nonAggregatableExistsFields.push(fieldData); + stats.nonAggregatableExistsFields.push({ + fieldName: field.name, + existsInDocs: true, + stats: { + sampleCount, + count, + }, + }); } else { - stats.nonAggregatableNotExistsFields.push(fieldData); + stats.nonAggregatableNotExistsFields.push({ + fieldName: field.name, + existsInDocs: false, + }); } } }); @@ -123,8 +179,8 @@ const getESQLOverallStatsInChunk = async ({ defaultMessage: 'Unable to fetch count & cardinality for {count} {count, plural, one {field} other {fields}}: {fieldNames}', values: { - count: aggregatableFieldsToQuery.length, - fieldNames: aggregatableFieldsToQuery.map((r) => r.name).join(), + count: fieldsToFetch.length, + fieldNames: fieldsToFetch.map((r) => r.name).join(), }, }), }); diff --git a/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/search_strategy/esql_requests/get_text_field_stats.ts b/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/search_strategy/esql_requests/get_text_field_stats.ts index f4bc710a05839..b5f26bbb89f0b 100644 --- a/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/search_strategy/esql_requests/get_text_field_stats.ts +++ b/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/search_strategy/esql_requests/get_text_field_stats.ts @@ -24,7 +24,7 @@ interface Params { * @param * @returns */ -export const getESQLTextFieldStats = async ({ +export const getESQLExampleFieldValues = async ({ runRequest, columns: textFields, esqlBaseQuery, diff --git a/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/types/index_data_visualizer_state.ts b/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/types/index_data_visualizer_state.ts index 960e3eb269547..767179c3c7c37 100644 --- a/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/types/index_data_visualizer_state.ts +++ b/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/types/index_data_visualizer_state.ts @@ -8,15 +8,28 @@ import type { Filter } from '@kbn/es-query'; import type { Query } from '@kbn/data-plugin/common/query'; import type { SearchQueryLanguage } from '@kbn/ml-query-utils'; +import type { FieldVisConfig } from '../../../../common/types/field_vis_config'; import type { RandomSamplerOption } from '../constants/random_sampler'; import type { DATA_VISUALIZER_INDEX_VIEWER } from '../constants/index_data_visualizer_viewer'; +import type { OverallStats } from './overall_stats'; export interface DataVisualizerIndexBasedPageUrlState { pageKey: typeof DATA_VISUALIZER_INDEX_VIEWER; pageUrlState: Required; } +export interface DataVisualizerPageState { + overallStats: OverallStats; + metricConfigs: FieldVisConfig[]; + totalMetricFieldCount: number; + populatedMetricFieldCount: number; + metricsLoaded: boolean; + nonMetricConfigs: FieldVisConfig[]; + nonMetricsLoaded: boolean; + documentCountStats?: FieldVisConfig; +} + export interface ListingPageUrlState { pageSize: number; pageIndex: number; diff --git a/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/types/storage.ts b/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/types/storage.ts index 1e1f39e1f5215..b4835ed4f5a04 100644 --- a/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/types/storage.ts +++ b/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/types/storage.ts @@ -6,9 +6,9 @@ */ import { type FrozenTierPreference } from '@kbn/ml-date-picker'; -import type { ESQLDefaultLimitSizeOption } from '../components/search_panel/esql/limit_size'; import { type RandomSamplerOption } from '../constants/random_sampler'; +import type { ESQLDefaultLimitSizeOption } from '../embeddables/grid_embeddable/types'; import { DATA_DRIFT_COMPARISON_CHART_TYPE } from './data_drift'; export const DV_FROZEN_TIER_PREFERENCE = 'dataVisualizer.frozenDataTierPreference'; From 39559b74a216e68cc9c6193b5ffa14cd1dc9a385 Mon Sep 17 00:00:00 2001 From: Davis McPhee Date: Mon, 12 Feb 2024 20:23:53 -0400 Subject: [PATCH 36/83] [Unified Data Table] Add header row height configuration (#175501) ## Summary This PR adds header row height configuration to Discover, and aligns the row height configuration UI in Discover and Lens. The header row height configuration should behave the same way as the cell row height configuration, including persisting to saved searches and displaying in the saved search embeddable. Expanded: expanded Collapsed: collapsed Resolves #171708. ### 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) - [ ] ~[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 - [ ] ~[Flaky Test Runner](https://ci-stats.kibana.dev/trigger_flaky_test_runner/1) was used on any tests changed~ - [x] 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)~ - [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) ### 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) --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: Stratoula Kalafateli --- .../__mocks__/local_storage_mock.ts | 9 +- packages/kbn-unified-data-table/index.ts | 4 + .../data_table_columns.test.tsx.snap | 2046 ++++++++++++++--- .../column_header_truncate_container.tsx | 31 +- .../src/components/data_table.scss | 5 + .../src/components/data_table.test.tsx | 20 +- .../src/components/data_table.tsx | 107 +- ...table_additional_display_settings.test.tsx | 92 +- ...data_table_additional_display_settings.tsx | 90 +- .../components/data_table_column_header.tsx | 18 +- .../components/data_table_columns.test.tsx | 25 + .../src/components/data_table_columns.tsx | 19 +- .../components/row_height_settings.test.tsx | 75 + .../src/components/row_height_settings.tsx | 100 + .../kbn-unified-data-table/src/constants.ts | 2 +- .../src/hooks/use_row_height.test.tsx | 153 ++ .../src/hooks/use_row_height.ts | 103 + .../src/hooks/use_row_heights_options.test.ts | 49 + .../hooks/use_row_heights_options.test.tsx | 77 - .../src/hooks/use_row_heights_options.ts | 66 +- .../src/utils/row_heights.ts | 12 +- .../check_registered_types.test.ts | 2 +- .../context/context_app_content.test.tsx | 6 + .../context/context_app_content.tsx | 2 +- .../components/layout/discover_documents.tsx | 47 +- .../discover_app_state_container.test.ts | 2 + .../services/discover_app_state_container.ts | 4 + .../main/services/discover_state.test.ts | 1 + .../main/utils/get_state_defaults.test.ts | 2 + .../main/utils/get_state_defaults.ts | 4 + .../main/utils/update_saved_search.ts | 1 + .../saved_search_embeddable.test.ts | 6 + .../embeddable/saved_search_embeddable.tsx | 8 +- .../public/embeddable/saved_search_grid.tsx | 2 +- .../search_embeddable_factory.test.ts | 1 + .../content_management/v1/cm_services.ts | 1 + .../common/saved_searches_utils.ts | 1 + .../common/service/get_saved_searches.test.ts | 2 + .../service/saved_searches_utils.test.ts | 2 + .../common/service/saved_searches_utils.ts | 1 + src/plugins/saved_search/common/types.ts | 2 + .../save_saved_searches.test.ts | 3 + .../saved_search_attribute_service.test.ts | 1 + .../public/services/saved_searches/types.ts | 1 + .../saved_search_storage.ts | 1 + .../server/saved_objects/schema.ts | 14 +- .../server/saved_objects/search.ts | 23 +- .../discover/group2/_data_grid_row_height.ts | 52 +- test/functional/services/data_grid.ts | 25 +- .../components/row_height_settings.tsx | 101 - .../datatable/components/toolbar.tsx | 4 +- x-pack/plugins/lens/tsconfig.json | 3 +- .../translations/translations/fr-FR.json | 3 - .../translations/translations/ja-JP.json | 3 - .../translations/translations/zh-CN.json | 3 - 55 files changed, 2690 insertions(+), 747 deletions(-) create mode 100644 packages/kbn-unified-data-table/src/components/row_height_settings.test.tsx create mode 100644 packages/kbn-unified-data-table/src/components/row_height_settings.tsx create mode 100644 packages/kbn-unified-data-table/src/hooks/use_row_height.test.tsx create mode 100644 packages/kbn-unified-data-table/src/hooks/use_row_height.ts create mode 100644 packages/kbn-unified-data-table/src/hooks/use_row_heights_options.test.ts delete mode 100644 packages/kbn-unified-data-table/src/hooks/use_row_heights_options.test.tsx delete mode 100644 x-pack/plugins/lens/public/visualizations/datatable/components/row_height_settings.tsx diff --git a/packages/kbn-unified-data-table/__mocks__/local_storage_mock.ts b/packages/kbn-unified-data-table/__mocks__/local_storage_mock.ts index 42cd33d2eb699..a89eb6662101c 100644 --- a/packages/kbn-unified-data-table/__mocks__/local_storage_mock.ts +++ b/packages/kbn-unified-data-table/__mocks__/local_storage_mock.ts @@ -6,7 +6,9 @@ * Side Public License, v 1. */ -export class LocalStorageMock { +import { Storage } from '@kbn/kibana-utils-plugin/public'; + +class LocalStorageMock { private store: Record; constructor(defaultStore: Record) { this.store = defaultStore; @@ -18,9 +20,12 @@ export class LocalStorageMock { return this.store[key] || null; } set(key: string, value: unknown) { - this.store[key] = String(value); + this.store[key] = value; } remove(key: string) { delete this.store[key]; } } + +export const createLocalStorageMock = (defaultStore: Record) => + new LocalStorageMock(defaultStore) as unknown as Storage; diff --git a/packages/kbn-unified-data-table/index.ts b/packages/kbn-unified-data-table/index.ts index ce166f212e2ae..9cb8b67dc4b2e 100644 --- a/packages/kbn-unified-data-table/index.ts +++ b/packages/kbn-unified-data-table/index.ts @@ -12,6 +12,10 @@ export type { UnifiedDataTableRenderCustomToolbar, UnifiedDataTableRenderCustomToolbarProps, } from './src/components/data_table'; +export { + RowHeightSettings, + type RowHeightSettingsProps, +} from './src/components/row_height_settings'; export { getDisplayedColumns } from './src/utils/columns'; export { getTextBasedColumnTypes } from './src/utils/get_column_types'; export { ROWS_HEIGHT_OPTIONS } from './src/constants'; diff --git a/packages/kbn-unified-data-table/src/components/__snapshots__/data_table_columns.test.tsx.snap b/packages/kbn-unified-data-table/src/components/__snapshots__/data_table_columns.test.tsx.snap index 2a80fd57f6ecd..3c00afb1c1dd8 100644 --- a/packages/kbn-unified-data-table/src/components/__snapshots__/data_table_columns.test.tsx.snap +++ b/packages/kbn-unified-data-table/src/components/__snapshots__/data_table_columns.test.tsx.snap @@ -97,60 +97,9 @@ Array [ "cellActions": Array [ [Function], ], - "display": undefined, - "displayAsText": "message", - "id": "message", - "isSortable": true, - "schema": "string", - "visibleCellActions": undefined, - }, -] -`; - -exports[`Data table columns column tokens returns eui grid columns with tokens 1`] = ` -Array [ - Object { - "actions": Object { - "additional": Array [ - Object { - "data-test-subj": "gridCopyColumnNameToClipBoardButton", - "iconProps": Object { - "size": "m", - }, - "iconType": "copyClipboard", - "label": , - "onClick": [Function], - "size": "xs", - }, - Object { - "data-test-subj": "gridCopyColumnValuesToClipBoardButton", - "iconProps": Object { - "size": "m", - }, - "iconType": "copyClipboard", - "label": , - "onClick": [Function], - "size": "xs", - }, - ], - "showHide": false, - "showMoveLeft": true, - "showMoveRight": true, - }, - "cellActions": Array [ - [Function], - [Function], - [Function], - ], - "display": , - "displayAsText": "timestamp", - "id": "timestamp", - "initialWidth": 212, + "displayAsText": "message", + "id": "message", "isSortable": true, - "schema": "datetime", + "schema": "string", "visibleCellActions": undefined, }, +] +`; + +exports[`Data table columns column tokens returns eui grid columns with tokens 1`] = ` +Array [ Object { "actions": Object { "additional": Array [ @@ -433,10 +508,7 @@ Array [ "size": "xs", }, ], - "showHide": Object { - "iconType": "cross", - "label": "Remove column", - }, + "showHide": false, "showMoveLeft": true, "showMoveRight": true, }, @@ -445,9 +517,7 @@ Array [ [Function], [Function], ], - "display": , - "displayAsText": "extension", - "id": "extension", - "isSortable": false, - "schema": "string", + "displayAsText": "timestamp", + "id": "timestamp", + "initialWidth": 212, + "isSortable": true, + "schema": "datetime", "visibleCellActions": undefined, }, Object { @@ -728,10 +810,12 @@ Array [ }, "cellActions": Array [ [Function], + [Function], + [Function], ], "display": , - "displayAsText": "message", - "id": "message", + "displayAsText": "extension", + "id": "extension", "isSortable": false, "schema": "string", "visibleCellActions": undefined, }, -] -`; - -exports[`Data table columns column tokens returns eui grid columns with tokens for custom column types 1`] = ` -Array [ Object { "actions": Object { "additional": Array [ @@ -1017,18 +1097,10 @@ Array [ }, "cellActions": Array [ [Function], - [Function], - [Function], ], "display": , - "displayAsText": "extension", - "id": "extension", + "displayAsText": "message", + "id": "message", "isSortable": false, - "schema": "numeric", + "schema": "string", "visibleCellActions": undefined, }, +] +`; + +exports[`Data table columns column tokens returns eui grid columns with tokens for custom column types 1`] = ` +Array [ Object { "actions": Object { "additional": Array [ @@ -1336,10 +1387,12 @@ Array [ }, "cellActions": Array [ [Function], + [Function], + [Function], ], "display": , - "displayAsText": "message", - "id": "message", - "isSortable": false, - "schema": "kibana-json", - "visibleCellActions": undefined, - }, -] -`; - -exports[`Data table columns getEuiGridColumns returns eui grid columns showing default columns 1`] = ` -Array [ - Object { - "actions": Object { - "additional": Array [ - Object { - "data-test-subj": "gridCopyColumnNameToClipBoardButton", - "iconProps": Object { - "size": "m", - }, - "iconType": "copyClipboard", - "label": , - "onClick": [Function], - "size": "xs", - }, - Object { - "data-test-subj": "gridCopyColumnValuesToClipBoardButton", - "iconProps": Object { - "size": "m", - }, - "iconType": "copyClipboard", - "label": , - "onClick": [Function], - "size": "xs", - }, - ], - "showHide": false, - "showMoveLeft": false, - "showMoveRight": false, - }, - "cellActions": Array [ - [Function], - [Function], - [Function], - ], - "display": undefined, "displayAsText": "extension", "id": "extension", "isSortable": false, - "schema": "string", - "visibleCellActions": undefined, - }, - Object { - "actions": Object { - "additional": Array [ - Object { - "data-test-subj": "gridCopyColumnNameToClipBoardButton", - "iconProps": Object { - "size": "m", - }, - "iconType": "copyClipboard", - "label": , - "onClick": [Function], - "size": "xs", - }, - Object { - "data-test-subj": "gridCopyColumnValuesToClipBoardButton", - "iconProps": Object { - "size": "m", - }, - "iconType": "copyClipboard", - "label": , - "onClick": [Function], - "size": "xs", - }, - ], - "showHide": false, - "showMoveLeft": false, - "showMoveRight": false, - }, - "cellActions": Array [ - [Function], - ], - "display": undefined, - "displayAsText": "message", - "id": "message", - "isSortable": false, - "schema": "string", + "schema": "numeric", "visibleCellActions": undefined, }, -] -`; - -exports[`Data table columns getEuiGridColumns returns eui grid columns with time column 1`] = ` -Array [ Object { "actions": Object { "additional": Array [ @@ -1748,16 +1698,25 @@ Array [ "size": "xs", }, ], - "showHide": false, + "showHide": Object { + "iconType": "cross", + "label": "Remove column", + }, "showMoveLeft": true, "showMoveRight": true, }, "cellActions": Array [ [Function], - [Function], - [Function], ], - "display": , - "displayAsText": "timestamp", - "id": "timestamp", - "initialWidth": 212, - "isSortable": true, - "schema": "datetime", - "visibleCellActions": undefined, - }, - Object { - "actions": Object { - "additional": Array [ - Object { - "data-test-subj": "gridCopyColumnNameToClipBoardButton", - "iconProps": Object { - "size": "m", - }, - "iconType": "copyClipboard", - "label": , - "onClick": [Function], + Object { + "type": "return", + "value": Object { + "aggregatable": true, + "displayName": "timestamp", + "filterable": true, + "name": "timestamp", + "scripted": false, + "sortable": true, + "type": "date", + }, + }, + Object { + "type": "return", + "value": Object { + "aggregatable": true, + "displayName": "extension", + "filterable": true, + "name": "extension", + "scripted": false, + "type": "string", + }, + }, + Object { + "type": "return", + "value": Object { + "displayName": "message", + "filterable": false, + "name": "message", + "scripted": false, + "type": "string", + }, + }, + Object { + "type": "return", + "value": Object { + "aggregatable": true, + "displayName": "timestamp", + "filterable": true, + "name": "timestamp", + "scripted": false, + "sortable": true, + "type": "date", + }, + }, + Object { + "type": "return", + "value": Object { + "aggregatable": true, + "displayName": "extension", + "filterable": true, + "name": "extension", + "scripted": false, + "type": "string", + }, + }, + Object { + "type": "return", + "value": Object { + "displayName": "message", + "filterable": false, + "name": "message", + "scripted": false, + "type": "string", + }, + }, + Object { + "type": "return", + "value": Object { + "aggregatable": true, + "displayName": "extension", + "filterable": true, + "name": "extension", + "scripted": false, + "type": "string", + }, + }, + Object { + "type": "return", + "value": Object { + "displayName": "message", + "filterable": false, + "name": "message", + "scripted": false, + "type": "string", + }, + }, + ], + }, + "getFormatterForField": [MockFunction], + "getIndexPattern": [Function], + "getName": [Function], + "getSourceFiltering": [Function], + "getTimeField": [Function], + "id": "index-pattern-with-timefield-id", + "isPersisted": [Function], + "isTimeBased": [Function], + "isTimeNanosBased": [Function], + "metaFields": Array [ + "_index", + "_score", + ], + "name": "index-pattern-with-timefield", + "timeFieldName": "timestamp", + "title": "index-pattern-with-timefield-title", + "toMinimalSpec": [Function], + "toSpec": [Function], + "type": "default", + } + } + headerRowHeight={5} + showColumnTokens={true} + />, + "displayAsText": "message", + "id": "message", + "isSortable": false, + "schema": "kibana-json", + "visibleCellActions": undefined, + }, +] +`; + +exports[`Data table columns getEuiGridColumns returns eui grid columns showing default columns 1`] = ` +Array [ + Object { + "actions": Object { + "additional": Array [ + Object { + "data-test-subj": "gridCopyColumnNameToClipBoardButton", + "iconProps": Object { + "size": "m", + }, + "iconType": "copyClipboard", + "label": , + "onClick": [Function], + "size": "xs", + }, + Object { + "data-test-subj": "gridCopyColumnValuesToClipBoardButton", + "iconProps": Object { + "size": "m", + }, + "iconType": "copyClipboard", + "label": , + "onClick": [Function], + "size": "xs", + }, + ], + "showHide": false, + "showMoveLeft": false, + "showMoveRight": false, + }, + "cellActions": Array [ + [Function], + [Function], + [Function], + ], + "display": , + "displayAsText": "extension", + "id": "extension", + "isSortable": false, + "schema": "string", + "visibleCellActions": undefined, + }, + Object { + "actions": Object { + "additional": Array [ + Object { + "data-test-subj": "gridCopyColumnNameToClipBoardButton", + "iconProps": Object { + "size": "m", + }, + "iconType": "copyClipboard", + "label": , + "onClick": [Function], + "size": "xs", + }, + Object { + "data-test-subj": "gridCopyColumnValuesToClipBoardButton", + "iconProps": Object { + "size": "m", + }, + "iconType": "copyClipboard", + "label": , + "onClick": [Function], + "size": "xs", + }, + ], + "showHide": false, + "showMoveLeft": false, + "showMoveRight": false, + }, + "cellActions": Array [ + [Function], + ], + "display": , + "displayAsText": "message", + "id": "message", + "isSortable": false, + "schema": "string", + "visibleCellActions": undefined, + }, +] +`; + +exports[`Data table columns getEuiGridColumns returns eui grid columns with time column 1`] = ` +Array [ + Object { + "actions": Object { + "additional": Array [ + Object { + "data-test-subj": "gridCopyColumnNameToClipBoardButton", + "iconProps": Object { + "size": "m", + }, + "iconType": "copyClipboard", + "label": , + "onClick": [Function], + "size": "xs", + }, + Object { + "data-test-subj": "gridCopyColumnValuesToClipBoardButton", + "iconProps": Object { + "size": "m", + }, + "iconType": "copyClipboard", + "label": , + "onClick": [Function], + "size": "xs", + }, + ], + "showHide": false, + "showMoveLeft": true, + "showMoveRight": true, + }, + "cellActions": Array [ + [Function], + [Function], + [Function], + ], + "display": , + "displayAsText": "timestamp", + "id": "timestamp", + "initialWidth": 212, + "isSortable": true, + "schema": "datetime", + "visibleCellActions": undefined, + }, + Object { + "actions": Object { + "additional": Array [ + Object { + "data-test-subj": "gridCopyColumnNameToClipBoardButton", + "iconProps": Object { + "size": "m", + }, + "iconType": "copyClipboard", + "label": , + "onClick": [Function], + "size": "xs", + }, + Object { + "data-test-subj": "gridCopyColumnValuesToClipBoardButton", + "iconProps": Object { + "size": "m", + }, + "iconType": "copyClipboard", + "label": , + "onClick": [Function], + "size": "xs", + }, + ], + "showHide": Object { + "iconType": "cross", + "label": "Remove column", + }, + "showMoveLeft": true, + "showMoveRight": true, + }, + "cellActions": Array [ + [Function], + [Function], + [Function], + ], + "display": , + "displayAsText": "extension", + "id": "extension", + "isSortable": false, + "schema": "string", + "visibleCellActions": undefined, + }, + Object { + "actions": Object { + "additional": Array [ + Object { + "data-test-subj": "gridCopyColumnNameToClipBoardButton", + "iconProps": Object { + "size": "m", + }, + "iconType": "copyClipboard", + "label": , + "onClick": [Function], "size": "xs", }, Object { @@ -1965,16 +2758,170 @@ Array [ }, "cellActions": Array [ [Function], - [Function], - [Function], ], - "display": undefined, - "displayAsText": "extension", - "id": "extension", + "display": , + "displayAsText": "message", + "id": "message", "isSortable": false, "schema": "string", "visibleCellActions": undefined, }, +] +`; + +exports[`Data table columns getEuiGridColumns returns eui grid with in memory sorting 1`] = ` +Array [ Object { "actions": Object { "additional": Array [ @@ -2007,28 +2954,225 @@ Array [ "size": "xs", }, ], - "showHide": Object { - "iconType": "cross", - "label": "Remove column", - }, + "showHide": false, "showMoveLeft": true, "showMoveRight": true, }, "cellActions": Array [ [Function], + [Function], + [Function], ], - "display": undefined, - "displayAsText": "message", - "id": "message", - "isSortable": false, - "schema": "string", + "display": , + "displayAsText": "timestamp", + "id": "timestamp", + "initialWidth": 212, + "isSortable": true, + "schema": "datetime", "visibleCellActions": undefined, }, -] -`; - -exports[`Data table columns getEuiGridColumns returns eui grid with in memory sorting 1`] = ` -Array [ Object { "actions": Object { "additional": Array [ @@ -2061,7 +3205,10 @@ Array [ "size": "xs", }, ], - "showHide": false, + "showHide": Object { + "iconType": "cross", + "label": "Remove column", + }, "showMoveLeft": true, "showMoveRight": true, }, @@ -2070,7 +3217,9 @@ Array [ [Function], [Function], ], - "display": , - "displayAsText": "timestamp", - "id": "timestamp", - "initialWidth": 212, - "isSortable": true, - "schema": "datetime", - "visibleCellActions": undefined, - }, - Object { - "actions": Object { - "additional": Array [ - Object { - "data-test-subj": "gridCopyColumnNameToClipBoardButton", - "iconProps": Object { - "size": "m", - }, - "iconType": "copyClipboard", - "label": , - "onClick": [Function], - "size": "xs", - }, - Object { - "data-test-subj": "gridCopyColumnValuesToClipBoardButton", - "iconProps": Object { - "size": "m", - }, - "iconType": "copyClipboard", - "label": , - "onClick": [Function], - "size": "xs", - }, - ], - "showHide": Object { - "iconType": "cross", - "label": "Remove column", - }, - "showMoveLeft": true, - "showMoveRight": true, - }, - "cellActions": Array [ - [Function], - [Function], - [Function], - ], - "display": undefined, "displayAsText": "extension", "id": "extension", "isSortable": true, @@ -2372,7 +3459,200 @@ Array [ "cellActions": Array [ [Function], ], - "display": undefined, + "display": , "displayAsText": "message", "id": "message", "isSortable": true, diff --git a/packages/kbn-unified-data-table/src/components/column_header_truncate_container.tsx b/packages/kbn-unified-data-table/src/components/column_header_truncate_container.tsx index c82ad2b8f13f4..4b7d12fa092c7 100644 --- a/packages/kbn-unified-data-table/src/components/column_header_truncate_container.tsx +++ b/packages/kbn-unified-data-table/src/components/column_header_truncate_container.tsx @@ -12,28 +12,29 @@ import { css } from '@emotion/react'; import { euiThemeVars } from '@kbn/ui-theme'; const ColumnHeaderTruncateContainer = ({ - headerRowHeight = 1, + headerRowHeight, children, }: { headerRowHeight?: number; children: React.ReactNode; }) => { - return ( - + const headerCss = css` + overflow-wrap: anywhere; + white-space: normal; + word-break: break-all; + line-height: ${euiThemeVars.euiSize}; + text-align: left; + .euiDataGridHeaderCell--numeric & { + float: right; + } + `; + + return headerRowHeight ? ( + {children} + ) : ( +
{children}
); }; diff --git a/packages/kbn-unified-data-table/src/components/data_table.scss b/packages/kbn-unified-data-table/src/components/data_table.scss index 011c8f7499547..e098e77f558e6 100644 --- a/packages/kbn-unified-data-table/src/components/data_table.scss +++ b/packages/kbn-unified-data-table/src/components/data_table.scss @@ -38,6 +38,11 @@ .euiDataGridHeaderCell { align-items: start; + + &:not(.euiDataGridHeaderCell--controlColumn) .euiDataGridHeaderCell__button { + height: 100%; + align-items: flex-start; + } } .euiDataGrid--headerUnderline .euiDataGridHeaderCell { diff --git a/packages/kbn-unified-data-table/src/components/data_table.test.tsx b/packages/kbn-unified-data-table/src/components/data_table.test.tsx index 506cd046e9f1c..c5bf7f17b4923 100644 --- a/packages/kbn-unified-data-table/src/components/data_table.test.tsx +++ b/packages/kbn-unified-data-table/src/components/data_table.test.tsx @@ -368,12 +368,18 @@ describe('UnifiedDataTable', () => { "showColumnSelector": false, "showDisplaySelector": Object { "additionalDisplaySettings": , "allowDensity": false, "allowResetButton": false, - "allowRowHeight": true, + "allowRowHeight": false, }, "showFullScreenSelector": true, "showSortSelector": true, @@ -393,8 +399,18 @@ describe('UnifiedDataTable', () => { "additionalControls": null, "showColumnSelector": false, "showDisplaySelector": Object { + "additionalDisplaySettings": , "allowDensity": false, - "allowRowHeight": true, + "allowResetButton": false, + "allowRowHeight": false, }, "showFullScreenSelector": true, "showSortSelector": true, diff --git a/packages/kbn-unified-data-table/src/components/data_table.tsx b/packages/kbn-unified-data-table/src/components/data_table.tsx index 7df0488b904c9..ea0cad4a37bea 100644 --- a/packages/kbn-unified-data-table/src/components/data_table.tsx +++ b/packages/kbn-unified-data-table/src/components/data_table.tsx @@ -72,10 +72,12 @@ import { useRowHeightsOptions } from '../hooks/use_row_heights_options'; import { DEFAULT_ROWS_PER_PAGE, GRID_STYLE, + ROWS_HEIGHT_OPTIONS, toolbarVisibility as toolbarVisibilityDefaults, } from '../constants'; import { UnifiedDataTableFooter } from './data_table_footer'; import { UnifiedDataTableAdditionalDisplaySettings } from './data_table_additional_display_settings'; +import { useRowHeight } from '../hooks/use_row_height'; export interface UnifiedDataTableRenderCustomToolbarProps { toolbarProps: EuiDataGridCustomToolbarProps; @@ -129,10 +131,18 @@ export interface UnifiedDataTableProps { * Field tokens could be rendered in column header next to the field name. */ showColumnTokens?: boolean; + /** + * Optional value for providing configuration setting for UnifiedDataTable header row height + */ + configHeaderRowHeight?: number; /** * Determines number of rows of a column header */ - headerRowHeight?: number; + headerRowHeightState?: number; + /** + * Update header row height state + */ + onUpdateHeaderRowHeight?: (headerRowHeight: number) => void; /** * If set, the given document is displayed in a flyout */ @@ -209,6 +219,10 @@ export interface UnifiedDataTableProps { * List of used control columns (available: 'openDetails', 'select') */ controlColumnIds?: string[]; + /** + * Optional value for providing configuration setting for UnifiedDataTable row height + */ + configRowHeight?: number; /** * Row height from state */ @@ -270,10 +284,6 @@ export interface UnifiedDataTableProps { displayedColumns: string[], columnTypes?: DataTableColumnTypes ) => JSX.Element | undefined; - /** - * Optional value for providing configuration setting for UnifiedDataTable rows height - */ - configRowHeight?: number; /** * Optional value for providing configuration setting for enabling to display the complex fields in the table. Default is true. */ @@ -367,7 +377,9 @@ export const UnifiedDataTable = ({ columns, columnTypes, showColumnTokens, - headerRowHeight, + configHeaderRowHeight, + headerRowHeightState, + onUpdateHeaderRowHeight, controlColumnIds = CONTROL_COLUMN_IDS_DEFAULT, dataView, loadingState, @@ -658,6 +670,29 @@ export const UnifiedDataTable = ({ dataGridRef, }); + const { + rowHeight: headerRowHeight, + rowHeightLines: headerRowHeightLines, + onChangeRowHeight: onChangeHeaderRowHeight, + onChangeRowHeightLines: onChangeHeaderRowHeightLines, + } = useRowHeight({ + storage, + consumer, + key: 'dataGridHeaderRowHeight', + configRowHeight: configHeaderRowHeight ?? 1, + rowHeightState: headerRowHeightState, + onUpdateRowHeight: onUpdateHeaderRowHeight, + }); + + const { rowHeight, rowHeightLines, onChangeRowHeight, onChangeRowHeightLines } = useRowHeight({ + storage, + consumer, + key: 'dataGridRowHeight', + configRowHeight: configRowHeight ?? ROWS_HEIGHT_OPTIONS.default, + rowHeightState, + onUpdateRowHeight, + }); + const euiGridColumns = useMemo( () => getEuiGridColumns({ @@ -680,29 +715,29 @@ export const UnifiedDataTable = ({ visibleCellActions, columnTypes, showColumnTokens, - headerRowHeight, + headerRowHeightLines, customGridColumnsConfiguration, }), [ - onFilter, - visibleColumns, + columnTypes, columnsCellActions, - displayedRows, + customGridColumnsConfiguration, dataView, - settings, + dataViewFieldEditor, defaultColumns, - isSortEnabled, + displayedRows.length, + editField, + headerRowHeightLines, isPlainRecord, - uiSettings, + isSortEnabled, + onFilter, + settings, + showColumnTokens, toastNotifications, - dataViewFieldEditor, + uiSettings, valueToStringConverter, - editField, visibleCellActions, - columnTypes, - showColumnTokens, - headerRowHeight, - customGridColumnsConfiguration, + visibleColumns, ] ); @@ -819,13 +854,21 @@ export const UnifiedDataTable = ({ if (onUpdateRowHeight) { options.allowDensity = false; - options.allowRowHeight = true; } - if (onUpdateSampleSize) { + if (onUpdateRowHeight || onUpdateHeaderRowHeight || onUpdateSampleSize) { + options.allowRowHeight = false; options.allowResetButton = false; options.additionalDisplaySettings = ( { return isPlainRecord && columns.length @@ -864,11 +921,7 @@ export const UnifiedDataTable = ({ ); const rowHeightsOptions = useRowHeightsOptions({ - rowHeightState, - onUpdateRowHeight, - storage, - configRowHeight, - consumer, + rowHeightLines, rowLineHeight: rowLineHeightOverride, }); diff --git a/packages/kbn-unified-data-table/src/components/data_table_additional_display_settings.test.tsx b/packages/kbn-unified-data-table/src/components/data_table_additional_display_settings.test.tsx index 79e887c8c670d..f6b3bfce3b762 100644 --- a/packages/kbn-unified-data-table/src/components/data_table_additional_display_settings.test.tsx +++ b/packages/kbn-unified-data-table/src/components/data_table_additional_display_settings.test.tsx @@ -10,11 +10,31 @@ import React from 'react'; import { mountWithIntl } from '@kbn/test-jest-helpers'; import { act } from 'react-dom/test-utils'; import { findTestSubject } from '@elastic/eui/lib/test'; -import { UnifiedDataTableAdditionalDisplaySettings } from './data_table_additional_display_settings'; +import { + UnifiedDataTableAdditionalDisplaySettings, + UnifiedDataTableAdditionalDisplaySettingsProps, +} from './data_table_additional_display_settings'; import lodash from 'lodash'; +import { fireEvent, render, screen } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; jest.spyOn(lodash, 'debounce').mockImplementation((fn: any) => fn); +const renderDisplaySettings = ( + props: Partial = {} +) => { + return render( + + ); +}; + describe('UnifiedDataTableAdditionalDisplaySettings', function () { describe('sampleSize', function () { it('should work correctly', async () => { @@ -24,6 +44,10 @@ describe('UnifiedDataTableAdditionalDisplaySettings', function () { ); const input = findTestSubject(component, 'unifiedDataTableSampleSizeInput').last(); @@ -56,6 +80,10 @@ describe('UnifiedDataTableAdditionalDisplaySettings', function () { maxAllowedSampleSize={500} sampleSize={50} onChangeSampleSize={onChangeSampleSizeMock} + rowHeight="custom" + rowHeightLines={10} + headerRowHeight="custom" + headerRowHeightLines={5} /> ); const input = findTestSubject(component, 'unifiedDataTableSampleSizeInput').last(); @@ -86,6 +114,10 @@ describe('UnifiedDataTableAdditionalDisplaySettings', function () { ); @@ -108,4 +140,62 @@ describe('UnifiedDataTableAdditionalDisplaySettings', function () { expect(onChangeSampleSizeMock).not.toHaveBeenCalled(); }); }); + + describe('rowHeight', () => { + it('should render rowHeight if onChangeRowHeight and onChangeRowHeightLines are defined', () => { + renderDisplaySettings({ + onChangeRowHeight: jest.fn(), + onChangeRowHeightLines: jest.fn(), + }); + expect(screen.getByLabelText('Cell row height')).toBeInTheDocument(); + }); + + it('should not render rowHeight if onChangeRowHeight and onChangeRowHeightLines are undefined', () => { + renderDisplaySettings(); + expect(screen.queryByLabelText('Cell row height')).not.toBeInTheDocument(); + }); + + it('should call onChangeRowHeight and onChangeRowHeightLines when the rowHeight changes', () => { + const onChangeRowHeight = jest.fn(); + const onChangeRowHeightLines = jest.fn(); + renderDisplaySettings({ + rowHeight: 'custom', + onChangeRowHeight, + onChangeRowHeightLines, + }); + fireEvent.change(screen.getByRole('slider', { hidden: true }), { target: { value: 5 } }); + expect(onChangeRowHeightLines).toHaveBeenCalledWith(5); + userEvent.click(screen.getByRole('button', { name: 'Auto fit' })); + expect(onChangeRowHeight).toHaveBeenCalledWith('auto'); + }); + }); + + describe('headerRowHeight', () => { + it('should render headerRowHeight if onChangeHeaderRowHeight and onChangeHeaderRowHeightLines are defined', () => { + renderDisplaySettings({ + onChangeHeaderRowHeight: jest.fn(), + onChangeHeaderRowHeightLines: jest.fn(), + }); + expect(screen.getByLabelText('Header row height')).toBeInTheDocument(); + }); + + it('should not render headerRowHeight if onChangeHeaderRowHeight and onChangeHeaderRowHeightLines are undefined', () => { + renderDisplaySettings(); + expect(screen.queryByLabelText('Header row height')).not.toBeInTheDocument(); + }); + + it('should call onChangeHeaderRowHeight and onChangeHeaderRowHeightLines when the headerRowHeight changes', () => { + const onChangeHeaderRowHeight = jest.fn(); + const onChangeHeaderRowHeightLines = jest.fn(); + renderDisplaySettings({ + headerRowHeight: 'custom', + onChangeHeaderRowHeight, + onChangeHeaderRowHeightLines, + }); + fireEvent.change(screen.getByRole('slider', { hidden: true }), { target: { value: 3 } }); + expect(onChangeHeaderRowHeightLines).toHaveBeenCalledWith(3); + userEvent.click(screen.getByRole('button', { name: 'Auto fit' })); + expect(onChangeHeaderRowHeight).toHaveBeenCalledWith('auto'); + }); + }); }); diff --git a/packages/kbn-unified-data-table/src/components/data_table_additional_display_settings.tsx b/packages/kbn-unified-data-table/src/components/data_table_additional_display_settings.tsx index 2555c5f253929..164ab402dd46d 100644 --- a/packages/kbn-unified-data-table/src/components/data_table_additional_display_settings.tsx +++ b/packages/kbn-unified-data-table/src/components/data_table_additional_display_settings.tsx @@ -7,9 +7,10 @@ */ import React, { useCallback, useEffect, useMemo, useState } from 'react'; -import { EuiFormRow, EuiRange } from '@elastic/eui'; +import { EuiFormRow, EuiHorizontalRule, EuiRange } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { debounce } from 'lodash'; +import { RowHeightSettings, RowHeightSettingsProps } from './row_height_settings'; export const DEFAULT_MAX_ALLOWED_SAMPLE_SIZE = 1000; export const MIN_ALLOWED_SAMPLE_SIZE = 1; @@ -17,14 +18,32 @@ export const RANGE_MIN_SAMPLE_SIZE = 10; // it's necessary to be able to use `st export const RANGE_STEP_SAMPLE_SIZE = 10; export interface UnifiedDataTableAdditionalDisplaySettingsProps { + rowHeight: RowHeightSettingsProps['rowHeight']; + rowHeightLines: RowHeightSettingsProps['rowHeightLines']; + onChangeRowHeight?: (rowHeight: RowHeightSettingsProps['rowHeight']) => void; + onChangeRowHeightLines?: (rowHeightLines: number) => void; + headerRowHeight: RowHeightSettingsProps['rowHeight']; + headerRowHeightLines: RowHeightSettingsProps['rowHeightLines']; + onChangeHeaderRowHeight?: (headerRowHeight: RowHeightSettingsProps['rowHeight']) => void; + onChangeHeaderRowHeightLines?: (headerRowHeightLines: number) => void; maxAllowedSampleSize?: number; sampleSize: number; - onChangeSampleSize: (sampleSize: number) => void; + onChangeSampleSize?: (sampleSize: number) => void; } +const defaultOnChangeSampleSize = () => {}; + export const UnifiedDataTableAdditionalDisplaySettings: React.FC< UnifiedDataTableAdditionalDisplaySettingsProps > = ({ + rowHeight, + rowHeightLines, + onChangeRowHeight, + onChangeRowHeightLines, + headerRowHeight, + headerRowHeightLines, + onChangeHeaderRowHeight, + onChangeHeaderRowHeightLines, maxAllowedSampleSize = DEFAULT_MAX_ALLOWED_SAMPLE_SIZE, sampleSize, onChangeSampleSize, @@ -36,7 +55,11 @@ export const UnifiedDataTableAdditionalDisplaySettings: React.FC< ); // flexible: allows to go lower than RANGE_MIN_SAMPLE_SIZE but greater than MIN_ALLOWED_SAMPLE_SIZE const debouncedOnChangeSampleSize = useMemo( - () => debounce(onChangeSampleSize, 300, { leading: false, trailing: true }), + () => + debounce(onChangeSampleSize ?? defaultOnChangeSampleSize, 300, { + leading: false, + trailing: true, + }), [onChangeSampleSize] ); @@ -68,18 +91,53 @@ export const UnifiedDataTableAdditionalDisplaySettings: React.FC< }, [sampleSize, setActiveSampleSize]); return ( - - - + <> + {onChangeHeaderRowHeight && onChangeHeaderRowHeightLines && ( + + )} + {onChangeRowHeight && onChangeRowHeightLines && ( + <> + + + + )} + {onChangeSampleSize && ( + <> + + + + + + )} + ); }; diff --git a/packages/kbn-unified-data-table/src/components/data_table_column_header.tsx b/packages/kbn-unified-data-table/src/components/data_table_column_header.tsx index 2dd048525a32f..0505639bf3c28 100644 --- a/packages/kbn-unified-data-table/src/components/data_table_column_header.tsx +++ b/packages/kbn-unified-data-table/src/components/data_table_column_header.tsx @@ -26,16 +26,14 @@ interface DataTableColumnHeaderProps { showColumnTokens?: boolean; } -export const DataTableColumnHeader: React.FC = (props) => { - const { - columnDisplayName, - showColumnTokens, - columnName, - columnTypes, - dataView, - headerRowHeight = 1, - } = props; - +export const DataTableColumnHeader: React.FC = ({ + columnDisplayName, + showColumnTokens, + columnName, + columnTypes, + dataView, + headerRowHeight, +}) => { return ( {showColumnTokens && ( diff --git a/packages/kbn-unified-data-table/src/components/data_table_columns.test.tsx b/packages/kbn-unified-data-table/src/components/data_table_columns.test.tsx index 31636b7027442..8bca26f164d11 100644 --- a/packages/kbn-unified-data-table/src/components/data_table_columns.test.tsx +++ b/packages/kbn-unified-data-table/src/components/data_table_columns.test.tsx @@ -10,6 +10,7 @@ import { dataViewMock } from '@kbn/discover-utils/src/__mocks__'; import type { DataView } from '@kbn/data-views-plugin/public'; import React from 'react'; import { + deserializeHeaderRowHeight, getEuiGridColumns, getVisibleColumns, hasSourceTimeFieldValue, @@ -18,6 +19,7 @@ import { dataViewWithTimefieldMock } from '../../__mocks__/data_view_with_timefi import { dataViewWithoutTimefieldMock } from '../../__mocks__/data_view_without_timefield'; import { dataTableContextMock } from '../../__mocks__/table_context'; import { servicesMock } from '../../__mocks__/services'; +import { ROWS_HEIGHT_OPTIONS } from '../constants'; const columns = ['extension', 'message']; const columnsWithTimeCol = getVisibleColumns( @@ -38,6 +40,7 @@ describe('Data table columns', function () { isPlainRecord: false, valueToStringConverter: dataTableContextMock.valueToStringConverter, rowsCount: 100, + headerRowHeightLines: 5, services: { uiSettings: servicesMock.uiSettings, toastNotifications: servicesMock.toastNotifications, @@ -59,6 +62,7 @@ describe('Data table columns', function () { isPlainRecord: false, valueToStringConverter: dataTableContextMock.valueToStringConverter, rowsCount: 100, + headerRowHeightLines: 5, services: { uiSettings: servicesMock.uiSettings, toastNotifications: servicesMock.toastNotifications, @@ -80,6 +84,7 @@ describe('Data table columns', function () { isPlainRecord: true, valueToStringConverter: dataTableContextMock.valueToStringConverter, rowsCount: 100, + headerRowHeightLines: 5, services: { uiSettings: servicesMock.uiSettings, toastNotifications: servicesMock.toastNotifications, @@ -256,6 +261,7 @@ describe('Data table columns', function () { isPlainRecord: false, valueToStringConverter: dataTableContextMock.valueToStringConverter, rowsCount: 100, + headerRowHeightLines: 5, services: { uiSettings: servicesMock.uiSettings, toastNotifications: servicesMock.toastNotifications, @@ -282,6 +288,7 @@ describe('Data table columns', function () { isPlainRecord: false, valueToStringConverter: dataTableContextMock.valueToStringConverter, rowsCount: 100, + headerRowHeightLines: 5, services: { uiSettings: servicesMock.uiSettings, toastNotifications: servicesMock.toastNotifications, @@ -310,6 +317,7 @@ describe('Data table columns', function () { isPlainRecord: true, valueToStringConverter: dataTableContextMock.valueToStringConverter, rowsCount: 100, + headerRowHeightLines: 5, services: { uiSettings: servicesMock.uiSettings, toastNotifications: servicesMock.toastNotifications, @@ -339,6 +347,7 @@ describe('Data table columns', function () { isPlainRecord: true, valueToStringConverter: dataTableContextMock.valueToStringConverter, rowsCount: 100, + headerRowHeightLines: 5, services: { uiSettings: servicesMock.uiSettings, toastNotifications: servicesMock.toastNotifications, @@ -363,6 +372,7 @@ describe('Data table columns', function () { isPlainRecord: true, valueToStringConverter: dataTableContextMock.valueToStringConverter, rowsCount: 100, + headerRowHeightLines: 5, services: { uiSettings: servicesMock.uiSettings, toastNotifications: servicesMock.toastNotifications, @@ -387,6 +397,7 @@ describe('Data table columns', function () { isPlainRecord: true, valueToStringConverter: dataTableContextMock.valueToStringConverter, rowsCount: 100, + headerRowHeightLines: 5, services: { uiSettings: servicesMock.uiSettings, toastNotifications: servicesMock.toastNotifications, @@ -400,4 +411,18 @@ describe('Data table columns', function () { expect(customizedGridColumns).toMatchSnapshot(); }); }); + + describe('deserializeHeaderRowHeight', () => { + it('returns undefined for auto', () => { + expect(deserializeHeaderRowHeight(ROWS_HEIGHT_OPTIONS.auto)).toBe(undefined); + }); + + it('returns 1 for single', () => { + expect(deserializeHeaderRowHeight(ROWS_HEIGHT_OPTIONS.single)).toBe(1); + }); + + it('returns the value for other values', () => { + expect(deserializeHeaderRowHeight(2)).toBe(2); + }); + }); }); diff --git a/packages/kbn-unified-data-table/src/components/data_table_columns.tsx b/packages/kbn-unified-data-table/src/components/data_table_columns.tsx index 4c4208e30ec14..d947cbf373846 100644 --- a/packages/kbn-unified-data-table/src/components/data_table_columns.tsx +++ b/packages/kbn-unified-data-table/src/components/data_table_columns.tsx @@ -22,7 +22,7 @@ import type { ValueToStringConverter, DataTableColumnTypes } from '../types'; import { buildCellActions } from './default_cell_actions'; import { getSchemaByKbnType } from './data_table_schema'; import { SelectButton } from './data_table_document_selection'; -import { defaultTimeColumnWidth } from '../constants'; +import { defaultTimeColumnWidth, ROWS_HEIGHT_OPTIONS } from '../constants'; import { buildCopyColumnNameButton, buildCopyColumnValuesButton } from './build_copy_column_button'; import { buildEditFieldButton } from './build_edit_field_button'; import { DataTableColumnHeader, DataTableTimeColumnHeader } from './data_table_column_header'; @@ -145,7 +145,7 @@ function buildEuiGridColumn({ schema: getSchemaByKbnType(columnType), isSortable: isSortEnabled && (isPlainRecord || dataViewField?.sortable === true), display: - showColumnTokens || (headerRowHeight && headerRowHeight !== 1) ? ( + showColumnTokens || headerRowHeight !== 1 ? ( { + if (headerRowHeightLines === ROWS_HEIGHT_OPTIONS.auto) { + return undefined; + } else if (headerRowHeightLines === ROWS_HEIGHT_OPTIONS.single) { + return 1; + } + + return headerRowHeightLines; +}; + export function getEuiGridColumns({ columns, columnsCellActions, @@ -229,7 +239,7 @@ export function getEuiGridColumns({ visibleCellActions, columnTypes, showColumnTokens, - headerRowHeight, + headerRowHeightLines, customGridColumnsConfiguration, }: { columns: string[]; @@ -251,10 +261,11 @@ export function getEuiGridColumns({ visibleCellActions?: number; columnTypes?: DataTableColumnTypes; showColumnTokens?: boolean; - headerRowHeight?: number; + headerRowHeightLines: number; customGridColumnsConfiguration?: CustomGridColumnsConfiguration; }) { const getColWidth = (column: string) => settings?.columns?.[column]?.width ?? 0; + const headerRowHeight = deserializeHeaderRowHeight(headerRowHeightLines); return columns.map((column, columnIndex) => buildEuiGridColumn({ diff --git a/packages/kbn-unified-data-table/src/components/row_height_settings.test.tsx b/packages/kbn-unified-data-table/src/components/row_height_settings.test.tsx new file mode 100644 index 0000000000000..16797aaaa375f --- /dev/null +++ b/packages/kbn-unified-data-table/src/components/row_height_settings.test.tsx @@ -0,0 +1,75 @@ +/* + * 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 { render, screen, fireEvent } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +import React, { useState } from 'react'; +import { RowHeightSettings, RowHeightSettingsProps } from '../..'; + +const renderRowHeightSettings = ({ maxRowHeight }: { maxRowHeight?: number } = {}) => { + const Wrapper = () => { + const [rowHeight, setRowHeight] = useState(); + const [rowHeightLines, setRowHeightLines] = useState(); + + return ( + + ); + }; + + return render(); +}; + +describe('RowHeightSettings', () => { + it('should set rowHeight when the selected button changes', () => { + renderRowHeightSettings(); + expect(screen.getByRole('button', { name: 'Single', pressed: true })).toBeInTheDocument(); + expect(screen.getByRole('button', { name: 'Auto fit', pressed: false })).toBeInTheDocument(); + expect(screen.getByRole('button', { name: 'Custom', pressed: false })).toBeInTheDocument(); + userEvent.click(screen.getByRole('button', { name: 'Auto fit' })); + expect(screen.getByRole('button', { name: 'Single', pressed: false })).toBeInTheDocument(); + expect(screen.getByRole('button', { name: 'Auto fit', pressed: true })).toBeInTheDocument(); + expect(screen.getByRole('button', { name: 'Custom', pressed: false })).toBeInTheDocument(); + userEvent.click(screen.getByRole('button', { name: 'Custom' })); + expect(screen.getByRole('button', { name: 'Single', pressed: false })).toBeInTheDocument(); + expect(screen.getByRole('button', { name: 'Auto fit', pressed: false })).toBeInTheDocument(); + expect(screen.getByRole('button', { name: 'Custom', pressed: true })).toBeInTheDocument(); + }); + + it('should show the range input when Custom is selected', () => { + renderRowHeightSettings(); + expect(screen.queryByRole('slider', { hidden: true })).not.toBeInTheDocument(); + userEvent.click(screen.getByRole('button', { name: 'Custom' })); + expect(screen.getByRole('slider', { hidden: true })).toBeInTheDocument(); + }); + + it('should set rowHeightLines when the range input changes', () => { + renderRowHeightSettings(); + userEvent.click(screen.getByRole('button', { name: 'Custom' })); + const slider = screen.getByRole('slider', { hidden: true }); + expect(slider).toHaveValue('2'); + fireEvent.change(slider, { target: { value: 10 } }); + expect(slider).toHaveValue('10'); + }); + + it('should limit the range input to the maxRowHeight', () => { + renderRowHeightSettings({ maxRowHeight: 5 }); + userEvent.click(screen.getByRole('button', { name: 'Custom' })); + const slider = screen.getByRole('slider', { hidden: true }); + expect(slider).toHaveValue('2'); + fireEvent.change(slider, { target: { value: 10 } }); + expect(slider).toHaveValue('5'); + }); +}); diff --git a/packages/kbn-unified-data-table/src/components/row_height_settings.tsx b/packages/kbn-unified-data-table/src/components/row_height_settings.tsx new file mode 100644 index 0000000000000..e903885fec71d --- /dev/null +++ b/packages/kbn-unified-data-table/src/components/row_height_settings.tsx @@ -0,0 +1,100 @@ +/* + * 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 { i18n } from '@kbn/i18n'; +import { EuiButtonGroup, EuiFormRow, EuiRange, htmlIdGenerator } from '@elastic/eui'; +import { euiThemeVars } from '@kbn/ui-theme'; + +export interface RowHeightSettingsProps { + rowHeight?: 'auto' | 'single' | 'custom'; + rowHeightLines?: number; + maxRowHeight?: number; + label: string; + compressed?: boolean; + onChangeRowHeight: (newHeightMode: 'auto' | 'single' | 'custom' | undefined) => void; + onChangeRowHeightLines: (newRowHeightLines: number) => void; + 'data-test-subj'?: string; +} + +const idPrefix = htmlIdGenerator()(); + +export function RowHeightSettings({ + label, + rowHeight, + rowHeightLines, + compressed, + onChangeRowHeight, + onChangeRowHeightLines, + maxRowHeight, + ['data-test-subj']: dataTestSubj, +}: RowHeightSettingsProps) { + const rowHeightModeOptions = [ + { + id: `${idPrefix}single`, + label: i18n.translate('unifiedDataTable.rowHeight.single', { + defaultMessage: 'Single', + }), + 'data-test-subj': `${dataTestSubj}_rowHeight_single`, + }, + { + id: `${idPrefix}auto`, + label: i18n.translate('unifiedDataTable.rowHeight.auto', { + defaultMessage: 'Auto fit', + }), + 'data-test-subj': `${dataTestSubj}_rowHeight_auto`, + }, + { + id: `${idPrefix}custom`, + label: i18n.translate('unifiedDataTable.rowHeight.custom', { + defaultMessage: 'Custom', + }), + 'data-test-subj': `${dataTestSubj}_rowHeight_custom`, + }, + ]; + + return ( + <> + + <> + { + const newMode = optionId.replace(idPrefix, '') as RowHeightSettingsProps['rowHeight']; + onChangeRowHeight(newMode); + }} + data-test-subj={`${dataTestSubj}_rowHeightButtonGroup`} + /> + {rowHeight === 'custom' ? ( + { + const lineCount = Number(e.currentTarget.value); + onChangeRowHeightLines(lineCount); + }} + data-test-subj={`${dataTestSubj}_lineCountNumber`} + css={{ + marginTop: compressed ? euiThemeVars.euiSizeXS : euiThemeVars.euiSizeM, + }} + /> + ) : null} + + + + ); +} diff --git a/packages/kbn-unified-data-table/src/constants.ts b/packages/kbn-unified-data-table/src/constants.ts index c4c236c828fc1..c2d5654c602c2 100644 --- a/packages/kbn-unified-data-table/src/constants.ts +++ b/packages/kbn-unified-data-table/src/constants.ts @@ -21,7 +21,7 @@ export const ROWS_HEIGHT_OPTIONS = { auto: -1, single: 0, default: 3, -}; +} as const; export const defaultRowLineHeight = '1.6em'; export const defaultMonacoEditorWidth = 370; export const defaultTimeColumnWidth = 212; diff --git a/packages/kbn-unified-data-table/src/hooks/use_row_height.test.tsx b/packages/kbn-unified-data-table/src/hooks/use_row_height.test.tsx new file mode 100644 index 0000000000000..0a7d88791377e --- /dev/null +++ b/packages/kbn-unified-data-table/src/hooks/use_row_height.test.tsx @@ -0,0 +1,153 @@ +/* + * 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 { renderHook } from '@testing-library/react-hooks'; +import { createLocalStorageMock } from '../../__mocks__/local_storage_mock'; +import { useRowHeight } from './use_row_height'; + +const CONFIG_ROW_HEIGHT = 3; + +const renderRowHeightHook = ( + { + previousRowHeight, + previousConfigRowHeight, + rowHeightState, + onUpdateRowHeight, + }: { + previousRowHeight?: number; + previousConfigRowHeight?: number; + rowHeightState?: number; + onUpdateRowHeight?: (rowHeight: number) => void; + } = { previousRowHeight: 5, previousConfigRowHeight: CONFIG_ROW_HEIGHT } +) => { + const storageValue = + previousRowHeight && previousConfigRowHeight + ? { ['discover:dataGridRowHeight']: { previousRowHeight, previousConfigRowHeight } } + : {}; + const storage = createLocalStorageMock(storageValue); + const initialProps = { + storage, + consumer: 'discover', + key: 'dataGridRowHeight', + configRowHeight: CONFIG_ROW_HEIGHT, + rowHeightState, + onUpdateRowHeight, + }; + + return { + storage, + initialProps, + hook: renderHook(useRowHeight, { initialProps }), + }; +}; + +describe('useRowHeightsOptions', () => { + it('should apply rowHeight from rowHeightState', () => { + const { + hook: { result }, + } = renderRowHeightHook({ rowHeightState: 2 }); + expect(result.current.rowHeight).toEqual('custom'); + expect(result.current.rowHeightLines).toEqual(2); + }); + + it('should apply rowHeight from local storage', () => { + const { + hook: { result }, + } = renderRowHeightHook(); + expect(result.current.rowHeight).toEqual('custom'); + expect(result.current.rowHeightLines).toEqual(5); + }); + + it('should apply rowHeight from configRowHeight', () => { + const { + hook: { result }, + } = renderRowHeightHook({ + previousRowHeight: undefined, + previousConfigRowHeight: undefined, + }); + expect(result.current.rowHeight).toEqual('custom'); + expect(result.current.rowHeightLines).toEqual(CONFIG_ROW_HEIGHT); + }); + + test('should apply rowHeight from configRowHeight instead of local storage value, since configRowHeight has been changed', () => { + const { + hook: { result }, + } = renderRowHeightHook({ + previousConfigRowHeight: 4, + }); + expect(result.current.rowHeight).toEqual('custom'); + expect(result.current.rowHeightLines).toEqual(3); + }); + + it('should return onChangeRowHeight and onChangeRowHeightLines when onUpdateRowHeight is provided', () => { + const { + hook: { result }, + } = renderRowHeightHook({ onUpdateRowHeight: jest.fn() }); + expect(result.current.onChangeRowHeight).toBeDefined(); + expect(result.current.onChangeRowHeightLines).toBeDefined(); + }); + + it('should return undefined for onChangeRowHeight and onChangeRowHeightLines when onUpdateRowHeight is not provided', () => { + const { + hook: { result }, + } = renderRowHeightHook(); + expect(result.current.onChangeRowHeight).toBeUndefined(); + }); + + it('should update stored row height and call onUpdateRowHeight when onChangeRowHeight is called', () => { + const onUpdateRowHeight = jest.fn(); + const { + storage, + hook: { result }, + } = renderRowHeightHook({ onUpdateRowHeight }); + result.current.onChangeRowHeight?.('auto'); + expect(storage.get('discover:dataGridRowHeight')).toEqual({ + previousRowHeight: -1, + previousConfigRowHeight: CONFIG_ROW_HEIGHT, + }); + expect(onUpdateRowHeight).toHaveBeenLastCalledWith(-1); + result.current.onChangeRowHeight?.('single'); + expect(storage.get('discover:dataGridRowHeight')).toEqual({ + previousRowHeight: 0, + previousConfigRowHeight: CONFIG_ROW_HEIGHT, + }); + expect(onUpdateRowHeight).toHaveBeenLastCalledWith(0); + result.current.onChangeRowHeight?.('custom'); + expect(storage.get('discover:dataGridRowHeight')).toEqual({ + previousRowHeight: CONFIG_ROW_HEIGHT, + previousConfigRowHeight: CONFIG_ROW_HEIGHT, + }); + expect(onUpdateRowHeight).toHaveBeenLastCalledWith(CONFIG_ROW_HEIGHT); + }); + + it('should update stored row height and call onUpdateRowHeight when onChangeRowHeightLines is called', () => { + const onUpdateRowHeight = jest.fn(); + const { + storage, + hook: { result }, + } = renderRowHeightHook({ onUpdateRowHeight }); + result.current.onChangeRowHeightLines?.(2); + expect(storage.get('discover:dataGridRowHeight')).toEqual({ + previousRowHeight: 2, + previousConfigRowHeight: CONFIG_ROW_HEIGHT, + }); + expect(onUpdateRowHeight).toHaveBeenLastCalledWith(2); + }); + + it('should convert provided rowHeightState to rowHeight and rowHeightLines', () => { + const { hook, initialProps } = renderRowHeightHook({ rowHeightState: -1 }); + expect(hook.result.current.rowHeight).toEqual('auto'); + expect(hook.result.current.rowHeightLines).toEqual(-1); + hook.rerender({ ...initialProps, rowHeightState: 0 }); + expect(hook.result.current.rowHeight).toEqual('single'); + expect(hook.result.current.rowHeightLines).toEqual(0); + hook.rerender({ ...initialProps, rowHeightState: 3 }); + expect(hook.result.current.rowHeight).toEqual('custom'); + expect(hook.result.current.rowHeightLines).toEqual(3); + }); +}); diff --git a/packages/kbn-unified-data-table/src/hooks/use_row_height.ts b/packages/kbn-unified-data-table/src/hooks/use_row_height.ts new file mode 100644 index 0000000000000..fdfb96d3c6c09 --- /dev/null +++ b/packages/kbn-unified-data-table/src/hooks/use_row_height.ts @@ -0,0 +1,103 @@ +/* + * 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 { Storage } from '@kbn/kibana-utils-plugin/public'; +import { useCallback, useMemo } from 'react'; +import { isValidRowHeight } from '../utils/validate_row_height'; +import { + DataGridOptionsRecord, + getStoredRowHeight, + updateStoredRowHeight, +} from '../utils/row_heights'; +import { ROWS_HEIGHT_OPTIONS } from '../constants'; +import { RowHeightSettingsProps } from '../components/row_height_settings'; + +interface UseRowHeightProps { + storage: Storage; + consumer: string; + key: string; + configRowHeight: number; + rowHeightState?: number; + onUpdateRowHeight?: (rowHeight: number) => void; +} + +export const useRowHeight = ({ + storage, + consumer, + key, + configRowHeight, + rowHeightState, + onUpdateRowHeight, +}: UseRowHeightProps) => { + const rowHeightLines = useMemo(() => { + const rowHeightFromLS = getStoredRowHeight(storage, consumer, key); + + const configHasNotChanged = ( + localStorageRecord: DataGridOptionsRecord | null + ): localStorageRecord is DataGridOptionsRecord => + localStorageRecord !== null && configRowHeight === localStorageRecord.previousConfigRowHeight; + + let currentRowLines: number; + if (isValidRowHeight(rowHeightState)) { + currentRowLines = rowHeightState; + } else if (configHasNotChanged(rowHeightFromLS)) { + currentRowLines = rowHeightFromLS.previousRowHeight; + } else { + currentRowLines = configRowHeight; + } + + return currentRowLines; + }, [configRowHeight, consumer, key, rowHeightState, storage]); + + const rowHeight = useMemo(() => { + switch (rowHeightLines) { + case ROWS_HEIGHT_OPTIONS.auto: + return 'auto'; + case ROWS_HEIGHT_OPTIONS.single: + return 'single'; + default: + return 'custom'; + } + }, [rowHeightLines]); + + const onChangeRowHeight = useCallback( + (newRowHeight: RowHeightSettingsProps['rowHeight']) => { + let newRowHeightLines: number; + + switch (newRowHeight) { + case 'auto': + newRowHeightLines = ROWS_HEIGHT_OPTIONS.auto; + break; + case 'single': + newRowHeightLines = ROWS_HEIGHT_OPTIONS.single; + break; + default: + newRowHeightLines = configRowHeight; + } + + updateStoredRowHeight(newRowHeightLines, configRowHeight, storage, consumer, key); + onUpdateRowHeight?.(newRowHeightLines); + }, + [configRowHeight, consumer, key, onUpdateRowHeight, storage] + ); + + const onChangeRowHeightLines = useCallback( + (newRowHeightLines: number) => { + updateStoredRowHeight(newRowHeightLines, configRowHeight, storage, consumer, key); + onUpdateRowHeight?.(newRowHeightLines); + }, + [configRowHeight, consumer, key, onUpdateRowHeight, storage] + ); + + return { + rowHeight, + rowHeightLines, + onChangeRowHeight: onUpdateRowHeight ? onChangeRowHeight : undefined, + onChangeRowHeightLines: onUpdateRowHeight ? onChangeRowHeightLines : undefined, + }; +}; diff --git a/packages/kbn-unified-data-table/src/hooks/use_row_heights_options.test.ts b/packages/kbn-unified-data-table/src/hooks/use_row_heights_options.test.ts new file mode 100644 index 0000000000000..c2db382068339 --- /dev/null +++ b/packages/kbn-unified-data-table/src/hooks/use_row_heights_options.test.ts @@ -0,0 +1,49 @@ +/* + * 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 { renderHook } from '@testing-library/react-hooks'; +import { useRowHeightsOptions } from './use_row_heights_options'; + +describe('useRowHeightsOptions', () => { + it('should convert rowHeightLines -1 to auto', () => { + const { result } = renderHook(() => { + return useRowHeightsOptions({ + rowHeightLines: -1, + }); + }); + expect(result.current.defaultHeight).toEqual('auto'); + }); + + it('should convert rowHeightLines 0 to undefined', () => { + const { result } = renderHook(() => { + return useRowHeightsOptions({ + rowHeightLines: 0, + }); + }); + expect(result.current.defaultHeight).toEqual(undefined); + }); + + it('should convert custom rowHeightLines to lineCount', () => { + const { result } = renderHook(() => { + return useRowHeightsOptions({ + rowHeightLines: 2, + }); + }); + expect(result.current.defaultHeight).toEqual({ lineCount: 2 }); + }); + + it('should pass through rowLineHeight', () => { + const { result } = renderHook(() => { + return useRowHeightsOptions({ + rowHeightLines: 2, + rowLineHeight: '2em', + }); + }); + expect(result.current.lineHeight).toEqual('2em'); + }); +}); diff --git a/packages/kbn-unified-data-table/src/hooks/use_row_heights_options.test.tsx b/packages/kbn-unified-data-table/src/hooks/use_row_heights_options.test.tsx deleted file mode 100644 index 2da08c178720a..0000000000000 --- a/packages/kbn-unified-data-table/src/hooks/use_row_heights_options.test.tsx +++ /dev/null @@ -1,77 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { renderHook } from '@testing-library/react-hooks'; -import { Storage } from '@kbn/kibana-utils-plugin/public'; -import { LocalStorageMock } from '../../__mocks__/local_storage_mock'; -import { useRowHeightsOptions } from './use_row_heights_options'; - -const CONFIG_ROW_HEIGHT = 3; - -describe('useRowHeightsOptions', () => { - test('should apply rowHeight from savedSearch', () => { - const { result } = renderHook(() => { - return useRowHeightsOptions({ - rowHeightState: 2, - storage: new LocalStorageMock({}) as unknown as Storage, - consumer: 'discover', - }); - }); - - expect(result.current.defaultHeight).toEqual({ lineCount: 2 }); - }); - - test('should apply rowHeight from local storage', () => { - const { result } = renderHook(() => { - return useRowHeightsOptions({ - storage: new LocalStorageMock({ - ['discover:dataGridRowHeight']: { - previousRowHeight: 5, - previousConfigRowHeight: 3, - }, - }) as unknown as Storage, - consumer: 'discover', - }); - }); - - expect(result.current.defaultHeight).toEqual({ lineCount: 5 }); - }); - - test('should apply rowHeight from configRowHeight', () => { - const { result } = renderHook(() => { - return useRowHeightsOptions({ - consumer: 'discover', - configRowHeight: 3, - storage: new LocalStorageMock({}) as unknown as Storage, - }); - }); - - expect(result.current.defaultHeight).toEqual({ - lineCount: CONFIG_ROW_HEIGHT, - }); - }); - - test('should apply rowHeight from uiSettings instead of local storage value, since uiSettings has been changed', () => { - const { result } = renderHook(() => { - return useRowHeightsOptions({ - storage: new LocalStorageMock({ - ['discover:dataGridRowHeight']: { - previousRowHeight: 4, - // different from uiSettings (config), now user changed it to 3, but prev was 4 - previousConfigRowHeight: 4, - }, - }) as unknown as Storage, - consumer: 'discover', - }); - }); - - expect(result.current.defaultHeight).toEqual({ - lineCount: CONFIG_ROW_HEIGHT, - }); - }); -}); diff --git a/packages/kbn-unified-data-table/src/hooks/use_row_heights_options.ts b/packages/kbn-unified-data-table/src/hooks/use_row_heights_options.ts index 542641e2ece7a..73fa448b4c729 100644 --- a/packages/kbn-unified-data-table/src/hooks/use_row_heights_options.ts +++ b/packages/kbn-unified-data-table/src/hooks/use_row_heights_options.ts @@ -7,40 +7,14 @@ */ import type { EuiDataGridRowHeightOption, EuiDataGridRowHeightsOptions } from '@elastic/eui'; -import type { Storage } from '@kbn/kibana-utils-plugin/public'; import { useMemo } from 'react'; -import { isValidRowHeight } from '../utils/validate_row_height'; -import { - DataGridOptionsRecord, - getStoredRowHeight, - updateStoredRowHeight, -} from '../utils/row_heights'; import { defaultRowLineHeight, ROWS_HEIGHT_OPTIONS } from '../constants'; interface UseRowHeightProps { - rowHeightState?: number; - onUpdateRowHeight?: (rowHeight: number) => void; - storage: Storage; - configRowHeight?: number; - consumer: string; + rowHeightLines: number; rowLineHeight?: string; } -/** - * Converts rowHeight of EuiDataGrid to rowHeight number (-1 to 20) - */ -const serializeRowHeight = (rowHeight?: EuiDataGridRowHeightOption): number => { - if (rowHeight === 'auto' || rowHeight === ROWS_HEIGHT_OPTIONS.auto) { - return ROWS_HEIGHT_OPTIONS.auto; - } else if (typeof rowHeight === 'object' && rowHeight.lineCount) { - return rowHeight.lineCount; // custom - } else if (typeof rowHeight === 'number') { - return rowHeight; - } - - return ROWS_HEIGHT_OPTIONS.single; -}; - /** * Converts rowHeight number (-1 to 20) of EuiDataGrid rowHeight */ @@ -55,47 +29,15 @@ const deserializeRowHeight = (number: number): EuiDataGridRowHeightOption | unde }; export const useRowHeightsOptions = ({ - rowHeightState, - onUpdateRowHeight, - storage, - configRowHeight = ROWS_HEIGHT_OPTIONS.default, - consumer, + rowHeightLines, rowLineHeight = defaultRowLineHeight, }: UseRowHeightProps) => { return useMemo((): EuiDataGridRowHeightsOptions => { - const rowHeightFromLS = getStoredRowHeight(storage, consumer); - - const configHasNotChanged = ( - localStorageRecord: DataGridOptionsRecord | null - ): localStorageRecord is DataGridOptionsRecord => - localStorageRecord !== null && configRowHeight === localStorageRecord.previousConfigRowHeight; - - let rowHeight; - if (isValidRowHeight(rowHeightState)) { - rowHeight = rowHeightState; - } else if (configHasNotChanged(rowHeightFromLS)) { - rowHeight = rowHeightFromLS.previousRowHeight; - } else { - rowHeight = configRowHeight; - } - - const defaultHeight = deserializeRowHeight(rowHeight); + const defaultHeight = deserializeRowHeight(rowHeightLines); return { defaultHeight, lineHeight: rowLineHeight, - onChange: ({ defaultHeight: newRowHeight }: EuiDataGridRowHeightsOptions) => { - if (newRowHeight === defaultHeight && typeof rowHeightState === 'undefined') { - // ignore, no changes required - return; - } - const newSerializedRowHeight = serializeRowHeight( - // pressing "Reset to default" triggers onChange with the same value - newRowHeight === defaultHeight ? configRowHeight : newRowHeight - ); - updateStoredRowHeight(newSerializedRowHeight, configRowHeight, storage, consumer); - onUpdateRowHeight?.(newSerializedRowHeight); - }, }; - }, [storage, consumer, rowHeightState, rowLineHeight, configRowHeight, onUpdateRowHeight]); + }, [rowHeightLines, rowLineHeight]); }; diff --git a/packages/kbn-unified-data-table/src/utils/row_heights.ts b/packages/kbn-unified-data-table/src/utils/row_heights.ts index 45f472286d030..795aad63445b6 100644 --- a/packages/kbn-unified-data-table/src/utils/row_heights.ts +++ b/packages/kbn-unified-data-table/src/utils/row_heights.ts @@ -14,13 +14,14 @@ export interface DataGridOptionsRecord { previousConfigRowHeight: number; } -const getRowHeightKey = (consumer: string) => `${consumer}:dataGridRowHeight`; +const getRowHeightKey = (consumer: string, key: string) => `${consumer}:${key}`; export const getStoredRowHeight = ( storage: Storage, - consumer: string + consumer: string, + key: string ): DataGridOptionsRecord | null => { - const entry = storage.get(getRowHeightKey(consumer)); + const entry = storage.get(getRowHeightKey(consumer, key)); if ( typeof entry === 'object' && entry !== null && @@ -36,9 +37,10 @@ export const updateStoredRowHeight = ( newRowHeight: number, configRowHeight: number, storage: Storage, - consumer: string + consumer: string, + key: string ) => { - storage.set(getRowHeightKey(consumer), { + storage.set(getRowHeightKey(consumer, key), { previousRowHeight: newRowHeight, previousConfigRowHeight: configRowHeight, }); diff --git a/src/core/server/integration_tests/ci_checks/saved_objects/check_registered_types.test.ts b/src/core/server/integration_tests/ci_checks/saved_objects/check_registered_types.test.ts index 5f1c48c0391ff..536bb87f77573 100644 --- a/src/core/server/integration_tests/ci_checks/saved_objects/check_registered_types.test.ts +++ b/src/core/server/integration_tests/ci_checks/saved_objects/check_registered_types.test.ts @@ -134,7 +134,7 @@ describe('checking migration metadata changes on all registered SO types', () => "risk-engine-configuration": "b105d4a3c6adce40708d729d12e5ef3c8fbd9508", "rules-settings": "892a2918ebaeba809a612b8d97cec0b07c800b5f", "sample-data-telemetry": "37441b12f5b0159c2d6d5138a494c9f440e950b5", - "search": "2c1ab8a17e6972be2fa8d3880ba2305dfd9a5a6e", + "search": "cf69e2bf8ae25c10af21887cd6effc4a9ea73064", "search-session": "b2fcd840e12a45039ada50b1355faeafa39876d1", "search-telemetry": "b568601618744720b5662946d3103e3fb75fe8ee", "security-rule": "07abb4d7e707d91675ec0495c73816394c7b521f", diff --git a/src/plugins/discover/public/application/context/context_app_content.test.tsx b/src/plugins/discover/public/application/context/context_app_content.test.tsx index f55fdd448df52..f615768a938fb 100644 --- a/src/plugins/discover/public/application/context/context_app_content.test.tsx +++ b/src/plugins/discover/public/application/context/context_app_content.test.tsx @@ -107,4 +107,10 @@ describe('ContextAppContent test', () => { expect(component.find(UnifiedDataTable).length).toBe(1); expect(findTestSubject(component, 'dscGridToolbar').exists()).toBe(true); }); + + it('should not show display options button', async () => { + const component = await mountComponent({ isLegacy: false }); + expect(findTestSubject(component, 'dscGridToolbar').exists()).toBe(true); + expect(findTestSubject(component, 'dataGridDisplaySelectorButton').exists()).toBe(false); + }); }); diff --git a/src/plugins/discover/public/application/context/context_app_content.tsx b/src/plugins/discover/public/application/context/context_app_content.tsx index a256284f3d760..fadb561aada0b 100644 --- a/src/plugins/discover/public/application/context/context_app_content.tsx +++ b/src/plugins/discover/public/application/context/context_app_content.tsx @@ -208,7 +208,7 @@ export function ContextAppContent({ maxDocFieldsDisplayed={services.uiSettings.get(MAX_DOC_FIELDS_DISPLAYED)} renderDocumentView={renderDocumentView} services={services} - headerRowHeight={3} + configHeaderRowHeight={3} />
diff --git a/src/plugins/discover/public/application/main/components/layout/discover_documents.tsx b/src/plugins/discover/public/application/main/components/layout/discover_documents.tsx index 604d2b0a27d53..c0147009f6f8e 100644 --- a/src/plugins/discover/public/application/main/components/layout/discover_documents.tsx +++ b/src/plugins/discover/public/application/main/components/layout/discover_documents.tsx @@ -112,19 +112,29 @@ function DiscoverDocumentsComponent({ const documents$ = stateContainer.dataState.data$.documents$; const savedSearch = useSavedSearchInitial(); const { dataViews, capabilities, uiSettings, uiActions } = services; - const [query, sort, rowHeight, rowsPerPage, grid, columns, index, sampleSizeState] = - useAppStateSelector((state) => { - return [ - state.query, - state.sort, - state.rowHeight, - state.rowsPerPage, - state.grid, - state.columns, - state.index, - state.sampleSize, - ]; - }); + const [ + query, + sort, + rowHeight, + headerRowHeight, + rowsPerPage, + grid, + columns, + index, + sampleSizeState, + ] = useAppStateSelector((state) => { + return [ + state.query, + state.sort, + state.rowHeight, + state.headerRowHeight, + state.rowsPerPage, + state.grid, + state.columns, + state.index, + state.sampleSize, + ]; + }); const setExpandedDoc = useCallback( (doc: DataTableRecord | undefined) => { stateContainer.internalState.transitions.setExpandedDoc(doc); @@ -212,6 +222,13 @@ function DiscoverDocumentsComponent({ [stateContainer] ); + const onUpdateHeaderRowHeight = useCallback( + (newHeaderRowHeight: number) => { + stateContainer.appState.update({ headerRowHeight: newHeaderRowHeight }); + }, + [stateContainer] + ); + const showTimeCol = useMemo( () => // for ES|QL we want to show the time column only when is on Document view @@ -407,6 +424,9 @@ function DiscoverDocumentsComponent({ onSort={!isTextBasedQuery ? onSort : undefined} onResize={onResizeDataGrid} useNewFieldsApi={useNewFieldsApi} + configHeaderRowHeight={3} + headerRowHeightState={headerRowHeight} + onUpdateHeaderRowHeight={onUpdateHeaderRowHeight} rowHeightState={rowHeight} onUpdateRowHeight={onUpdateRowHeight} isSortEnabled={isTextBasedQuery ? Boolean(currentColumns.length) : true} @@ -426,7 +446,6 @@ function DiscoverDocumentsComponent({ totalHits={totalHits} onFetchMoreRecords={onFetchMoreRecords} componentsTourSteps={TOUR_STEPS} - headerRowHeight={3} externalCustomRenderers={externalCustomRenderers} customGridColumnsConfiguration={customGridColumnsConfiguration} customControlColumnsConfiguration={customControlColumnsConfiguration} diff --git a/src/plugins/discover/public/application/main/services/discover_app_state_container.test.ts b/src/plugins/discover/public/application/main/services/discover_app_state_container.test.ts index a215635e7cde8..dab98134db317 100644 --- a/src/plugins/discover/public/application/main/services/discover_app_state_container.test.ts +++ b/src/plugins/discover/public/application/main/services/discover_app_state_container.test.ts @@ -115,6 +115,7 @@ describe('Test discover app state container', () => { interval: 'auto', query: customQuery, rowHeight: undefined, + headerRowHeight: undefined, rowsPerPage: 250, hideAggregatedPreview: true, savedQuery: undefined, @@ -150,6 +151,7 @@ describe('Test discover app state container', () => { interval: 'auto', query: defaultQuery, rowHeight: undefined, + headerRowHeight: undefined, rowsPerPage: undefined, hideAggregatedPreview: undefined, savedQuery: undefined, diff --git a/src/plugins/discover/public/application/main/services/discover_app_state_container.ts b/src/plugins/discover/public/application/main/services/discover_app_state_container.ts index e1614bf796391..7d75d76cb6f8b 100644 --- a/src/plugins/discover/public/application/main/services/discover_app_state_container.ts +++ b/src/plugins/discover/public/application/main/services/discover_app_state_container.ts @@ -131,6 +131,10 @@ export interface DiscoverAppState { * Document explorer row height option */ rowHeight?: number; + /** + * Document explorer header row height option + */ + headerRowHeight?: number; /** * Number of rows in the grid per page */ diff --git a/src/plugins/discover/public/application/main/services/discover_state.test.ts b/src/plugins/discover/public/application/main/services/discover_state.test.ts index a25f836433958..8e6990742c663 100644 --- a/src/plugins/discover/public/application/main/services/discover_state.test.ts +++ b/src/plugins/discover/public/application/main/services/discover_state.test.ts @@ -439,6 +439,7 @@ describe('Test discover state actions', () => { "columns": Array [ "default_column", ], + "headerRowHeight": undefined, "hideAggregatedPreview": undefined, "hideChart": undefined, "refreshInterval": undefined, diff --git a/src/plugins/discover/public/application/main/utils/get_state_defaults.test.ts b/src/plugins/discover/public/application/main/utils/get_state_defaults.test.ts index a659f543f9993..bc15de09a8b60 100644 --- a/src/plugins/discover/public/application/main/utils/get_state_defaults.test.ts +++ b/src/plugins/discover/public/application/main/utils/get_state_defaults.test.ts @@ -29,6 +29,7 @@ describe('getStateDefaults', () => { ], "filters": undefined, "grid": undefined, + "headerRowHeight": undefined, "hideAggregatedPreview": undefined, "hideChart": undefined, "index": "index-pattern-with-timefield-id", @@ -64,6 +65,7 @@ describe('getStateDefaults', () => { ], "filters": undefined, "grid": undefined, + "headerRowHeight": undefined, "hideAggregatedPreview": undefined, "hideChart": undefined, "index": "the-data-view-id", diff --git a/src/plugins/discover/public/application/main/utils/get_state_defaults.ts b/src/plugins/discover/public/application/main/utils/get_state_defaults.ts index 943d9b4c98cf0..2c1c9fd7d9702 100644 --- a/src/plugins/discover/public/application/main/utils/get_state_defaults.ts +++ b/src/plugins/discover/public/application/main/utils/get_state_defaults.ts @@ -69,6 +69,7 @@ export function getStateDefaults({ hideAggregatedPreview: undefined, savedQuery: undefined, rowHeight: undefined, + headerRowHeight: undefined, rowsPerPage: undefined, sampleSize: undefined, grid: undefined, @@ -83,6 +84,9 @@ export function getStateDefaults({ if (savedSearch.rowHeight !== undefined) { defaultState.rowHeight = savedSearch.rowHeight; } + if (savedSearch.headerRowHeight !== undefined) { + defaultState.headerRowHeight = savedSearch.headerRowHeight; + } if (savedSearch.viewMode) { defaultState.viewMode = getValidViewMode({ viewMode: savedSearch.viewMode, diff --git a/src/plugins/discover/public/application/main/utils/update_saved_search.ts b/src/plugins/discover/public/application/main/utils/update_saved_search.ts index ff9d5a70f39be..fe3cdac90fc97 100644 --- a/src/plugins/discover/public/application/main/utils/update_saved_search.ts +++ b/src/plugins/discover/public/application/main/utils/update_saved_search.ts @@ -62,6 +62,7 @@ export function updateSavedSearch({ } savedSearch.hideChart = state.hideChart; savedSearch.rowHeight = state.rowHeight; + savedSearch.headerRowHeight = state.headerRowHeight; savedSearch.rowsPerPage = state.rowsPerPage; savedSearch.sampleSize = state.sampleSize; diff --git a/src/plugins/discover/public/embeddable/saved_search_embeddable.test.ts b/src/plugins/discover/public/embeddable/saved_search_embeddable.test.ts index 47c6a26ba7647..f9f60bfc5049d 100644 --- a/src/plugins/discover/public/embeddable/saved_search_embeddable.test.ts +++ b/src/plugins/discover/public/embeddable/saved_search_embeddable.test.ts @@ -118,6 +118,7 @@ describe('saved search embeddable', () => { timeRange: { from: 'now-15m', to: 'now' }, columns: ['message', 'extension'], rowHeight: 30, + headerRowHeight: 5, rowsPerPage: 50, sampleSize: 250, }; @@ -191,6 +192,11 @@ describe('saved search embeddable', () => { await waitOneTick(); expect(searchProps.rowHeightState).toEqual(40); + expect(searchProps.headerRowHeightState).toEqual(5); + searchProps.onUpdateHeaderRowHeight!(3); + await waitOneTick(); + expect(searchProps.headerRowHeightState).toEqual(3); + expect(searchProps.rowsPerPageState).toEqual(50); searchProps.onUpdateRowsPerPage!(100); await waitOneTick(); diff --git a/src/plugins/discover/public/embeddable/saved_search_embeddable.tsx b/src/plugins/discover/public/embeddable/saved_search_embeddable.tsx index 9cc620d259a80..431d75e348f79 100644 --- a/src/plugins/discover/public/embeddable/saved_search_embeddable.tsx +++ b/src/plugins/discover/public/embeddable/saved_search_embeddable.tsx @@ -89,6 +89,7 @@ export type SearchProps = Partial & interceptedWarnings?: SearchResponseWarning[]; onMoveColumn?: (column: string, index: number) => void; onUpdateRowHeight?: (rowHeight?: number) => void; + onUpdateHeaderRowHeight?: (headerRowHeight?: number) => void; onUpdateRowsPerPage?: (rowsPerPage?: number) => void; onUpdateSampleSize?: (sampleSize?: number) => void; }; @@ -503,6 +504,10 @@ export class SavedSearchEmbeddable onUpdateRowHeight: (rowHeight) => { this.updateInput({ rowHeight }); }, + headerRowHeightState: this.input.headerRowHeight || savedSearch.headerRowHeight, + onUpdateHeaderRowHeight: (headerRowHeight) => { + this.updateInput({ headerRowHeight }); + }, rowsPerPageState: this.input.rowsPerPage || savedSearch.rowsPerPage, onUpdateRowsPerPage: (rowsPerPage) => { this.updateInput({ rowsPerPage }); @@ -594,7 +599,8 @@ export class SavedSearchEmbeddable searchProps.sort = this.getSort(this.input.sort || savedSearch.sort, searchProps?.dataView); searchProps.sharedItemTitle = this.panelTitleInternal; searchProps.searchTitle = this.panelTitleInternal; - searchProps.rowHeightState = this.input.rowHeight || savedSearch.rowHeight; + searchProps.rowHeightState = this.input.rowHeight ?? savedSearch.rowHeight; + searchProps.headerRowHeightState = this.input.headerRowHeight ?? savedSearch.headerRowHeight; searchProps.rowsPerPageState = this.input.rowsPerPage || savedSearch.rowsPerPage || diff --git a/src/plugins/discover/public/embeddable/saved_search_grid.tsx b/src/plugins/discover/public/embeddable/saved_search_grid.tsx index f64f000c0bf0b..9cac74d4120ce 100644 --- a/src/plugins/discover/public/embeddable/saved_search_grid.tsx +++ b/src/plugins/discover/public/embeddable/saved_search_grid.tsx @@ -101,7 +101,7 @@ export function DiscoverGridEmbeddable(props: DiscoverGridEmbeddableProps) { renderDocumentView={renderDocumentView} renderCustomToolbar={renderCustomToolbar} showColumnTokens - headerRowHeight={3} + configHeaderRowHeight={3} /> ); diff --git a/src/plugins/discover/public/embeddable/search_embeddable_factory.test.ts b/src/plugins/discover/public/embeddable/search_embeddable_factory.test.ts index 2494cc42ee1e5..c3897be27b3b3 100644 --- a/src/plugins/discover/public/embeddable/search_embeddable_factory.test.ts +++ b/src/plugins/discover/public/embeddable/search_embeddable_factory.test.ts @@ -24,6 +24,7 @@ const input = { timeRange: { from: 'now-15m', to: 'now' }, columns: ['message', 'extension'], rowHeight: 30, + headerRowHeight: 5, rowsPerPage: 50, }; diff --git a/src/plugins/saved_search/common/content_management/v1/cm_services.ts b/src/plugins/saved_search/common/content_management/v1/cm_services.ts index 0cbbe69c4bfeb..24319df7d43ac 100644 --- a/src/plugins/saved_search/common/content_management/v1/cm_services.ts +++ b/src/plugins/saved_search/common/content_management/v1/cm_services.ts @@ -46,6 +46,7 @@ const savedSearchAttributesSchema = schema.object( ), hideAggregatedPreview: schema.maybe(schema.boolean()), rowHeight: schema.maybe(schema.number()), + headerRowHeight: schema.maybe(schema.number()), hits: schema.maybe(schema.number()), timeRestore: schema.maybe(schema.boolean()), timeRange: schema.maybe( diff --git a/src/plugins/saved_search/common/saved_searches_utils.ts b/src/plugins/saved_search/common/saved_searches_utils.ts index 2d3b0fc3e7b12..b71819e96e210 100644 --- a/src/plugins/saved_search/common/saved_searches_utils.ts +++ b/src/plugins/saved_search/common/saved_searches_utils.ts @@ -27,6 +27,7 @@ export const fromSavedSearchAttributes = ( viewMode: attributes.viewMode, hideAggregatedPreview: attributes.hideAggregatedPreview, rowHeight: attributes.rowHeight, + headerRowHeight: attributes.headerRowHeight, isTextBasedQuery: attributes.isTextBasedQuery, usesAdHocDataView: attributes.usesAdHocDataView, timeRestore: attributes.timeRestore, diff --git a/src/plugins/saved_search/common/service/get_saved_searches.test.ts b/src/plugins/saved_search/common/service/get_saved_searches.test.ts index 792a46b904de8..be971f1469ade 100644 --- a/src/plugins/saved_search/common/service/get_saved_searches.test.ts +++ b/src/plugins/saved_search/common/service/get_saved_searches.test.ts @@ -90,6 +90,7 @@ describe('getSavedSearch', () => { ], "description": "description", "grid": Object {}, + "headerRowHeight": undefined, "hideAggregatedPreview": undefined, "hideChart": false, "id": "ccf1af80-2297-11ec-86e0-1155ffb9c7a7", @@ -197,6 +198,7 @@ describe('getSavedSearch', () => { ], "description": "description", "grid": Object {}, + "headerRowHeight": undefined, "hideAggregatedPreview": undefined, "hideChart": true, "id": "ccf1af80-2297-11ec-86e0-1155ffb9c7a7", diff --git a/src/plugins/saved_search/common/service/saved_searches_utils.test.ts b/src/plugins/saved_search/common/service/saved_searches_utils.test.ts index 950823e0018a0..716d9db855a02 100644 --- a/src/plugins/saved_search/common/service/saved_searches_utils.test.ts +++ b/src/plugins/saved_search/common/service/saved_searches_utils.test.ts @@ -49,6 +49,7 @@ describe('saved_searches_utils', () => { ], "description": "foo", "grid": Object {}, + "headerRowHeight": undefined, "hideAggregatedPreview": undefined, "hideChart": true, "id": "id", @@ -120,6 +121,7 @@ describe('saved_searches_utils', () => { ], "description": "description", "grid": Object {}, + "headerRowHeight": undefined, "hideAggregatedPreview": undefined, "hideChart": true, "isTextBasedQuery": true, diff --git a/src/plugins/saved_search/common/service/saved_searches_utils.ts b/src/plugins/saved_search/common/service/saved_searches_utils.ts index 33bb9621835e6..ce3da85a2d3bd 100644 --- a/src/plugins/saved_search/common/service/saved_searches_utils.ts +++ b/src/plugins/saved_search/common/service/saved_searches_utils.ts @@ -41,6 +41,7 @@ export const toSavedSearchAttributes = ( viewMode: savedSearch.viewMode, hideAggregatedPreview: savedSearch.hideAggregatedPreview, rowHeight: savedSearch.rowHeight, + headerRowHeight: savedSearch.headerRowHeight, isTextBasedQuery: savedSearch.isTextBasedQuery ?? false, usesAdHocDataView: savedSearch.usesAdHocDataView, timeRestore: savedSearch.timeRestore ?? false, diff --git a/src/plugins/saved_search/common/types.ts b/src/plugins/saved_search/common/types.ts index d4ccb46d69cd5..d58f8c1cec7fc 100644 --- a/src/plugins/saved_search/common/types.ts +++ b/src/plugins/saved_search/common/types.ts @@ -36,6 +36,7 @@ export interface SavedSearchAttributes { viewMode?: VIEW_MODE; hideAggregatedPreview?: boolean; rowHeight?: number; + headerRowHeight?: number; timeRestore?: boolean; timeRange?: Pick; @@ -63,6 +64,7 @@ export interface SavedSearch { viewMode?: VIEW_MODE; hideAggregatedPreview?: boolean; rowHeight?: number; + headerRowHeight?: number; isTextBasedQuery?: boolean; usesAdHocDataView?: boolean; diff --git a/src/plugins/saved_search/public/services/saved_searches/save_saved_searches.test.ts b/src/plugins/saved_search/public/services/saved_searches/save_saved_searches.test.ts index 344087cdf6686..2dadc37de3cfa 100644 --- a/src/plugins/saved_search/public/services/saved_searches/save_saved_searches.test.ts +++ b/src/plugins/saved_search/public/services/saved_searches/save_saved_searches.test.ts @@ -128,6 +128,7 @@ describe('saveSavedSearch', () => { kibanaSavedObjectMeta: { searchSourceJSON: '{}' }, refreshInterval: undefined, rowHeight: undefined, + headerRowHeight: undefined, rowsPerPage: undefined, sampleSize: undefined, sort: [], @@ -163,6 +164,7 @@ describe('saveSavedSearch', () => { kibanaSavedObjectMeta: { searchSourceJSON: '{}' }, refreshInterval: undefined, rowHeight: undefined, + headerRowHeight: undefined, rowsPerPage: undefined, sampleSize: undefined, timeRange: undefined, @@ -213,6 +215,7 @@ describe('saveSavedSearch', () => { kibanaSavedObjectMeta: { searchSourceJSON: '{}' }, refreshInterval: undefined, rowHeight: undefined, + headerRowHeight: undefined, rowsPerPage: undefined, sampleSize: undefined, sort: [], diff --git a/src/plugins/saved_search/public/services/saved_searches/saved_search_attribute_service.test.ts b/src/plugins/saved_search/public/services/saved_searches/saved_search_attribute_service.test.ts index f2dcef1104520..81b7e68cae319 100644 --- a/src/plugins/saved_search/public/services/saved_searches/saved_search_attribute_service.test.ts +++ b/src/plugins/saved_search/public/services/saved_searches/saved_search_attribute_service.test.ts @@ -186,6 +186,7 @@ describe('getSavedSearchAttributeService', () => { ], "description": "", "grid": Object {}, + "headerRowHeight": undefined, "hideAggregatedPreview": undefined, "hideChart": false, "id": "saved-object-id", diff --git a/src/plugins/saved_search/public/services/saved_searches/types.ts b/src/plugins/saved_search/public/services/saved_searches/types.ts index 086d71848b6c6..d2e4e6ad5de51 100644 --- a/src/plugins/saved_search/public/services/saved_searches/types.ts +++ b/src/plugins/saved_search/public/services/saved_searches/types.ts @@ -33,6 +33,7 @@ interface SearchBaseInput extends EmbeddableInput { columns?: string[]; sort?: SortOrder[]; rowHeight?: number; + headerRowHeight?: number; rowsPerPage?: number; sampleSize?: number; } diff --git a/src/plugins/saved_search/server/content_management/saved_search_storage.ts b/src/plugins/saved_search/server/content_management/saved_search_storage.ts index 0615dbdc3049e..53fe82eb6e1e4 100644 --- a/src/plugins/saved_search/server/content_management/saved_search_storage.ts +++ b/src/plugins/saved_search/server/content_management/saved_search_storage.ts @@ -38,6 +38,7 @@ export class SavedSearchStorage extends SOContentStorage { 'viewMode', 'hideAggregatedPreview', 'rowHeight', + 'headerRowHeight', 'timeRestore', 'timeRange', 'refreshInterval', diff --git a/src/plugins/saved_search/server/saved_objects/schema.ts b/src/plugins/saved_search/server/saved_objects/schema.ts index 19dfdf5e7a11c..851a14417b400 100644 --- a/src/plugins/saved_search/server/saved_objects/schema.ts +++ b/src/plugins/saved_search/server/saved_objects/schema.ts @@ -13,7 +13,7 @@ import { VIEW_MODE, } from '../../common'; -const SCHEMA_SEARCH_BASE = { +const SCHEMA_SEARCH_BASE = schema.object({ // General title: schema.string(), description: schema.string({ defaultValue: '' }), @@ -81,11 +81,11 @@ const SCHEMA_SEARCH_BASE = { // Legacy hits: schema.maybe(schema.number()), version: schema.maybe(schema.number()), -}; +}); + +export const SCHEMA_SEARCH_V8_8_0 = SCHEMA_SEARCH_BASE; -export const SCHEMA_SEARCH_V8_8_0 = schema.object(SCHEMA_SEARCH_BASE); -export const SCHEMA_SEARCH_V8_12_0 = schema.object({ - ...SCHEMA_SEARCH_BASE, +export const SCHEMA_SEARCH_MODEL_VERSION_1 = SCHEMA_SEARCH_BASE.extends({ sampleSize: schema.maybe( schema.number({ min: MIN_SAVED_SEARCH_SAMPLE_SIZE, @@ -93,3 +93,7 @@ export const SCHEMA_SEARCH_V8_12_0 = schema.object({ }) ), }); + +export const SCHEMA_SEARCH_MODEL_VERSION_2 = SCHEMA_SEARCH_MODEL_VERSION_1.extends({ + headerRowHeight: schema.maybe(schema.number()), +}); diff --git a/src/plugins/saved_search/server/saved_objects/search.ts b/src/plugins/saved_search/server/saved_objects/search.ts index 2d3844f098c6a..a913e513e897f 100644 --- a/src/plugins/saved_search/server/saved_objects/search.ts +++ b/src/plugins/saved_search/server/saved_objects/search.ts @@ -10,7 +10,11 @@ import { ANALYTICS_SAVED_OBJECT_INDEX } from '@kbn/core-saved-objects-server'; import { SavedObjectsType } from '@kbn/core/server'; import { MigrateFunctionsObject } from '@kbn/kibana-utils-plugin/common'; import { getAllMigrations } from './search_migrations'; -import { SCHEMA_SEARCH_V8_8_0, SCHEMA_SEARCH_V8_12_0 } from './schema'; +import { + SCHEMA_SEARCH_V8_8_0, + SCHEMA_SEARCH_MODEL_VERSION_1, + SCHEMA_SEARCH_MODEL_VERSION_2, +} from './schema'; export function getSavedSearchObjectType( getSearchSourceMigrations: () => MigrateFunctionsObject @@ -35,6 +39,22 @@ export function getSavedSearchObjectType( }; }, }, + modelVersions: { + 1: { + changes: [], + schemas: { + forwardCompatibility: SCHEMA_SEARCH_MODEL_VERSION_1.extends({}, { unknowns: 'ignore' }), + create: SCHEMA_SEARCH_MODEL_VERSION_1, + }, + }, + 2: { + changes: [], + schemas: { + forwardCompatibility: SCHEMA_SEARCH_MODEL_VERSION_2.extends({}, { unknowns: 'ignore' }), + create: SCHEMA_SEARCH_MODEL_VERSION_2, + }, + }, + }, mappings: { dynamic: false, properties: { @@ -44,7 +64,6 @@ export function getSavedSearchObjectType( }, schemas: { '8.8.0': SCHEMA_SEARCH_V8_8_0, - '8.12.0': SCHEMA_SEARCH_V8_12_0, }, migrations: () => getAllMigrations(getSearchSourceMigrations()), }; diff --git a/test/functional/apps/discover/group2/_data_grid_row_height.ts b/test/functional/apps/discover/group2/_data_grid_row_height.ts index b0808e8c2de4d..d1191eca828ac 100644 --- a/test/functional/apps/discover/group2/_data_grid_row_height.ts +++ b/test/functional/apps/discover/group2/_data_grid_row_height.ts @@ -74,7 +74,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { expect(await dataGrid.getCurrentRowHeightValue()).to.be('Auto fit'); }); - it('should persist the selection after reloading the page', async () => { + it('should persist the row height selection after reloading the page', async () => { await dataGrid.clickGridSettings(); expect(await dataGrid.getCurrentRowHeightValue()).to.be('Auto fit'); @@ -89,5 +89,55 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { expect(await dataGrid.getCurrentRowHeightValue()).to.be('Single'); }); + + it('should use the default header row height', async () => { + const rows = await dataGrid.getDocTableRows(); + expect(rows.length).to.be.above(0); + + await dataGrid.clickGridSettings(); + expect(await dataGrid.getCurrentHeaderRowHeightValue()).to.be('Custom'); + }); + + it('should allow to change header row height', async () => { + await dataGrid.clickGridSettings(); + expect(await dataGrid.getCurrentHeaderRowHeightValue()).to.be('Custom'); + + await dataGrid.changeHeaderRowHeightValue('Single'); + + // toggle the popover + await dataGrid.clickGridSettings(); + await dataGrid.clickGridSettings(); + + expect(await dataGrid.getCurrentHeaderRowHeightValue()).to.be('Single'); + + // we hide "Reset to default" action in Discover + await testSubjects.missingOrFail('resetDisplaySelector'); + + await dataGrid.changeHeaderRowHeightValue('Custom'); + + expect(await dataGrid.getCurrentHeaderRowHeightValue()).to.be('Custom'); + + await testSubjects.missingOrFail('resetDisplaySelector'); + + await dataGrid.changeHeaderRowHeightValue('Auto fit'); + + expect(await dataGrid.getCurrentHeaderRowHeightValue()).to.be('Auto fit'); + }); + + it('should persist the header row height selection after reloading the page', async () => { + await dataGrid.clickGridSettings(); + expect(await dataGrid.getCurrentHeaderRowHeightValue()).to.be('Auto fit'); + + await dataGrid.changeHeaderRowHeightValue('Single'); + + expect(await dataGrid.getCurrentHeaderRowHeightValue()).to.be('Single'); + + await browser.refresh(); + + await PageObjects.discover.waitUntilSearchingHasFinished(); + await dataGrid.clickGridSettings(); + + expect(await dataGrid.getCurrentHeaderRowHeightValue()).to.be('Single'); + }); }); } diff --git a/test/functional/services/data_grid.ts b/test/functional/services/data_grid.ts index b139392c0f5c9..e9cef52332c9f 100644 --- a/test/functional/services/data_grid.ts +++ b/test/functional/services/data_grid.ts @@ -367,20 +367,37 @@ export class DataGridService extends FtrService { } public async getCurrentRowHeightValue() { - const buttonGroup = await this.testSubjects.find('rowHeightButtonGroup'); + const buttonGroup = await this.testSubjects.find( + 'unifiedDataTableRowHeightSettings_rowHeightButtonGroup' + ); return ( await buttonGroup.findByCssSelector('.euiButtonGroupButton-isSelected') ).getVisibleText(); } public async changeRowHeightValue(newValue: string) { - const buttonGroup = await this.testSubjects.find('rowHeightButtonGroup'); + const buttonGroup = await this.testSubjects.find( + 'unifiedDataTableRowHeightSettings_rowHeightButtonGroup' + ); const option = await buttonGroup.findByCssSelector(`[data-text="${newValue}"]`); await option.click(); } - public async resetRowHeightValue() { - await this.testSubjects.click('resetDisplaySelector'); + public async getCurrentHeaderRowHeightValue() { + const buttonGroup = await this.testSubjects.find( + 'unifiedDataTableHeaderRowHeightSettings_rowHeightButtonGroup' + ); + return ( + await buttonGroup.findByCssSelector('.euiButtonGroupButton-isSelected') + ).getVisibleText(); + } + + public async changeHeaderRowHeightValue(newValue: string) { + const buttonGroup = await this.testSubjects.find( + 'unifiedDataTableHeaderRowHeightSettings_rowHeightButtonGroup' + ); + const option = await buttonGroup.findByCssSelector(`[data-text="${newValue}"]`); + await option.click(); } private async findSampleSizeInput() { diff --git a/x-pack/plugins/lens/public/visualizations/datatable/components/row_height_settings.tsx b/x-pack/plugins/lens/public/visualizations/datatable/components/row_height_settings.tsx deleted file mode 100644 index a6be375441be5..0000000000000 --- a/x-pack/plugins/lens/public/visualizations/datatable/components/row_height_settings.tsx +++ /dev/null @@ -1,101 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import React from 'react'; -import { i18n } from '@kbn/i18n'; -import { EuiButtonGroup, EuiFormRow, EuiRange, htmlIdGenerator, EuiSpacer } from '@elastic/eui'; -import type { DatatableVisualizationState } from '../visualization'; - -export interface RowHeightSettingsProps { - rowHeight?: 'auto' | 'single' | 'custom'; - rowHeightLines?: number; - maxRowHeight?: number; - label: string; - onChangeRowHeight: (newHeightMode: 'auto' | 'single' | 'custom' | undefined) => void; - onChangeRowHeightLines: (newRowHeightLines: number) => void; - 'data-test-subj'?: string; -} - -const idPrefix = htmlIdGenerator()(); - -export function RowHeightSettings(props: RowHeightSettingsProps) { - const { - label, - rowHeight, - rowHeightLines, - onChangeRowHeight, - onChangeRowHeightLines, - maxRowHeight, - } = props; - - const rowHeightModeOptions = [ - { - id: `${idPrefix}single`, - label: i18n.translate('xpack.lens.table.rowHeight.single', { - defaultMessage: 'Single', - }), - 'data-test-subj': 'lnsDatatable_rowHeight_single', - }, - { - id: `${idPrefix}auto`, - label: i18n.translate('xpack.lens.table.rowHeight.auto', { - defaultMessage: 'Auto fit', - }), - 'data-test-subj': 'lnsDatatable_rowHeight_auto', - }, - { - id: `${idPrefix}custom`, - label: i18n.translate('xpack.lens.table.rowHeight.custom', { - defaultMessage: 'Custom', - }), - 'data-test-subj': 'lnsDatatable_rowHeight_custom', - }, - ]; - - return ( - <> - - <> - { - const newMode = optionId.replace( - idPrefix, - '' - ) as DatatableVisualizationState['rowHeight']; - onChangeRowHeight(newMode); - }} - /> - {rowHeight === 'custom' ? ( - <> - - { - const lineCount = Number(e.currentTarget.value); - onChangeRowHeightLines(lineCount); - }} - data-test-subj="lens-table-row-height-lineCountNumber" - /> - - ) : null} - - - - ); -} diff --git a/x-pack/plugins/lens/public/visualizations/datatable/components/toolbar.tsx b/x-pack/plugins/lens/public/visualizations/datatable/components/toolbar.tsx index fee76d99593fd..19e60e28cd566 100644 --- a/x-pack/plugins/lens/public/visualizations/datatable/components/toolbar.tsx +++ b/x-pack/plugins/lens/public/visualizations/datatable/components/toolbar.tsx @@ -8,10 +8,10 @@ import React, { useCallback } from 'react'; import { i18n } from '@kbn/i18n'; import { EuiFlexGroup, EuiFormRow, EuiSwitch, EuiToolTip } from '@elastic/eui'; +import { RowHeightSettings } from '@kbn/unified-data-table'; import { ToolbarPopover } from '../../../shared_components'; import type { VisualizationToolbarProps } from '../../../types'; import type { DatatableVisualizationState } from '../visualization'; -import { RowHeightSettings } from './row_height_settings'; import { DEFAULT_PAGE_SIZE } from './table_basic'; export function DataTableToolbar(props: VisualizationToolbarProps) { @@ -72,6 +72,7 @@ export function DataTableToolbar(props: VisualizationToolbarProps Date: Mon, 12 Feb 2024 16:25:49 -0800 Subject: [PATCH 37/83] [Cloud Security] Added Create Detection Rule in Take Action button in Rules Flyout (#176649) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary This PR adds create Detection Rule in Take action button in Rules Flyout Screenshot 2024-02-12 at 1 59 53 PM Screenshot 2024-02-12 at 2 00 08 PM --- .../public/components/take_action.tsx | 6 ++++++ .../public/pages/rules/rules_flyout.tsx | 17 +++++++++++++++-- 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/x-pack/plugins/cloud_security_posture/public/components/take_action.tsx b/x-pack/plugins/cloud_security_posture/public/components/take_action.tsx index 054fc9f3759ff..d388e2bf1afbc 100644 --- a/x-pack/plugins/cloud_security_posture/public/components/take_action.tsx +++ b/x-pack/plugins/cloud_security_posture/public/components/take_action.tsx @@ -31,6 +31,7 @@ interface TakeActionProps { createRuleFn?: (http: HttpSetup) => Promise; enableBenchmarkRuleFn?: () => Promise; disableBenchmarkRuleFn?: () => Promise; + isCreateDetectionRuleDisabled?: boolean; } export const showCreateDetectionRuleSuccessToast = ( @@ -169,6 +170,7 @@ export const TakeAction = ({ createRuleFn, enableBenchmarkRuleFn, disableBenchmarkRuleFn, + isCreateDetectionRuleDisabled = false, }: TakeActionProps) => { const queryClient = useQueryClient(); const [isPopoverOpen, setPopoverOpen] = useState(false); @@ -206,6 +208,7 @@ export const TakeAction = ({ notifications={notifications} http={http} queryClient={queryClient} + isCreateDetectionRuleDisabled={isCreateDetectionRuleDisabled} /> ); if (enableBenchmarkRuleFn) @@ -255,6 +258,7 @@ const CreateDetectionRule = ({ notifications, http, queryClient, + isCreateDetectionRuleDisabled = false, }: { createRuleFn: (http: HttpSetup) => Promise; setIsLoading: (isLoading: boolean) => void; @@ -262,10 +266,12 @@ const CreateDetectionRule = ({ notifications: NotificationsStart; http: HttpSetup; queryClient: QueryClient; + isCreateDetectionRuleDisabled: boolean; }) => { return ( { closePopover(); setIsLoading(true); diff --git a/x-pack/plugins/cloud_security_posture/public/pages/rules/rules_flyout.tsx b/x-pack/plugins/cloud_security_posture/public/pages/rules/rules_flyout.tsx index 5026884173b6e..131bc177ad273 100644 --- a/x-pack/plugins/cloud_security_posture/public/pages/rules/rules_flyout.tsx +++ b/x-pack/plugins/cloud_security_posture/public/pages/rules/rules_flyout.tsx @@ -22,6 +22,7 @@ import { } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n-react'; +import { HttpSetup } from '@kbn/core/public'; import { useKibana } from '../../common/hooks/use_kibana'; import { getFindingsDetectionRuleSearchTags } from '../../../common/utils/detection_rules'; import { CspBenchmarkRuleMetadata } from '../../../common/types/latest'; @@ -35,6 +36,7 @@ import { TakeAction, } from '../../components/take_action'; import { useFetchDetectionRulesByTags } from '../../common/api/use_fetch_detection_rules_by_tags'; +import { createDetectionRuleFromBenchmark } from '../configurations/utils/create_detection_rule_from_benchmark'; export const RULES_FLYOUT_SWITCH_BUTTON = 'rule-flyout-switch-button'; @@ -89,6 +91,9 @@ export const RuleFlyout = ({ onClose, rule, refetchRulesStates }: RuleFlyoutProp } }; + const createMisconfigurationRuleFn = async (http: HttpSetup) => + await createDetectionRuleFromBenchmark(http, rule.metadata); + return ( {isRuleMuted ? ( - + ) : ( - + )} From d0b0d160bff62b89f1d9d584836a60363787ab1b Mon Sep 17 00:00:00 2001 From: Tiago Costa Date: Tue, 13 Feb 2024 03:39:33 +0000 Subject: [PATCH 38/83] chore(NA): test current cwd --- .buildkite/scripts/steps/es_snapshots/promote_manifest.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/.buildkite/scripts/steps/es_snapshots/promote_manifest.ts b/.buildkite/scripts/steps/es_snapshots/promote_manifest.ts index 3d4009139e3dc..7077a9f4d7a46 100644 --- a/.buildkite/scripts/steps/es_snapshots/promote_manifest.ts +++ b/.buildkite/scripts/steps/es_snapshots/promote_manifest.ts @@ -38,6 +38,7 @@ import { BASE_BUCKET_DAILY, BASE_BUCKET_PERMANENT } from './bucket_config'; execSync( ` set -euo pipefail + echo $CWD .buildkite/scripts/common/activate_service_account.sh ${bucket} cp manifest.json manifest-latest-verified.json gsutil -h "Cache-Control:no-cache, max-age=0, no-transform" cp manifest-latest-verified.json gs://${BASE_BUCKET_DAILY}/${version}/ From dd18b4ea9c4331bed007d672e8ce11520b51ccad Mon Sep 17 00:00:00 2001 From: Tiago Costa Date: Tue, 13 Feb 2024 03:41:59 +0000 Subject: [PATCH 39/83] chore(NA): test current cwd --- .buildkite/scripts/steps/es_snapshots/promote_manifest.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.buildkite/scripts/steps/es_snapshots/promote_manifest.ts b/.buildkite/scripts/steps/es_snapshots/promote_manifest.ts index 7077a9f4d7a46..0590556059a66 100644 --- a/.buildkite/scripts/steps/es_snapshots/promote_manifest.ts +++ b/.buildkite/scripts/steps/es_snapshots/promote_manifest.ts @@ -38,7 +38,7 @@ import { BASE_BUCKET_DAILY, BASE_BUCKET_PERMANENT } from './bucket_config'; execSync( ` set -euo pipefail - echo $CWD + echo $(pwd) .buildkite/scripts/common/activate_service_account.sh ${bucket} cp manifest.json manifest-latest-verified.json gsutil -h "Cache-Control:no-cache, max-age=0, no-transform" cp manifest-latest-verified.json gs://${BASE_BUCKET_DAILY}/${version}/ From e11299e0cdc3a0e90c6e9a1e9f9709d2c0d034bc Mon Sep 17 00:00:00 2001 From: Tiago Costa Date: Tue, 13 Feb 2024 03:55:11 +0000 Subject: [PATCH 40/83] chore(NA): test current cwd --- .buildkite/scripts/steps/es_snapshots/promote_manifest.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.buildkite/scripts/steps/es_snapshots/promote_manifest.ts b/.buildkite/scripts/steps/es_snapshots/promote_manifest.ts index 0590556059a66..0bcbf67c1e048 100644 --- a/.buildkite/scripts/steps/es_snapshots/promote_manifest.ts +++ b/.buildkite/scripts/steps/es_snapshots/promote_manifest.ts @@ -35,10 +35,12 @@ import { BASE_BUCKET_DAILY, BASE_BUCKET_PERMANENT } from './bucket_config'; fs.writeFileSync('manifest-permanent.json', manifestPermanentJson); + const currentPath = execSync('pwd', { encoding: 'utf-8' }).trim(); + console.log('Current working directory:', currentPath); + execSync( ` set -euo pipefail - echo $(pwd) .buildkite/scripts/common/activate_service_account.sh ${bucket} cp manifest.json manifest-latest-verified.json gsutil -h "Cache-Control:no-cache, max-age=0, no-transform" cp manifest-latest-verified.json gs://${BASE_BUCKET_DAILY}/${version}/ From 2e912abde0a287d3d31e77b268ef8539c09ff235 Mon Sep 17 00:00:00 2001 From: Tiago Costa Date: Tue, 13 Feb 2024 04:43:58 +0000 Subject: [PATCH 41/83] fix(NA): run script over original cwd for promote manifest pipeline (#176781) This PR solves a problem introduced at https://github.com/elastic/kibana/pull/174756 where the addition into the bash script was not being correctly loaded for the promote manifest pipeline as it was not running in the original main cwd. --- .buildkite/scripts/steps/es_snapshots/promote_manifest.ts | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/.buildkite/scripts/steps/es_snapshots/promote_manifest.ts b/.buildkite/scripts/steps/es_snapshots/promote_manifest.ts index 0bcbf67c1e048..274807a8e5038 100644 --- a/.buildkite/scripts/steps/es_snapshots/promote_manifest.ts +++ b/.buildkite/scripts/steps/es_snapshots/promote_manifest.ts @@ -18,6 +18,7 @@ import { BASE_BUCKET_DAILY, BASE_BUCKET_PERMANENT } from './bucket_config'; throw Error('Manifest URL missing'); } + const mainCWD = process.cwd(); const tempDir = fs.mkdtempSync('snapshot-promotion'); process.chdir(tempDir); @@ -35,13 +36,10 @@ import { BASE_BUCKET_DAILY, BASE_BUCKET_PERMANENT } from './bucket_config'; fs.writeFileSync('manifest-permanent.json', manifestPermanentJson); - const currentPath = execSync('pwd', { encoding: 'utf-8' }).trim(); - console.log('Current working directory:', currentPath); - execSync( ` set -euo pipefail - .buildkite/scripts/common/activate_service_account.sh ${bucket} + ${mainCWD}/.buildkite/scripts/common/activate_service_account.sh ${bucket} cp manifest.json manifest-latest-verified.json gsutil -h "Cache-Control:no-cache, max-age=0, no-transform" cp manifest-latest-verified.json gs://${BASE_BUCKET_DAILY}/${version}/ rm manifest.json From 5155ffdf492ed88e7611bc24aad2c3e0d7b1c1f8 Mon Sep 17 00:00:00 2001 From: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Date: Tue, 13 Feb 2024 01:10:27 -0500 Subject: [PATCH 42/83] [api-docs] 2024-02-13 Daily api_docs build (#176784) Generated by https://buildkite.com/elastic/kibana-api-docs-daily/builds/612 --- api_docs/actions.mdx | 2 +- api_docs/advanced_settings.mdx | 2 +- .../ai_assistant_management_observability.mdx | 2 +- .../ai_assistant_management_selection.mdx | 2 +- api_docs/aiops.mdx | 2 +- api_docs/alerting.devdocs.json | 2 +- api_docs/alerting.mdx | 2 +- api_docs/apm.mdx | 2 +- api_docs/apm_data_access.mdx | 2 +- api_docs/asset_manager.mdx | 2 +- api_docs/banners.mdx | 2 +- api_docs/bfetch.mdx | 2 +- api_docs/canvas.mdx | 2 +- api_docs/cases.mdx | 2 +- api_docs/charts.mdx | 2 +- api_docs/cloud.mdx | 2 +- api_docs/cloud_data_migration.mdx | 2 +- api_docs/cloud_defend.mdx | 2 +- api_docs/cloud_experiments.mdx | 2 +- api_docs/cloud_security_posture.mdx | 2 +- api_docs/console.mdx | 2 +- api_docs/content_management.mdx | 2 +- api_docs/controls.mdx | 2 +- api_docs/custom_integrations.mdx | 2 +- api_docs/dashboard.devdocs.json | 73 +- api_docs/dashboard.mdx | 2 +- api_docs/dashboard_enhanced.mdx | 2 +- api_docs/data.mdx | 4 +- api_docs/data_query.devdocs.json | 95 +- api_docs/data_query.mdx | 4 +- api_docs/data_search.mdx | 4 +- api_docs/data_view_editor.mdx | 2 +- api_docs/data_view_field_editor.mdx | 2 +- api_docs/data_view_management.mdx | 2 +- api_docs/data_views.mdx | 2 +- api_docs/data_visualizer.mdx | 2 +- api_docs/dataset_quality.mdx | 2 +- api_docs/deprecations_by_api.mdx | 4 +- api_docs/deprecations_by_plugin.mdx | 14 +- api_docs/deprecations_by_team.mdx | 2 +- api_docs/dev_tools.mdx | 2 +- api_docs/discover.devdocs.json | 16 + api_docs/discover.mdx | 4 +- api_docs/discover_enhanced.mdx | 2 +- api_docs/ecs_data_quality_dashboard.mdx | 2 +- api_docs/elastic_assistant.mdx | 2 +- api_docs/embeddable.devdocs.json | 32 +- api_docs/embeddable.mdx | 2 +- api_docs/embeddable_enhanced.devdocs.json | 118 +- api_docs/embeddable_enhanced.mdx | 4 +- api_docs/encrypted_saved_objects.mdx | 2 +- api_docs/enterprise_search.mdx | 2 +- api_docs/es_ui_shared.mdx | 2 +- api_docs/event_annotation.mdx | 2 +- api_docs/event_annotation_listing.mdx | 2 +- api_docs/event_log.devdocs.json | 6 +- api_docs/event_log.mdx | 2 +- api_docs/exploratory_view.mdx | 2 +- api_docs/expression_error.mdx | 2 +- api_docs/expression_gauge.mdx | 2 +- api_docs/expression_heatmap.mdx | 2 +- api_docs/expression_image.mdx | 2 +- api_docs/expression_legacy_metric_vis.mdx | 2 +- api_docs/expression_metric.mdx | 2 +- api_docs/expression_metric_vis.mdx | 2 +- api_docs/expression_partition_vis.mdx | 2 +- api_docs/expression_repeat_image.mdx | 2 +- api_docs/expression_reveal_image.mdx | 2 +- api_docs/expression_shape.mdx | 2 +- api_docs/expression_tagcloud.mdx | 2 +- api_docs/expression_x_y.devdocs.json | 8 +- api_docs/expression_x_y.mdx | 2 +- api_docs/expressions.mdx | 2 +- api_docs/features.mdx | 2 +- api_docs/field_formats.mdx | 2 +- api_docs/file_upload.mdx | 2 +- api_docs/files.mdx | 2 +- api_docs/files_management.mdx | 2 +- api_docs/fleet.devdocs.json | 1297 +---------------- api_docs/fleet.mdx | 4 +- api_docs/global_search.devdocs.json | 92 ++ api_docs/global_search.mdx | 4 +- api_docs/guided_onboarding.mdx | 2 +- api_docs/home.mdx | 2 +- api_docs/image_embeddable.mdx | 2 +- api_docs/index_lifecycle_management.mdx | 2 +- api_docs/index_management.mdx | 2 +- api_docs/infra.mdx | 2 +- api_docs/ingest_pipelines.mdx | 2 +- api_docs/inspector.mdx | 2 +- api_docs/interactive_setup.mdx | 2 +- api_docs/kbn_ace.mdx | 2 +- api_docs/kbn_actions_types.mdx | 2 +- api_docs/kbn_aiops_components.mdx | 2 +- api_docs/kbn_aiops_utils.mdx | 2 +- .../kbn_alerting_api_integration_helpers.mdx | 2 +- api_docs/kbn_alerting_state_types.mdx | 2 +- api_docs/kbn_alerting_types.mdx | 2 +- api_docs/kbn_alerts_as_data_utils.mdx | 2 +- api_docs/kbn_alerts_ui_shared.mdx | 2 +- api_docs/kbn_analytics.mdx | 2 +- api_docs/kbn_analytics_client.mdx | 2 +- api_docs/kbn_analytics_collection_utils.mdx | 2 +- ..._analytics_shippers_elastic_v3_browser.mdx | 2 +- ...n_analytics_shippers_elastic_v3_common.mdx | 2 +- ...n_analytics_shippers_elastic_v3_server.mdx | 2 +- api_docs/kbn_analytics_shippers_fullstory.mdx | 2 +- api_docs/kbn_apm_config_loader.mdx | 2 +- api_docs/kbn_apm_synthtrace.mdx | 2 +- api_docs/kbn_apm_synthtrace_client.mdx | 2 +- api_docs/kbn_apm_utils.mdx | 2 +- api_docs/kbn_axe_config.mdx | 2 +- api_docs/kbn_bfetch_error.mdx | 2 +- api_docs/kbn_calculate_auto.mdx | 2 +- .../kbn_calculate_width_from_char_count.mdx | 2 +- api_docs/kbn_cases_components.mdx | 2 +- api_docs/kbn_cell_actions.mdx | 2 +- api_docs/kbn_chart_expressions_common.mdx | 2 +- api_docs/kbn_chart_icons.mdx | 2 +- api_docs/kbn_ci_stats_core.mdx | 2 +- api_docs/kbn_ci_stats_performance_metrics.mdx | 2 +- api_docs/kbn_ci_stats_reporter.mdx | 2 +- api_docs/kbn_cli_dev_mode.mdx | 2 +- api_docs/kbn_code_editor.mdx | 2 +- api_docs/kbn_code_editor_mock.mdx | 2 +- api_docs/kbn_code_owners.mdx | 2 +- api_docs/kbn_coloring.mdx | 2 +- api_docs/kbn_config.mdx | 2 +- api_docs/kbn_config_mocks.mdx | 2 +- api_docs/kbn_config_schema.mdx | 2 +- .../kbn_content_management_content_editor.mdx | 2 +- ...tent_management_tabbed_table_list_view.mdx | 2 +- ...kbn_content_management_table_list_view.mdx | 2 +- ...tent_management_table_list_view_common.mdx | 2 +- ...ntent_management_table_list_view_table.mdx | 2 +- api_docs/kbn_content_management_utils.mdx | 2 +- api_docs/kbn_core_analytics_browser.mdx | 2 +- .../kbn_core_analytics_browser_internal.mdx | 2 +- api_docs/kbn_core_analytics_browser_mocks.mdx | 2 +- api_docs/kbn_core_analytics_server.mdx | 2 +- .../kbn_core_analytics_server_internal.mdx | 2 +- api_docs/kbn_core_analytics_server_mocks.mdx | 2 +- api_docs/kbn_core_application_browser.mdx | 2 +- .../kbn_core_application_browser_internal.mdx | 2 +- .../kbn_core_application_browser_mocks.mdx | 2 +- api_docs/kbn_core_application_common.mdx | 2 +- api_docs/kbn_core_apps_browser_internal.mdx | 2 +- api_docs/kbn_core_apps_browser_mocks.mdx | 2 +- api_docs/kbn_core_apps_server_internal.mdx | 2 +- api_docs/kbn_core_base_browser_mocks.mdx | 2 +- api_docs/kbn_core_base_common.mdx | 2 +- api_docs/kbn_core_base_server_internal.mdx | 2 +- api_docs/kbn_core_base_server_mocks.mdx | 2 +- .../kbn_core_capabilities_browser_mocks.mdx | 2 +- api_docs/kbn_core_capabilities_common.mdx | 2 +- api_docs/kbn_core_capabilities_server.mdx | 2 +- .../kbn_core_capabilities_server_mocks.mdx | 2 +- api_docs/kbn_core_chrome_browser.mdx | 2 +- api_docs/kbn_core_chrome_browser_mocks.mdx | 2 +- api_docs/kbn_core_config_server_internal.mdx | 2 +- api_docs/kbn_core_custom_branding_browser.mdx | 2 +- ..._core_custom_branding_browser_internal.mdx | 2 +- ...kbn_core_custom_branding_browser_mocks.mdx | 2 +- api_docs/kbn_core_custom_branding_common.mdx | 2 +- api_docs/kbn_core_custom_branding_server.mdx | 2 +- ...n_core_custom_branding_server_internal.mdx | 2 +- .../kbn_core_custom_branding_server_mocks.mdx | 2 +- api_docs/kbn_core_deprecations_browser.mdx | 2 +- ...kbn_core_deprecations_browser_internal.mdx | 2 +- .../kbn_core_deprecations_browser_mocks.mdx | 2 +- api_docs/kbn_core_deprecations_common.mdx | 2 +- api_docs/kbn_core_deprecations_server.mdx | 2 +- .../kbn_core_deprecations_server_internal.mdx | 2 +- .../kbn_core_deprecations_server_mocks.mdx | 2 +- api_docs/kbn_core_doc_links_browser.mdx | 2 +- api_docs/kbn_core_doc_links_browser_mocks.mdx | 2 +- api_docs/kbn_core_doc_links_server.mdx | 2 +- api_docs/kbn_core_doc_links_server_mocks.mdx | 2 +- ...e_elasticsearch_client_server_internal.mdx | 2 +- ...core_elasticsearch_client_server_mocks.mdx | 2 +- api_docs/kbn_core_elasticsearch_server.mdx | 2 +- ...kbn_core_elasticsearch_server_internal.mdx | 2 +- .../kbn_core_elasticsearch_server_mocks.mdx | 2 +- .../kbn_core_environment_server_internal.mdx | 2 +- .../kbn_core_environment_server_mocks.mdx | 2 +- .../kbn_core_execution_context_browser.mdx | 2 +- ...ore_execution_context_browser_internal.mdx | 2 +- ...n_core_execution_context_browser_mocks.mdx | 2 +- .../kbn_core_execution_context_common.mdx | 2 +- .../kbn_core_execution_context_server.mdx | 2 +- ...core_execution_context_server_internal.mdx | 2 +- ...bn_core_execution_context_server_mocks.mdx | 2 +- api_docs/kbn_core_fatal_errors_browser.mdx | 2 +- .../kbn_core_fatal_errors_browser_mocks.mdx | 2 +- api_docs/kbn_core_http_browser.mdx | 2 +- api_docs/kbn_core_http_browser_internal.mdx | 2 +- api_docs/kbn_core_http_browser_mocks.mdx | 2 +- api_docs/kbn_core_http_common.mdx | 2 +- .../kbn_core_http_context_server_mocks.mdx | 2 +- ...re_http_request_handler_context_server.mdx | 2 +- api_docs/kbn_core_http_resources_server.mdx | 2 +- ...bn_core_http_resources_server_internal.mdx | 2 +- .../kbn_core_http_resources_server_mocks.mdx | 2 +- .../kbn_core_http_router_server_internal.mdx | 2 +- .../kbn_core_http_router_server_mocks.mdx | 2 +- api_docs/kbn_core_http_server.devdocs.json | 18 +- api_docs/kbn_core_http_server.mdx | 2 +- api_docs/kbn_core_http_server_internal.mdx | 2 +- api_docs/kbn_core_http_server_mocks.mdx | 2 +- api_docs/kbn_core_i18n_browser.mdx | 2 +- api_docs/kbn_core_i18n_browser_mocks.mdx | 2 +- api_docs/kbn_core_i18n_server.mdx | 2 +- api_docs/kbn_core_i18n_server_internal.mdx | 2 +- api_docs/kbn_core_i18n_server_mocks.mdx | 2 +- ...n_core_injected_metadata_browser_mocks.mdx | 2 +- ...kbn_core_integrations_browser_internal.mdx | 2 +- .../kbn_core_integrations_browser_mocks.mdx | 2 +- api_docs/kbn_core_lifecycle_browser.mdx | 2 +- api_docs/kbn_core_lifecycle_browser_mocks.mdx | 2 +- api_docs/kbn_core_lifecycle_server.mdx | 2 +- api_docs/kbn_core_lifecycle_server_mocks.mdx | 2 +- api_docs/kbn_core_logging_browser_mocks.mdx | 2 +- api_docs/kbn_core_logging_common_internal.mdx | 2 +- api_docs/kbn_core_logging_server.mdx | 2 +- api_docs/kbn_core_logging_server_internal.mdx | 2 +- api_docs/kbn_core_logging_server_mocks.mdx | 2 +- ...ore_metrics_collectors_server_internal.mdx | 2 +- ...n_core_metrics_collectors_server_mocks.mdx | 2 +- api_docs/kbn_core_metrics_server.mdx | 2 +- api_docs/kbn_core_metrics_server_internal.mdx | 2 +- api_docs/kbn_core_metrics_server_mocks.mdx | 2 +- api_docs/kbn_core_mount_utils_browser.mdx | 2 +- api_docs/kbn_core_node_server.mdx | 2 +- api_docs/kbn_core_node_server_internal.mdx | 2 +- api_docs/kbn_core_node_server_mocks.mdx | 2 +- api_docs/kbn_core_notifications_browser.mdx | 2 +- ...bn_core_notifications_browser_internal.mdx | 2 +- .../kbn_core_notifications_browser_mocks.mdx | 2 +- api_docs/kbn_core_overlays_browser.mdx | 2 +- .../kbn_core_overlays_browser_internal.mdx | 2 +- api_docs/kbn_core_overlays_browser_mocks.mdx | 2 +- api_docs/kbn_core_plugins_browser.mdx | 2 +- api_docs/kbn_core_plugins_browser_mocks.mdx | 2 +- .../kbn_core_plugins_contracts_browser.mdx | 2 +- .../kbn_core_plugins_contracts_server.mdx | 2 +- api_docs/kbn_core_plugins_server.mdx | 2 +- api_docs/kbn_core_plugins_server_mocks.mdx | 2 +- api_docs/kbn_core_preboot_server.mdx | 2 +- api_docs/kbn_core_preboot_server_mocks.mdx | 2 +- api_docs/kbn_core_rendering_browser_mocks.mdx | 2 +- .../kbn_core_rendering_server_internal.mdx | 2 +- api_docs/kbn_core_rendering_server_mocks.mdx | 2 +- api_docs/kbn_core_root_server_internal.mdx | 2 +- .../kbn_core_saved_objects_api_browser.mdx | 2 +- .../kbn_core_saved_objects_api_server.mdx | 2 +- ...bn_core_saved_objects_api_server_mocks.mdx | 2 +- ...ore_saved_objects_base_server_internal.mdx | 2 +- ...n_core_saved_objects_base_server_mocks.mdx | 2 +- api_docs/kbn_core_saved_objects_browser.mdx | 2 +- ...bn_core_saved_objects_browser_internal.mdx | 2 +- .../kbn_core_saved_objects_browser_mocks.mdx | 2 +- api_docs/kbn_core_saved_objects_common.mdx | 2 +- ..._objects_import_export_server_internal.mdx | 2 +- ...ved_objects_import_export_server_mocks.mdx | 2 +- ...aved_objects_migration_server_internal.mdx | 2 +- ...e_saved_objects_migration_server_mocks.mdx | 2 +- api_docs/kbn_core_saved_objects_server.mdx | 2 +- ...kbn_core_saved_objects_server_internal.mdx | 2 +- .../kbn_core_saved_objects_server_mocks.mdx | 2 +- .../kbn_core_saved_objects_utils_server.mdx | 2 +- api_docs/kbn_core_status_common.mdx | 2 +- api_docs/kbn_core_status_common_internal.mdx | 2 +- api_docs/kbn_core_status_server.mdx | 2 +- api_docs/kbn_core_status_server_internal.mdx | 2 +- api_docs/kbn_core_status_server_mocks.mdx | 2 +- ...core_test_helpers_deprecations_getters.mdx | 2 +- ...n_core_test_helpers_http_setup_browser.mdx | 2 +- api_docs/kbn_core_test_helpers_kbn_server.mdx | 2 +- .../kbn_core_test_helpers_model_versions.mdx | 2 +- ...n_core_test_helpers_so_type_serializer.mdx | 2 +- api_docs/kbn_core_test_helpers_test_utils.mdx | 2 +- api_docs/kbn_core_theme_browser.mdx | 2 +- api_docs/kbn_core_theme_browser_mocks.mdx | 2 +- api_docs/kbn_core_ui_settings_browser.mdx | 2 +- .../kbn_core_ui_settings_browser_internal.mdx | 2 +- .../kbn_core_ui_settings_browser_mocks.mdx | 2 +- api_docs/kbn_core_ui_settings_common.mdx | 2 +- api_docs/kbn_core_ui_settings_server.mdx | 2 +- .../kbn_core_ui_settings_server_internal.mdx | 2 +- .../kbn_core_ui_settings_server_mocks.mdx | 2 +- api_docs/kbn_core_usage_data_server.mdx | 2 +- .../kbn_core_usage_data_server_internal.mdx | 2 +- api_docs/kbn_core_usage_data_server_mocks.mdx | 2 +- api_docs/kbn_core_user_settings_server.mdx | 2 +- ...kbn_core_user_settings_server_internal.mdx | 2 +- .../kbn_core_user_settings_server_mocks.mdx | 2 +- api_docs/kbn_crypto.mdx | 2 +- api_docs/kbn_crypto_browser.mdx | 2 +- api_docs/kbn_custom_icons.mdx | 2 +- api_docs/kbn_custom_integrations.mdx | 2 +- api_docs/kbn_cypress_config.mdx | 2 +- api_docs/kbn_data_forge.mdx | 2 +- api_docs/kbn_data_service.mdx | 2 +- api_docs/kbn_data_stream_adapter.mdx | 2 +- api_docs/kbn_data_view_utils.mdx | 2 +- api_docs/kbn_datemath.mdx | 2 +- api_docs/kbn_deeplinks_analytics.mdx | 2 +- api_docs/kbn_deeplinks_devtools.mdx | 2 +- api_docs/kbn_deeplinks_management.mdx | 2 +- api_docs/kbn_deeplinks_ml.mdx | 2 +- api_docs/kbn_deeplinks_observability.mdx | 2 +- api_docs/kbn_deeplinks_search.mdx | 2 +- api_docs/kbn_default_nav_analytics.mdx | 2 +- api_docs/kbn_default_nav_devtools.mdx | 2 +- api_docs/kbn_default_nav_management.mdx | 2 +- api_docs/kbn_default_nav_ml.mdx | 2 +- api_docs/kbn_dev_cli_errors.mdx | 2 +- api_docs/kbn_dev_cli_runner.mdx | 2 +- api_docs/kbn_dev_proc_runner.mdx | 2 +- api_docs/kbn_dev_utils.mdx | 2 +- api_docs/kbn_discover_utils.mdx | 2 +- api_docs/kbn_doc_links.mdx | 2 +- api_docs/kbn_docs_utils.mdx | 2 +- api_docs/kbn_dom_drag_drop.mdx | 2 +- api_docs/kbn_ebt_tools.mdx | 2 +- api_docs/kbn_ecs.mdx | 2 +- api_docs/kbn_ecs_data_quality_dashboard.mdx | 2 +- api_docs/kbn_elastic_agent_utils.mdx | 2 +- api_docs/kbn_elastic_assistant.mdx | 2 +- api_docs/kbn_elastic_assistant_common.mdx | 2 +- api_docs/kbn_es.mdx | 2 +- api_docs/kbn_es_archiver.mdx | 2 +- api_docs/kbn_es_errors.mdx | 2 +- api_docs/kbn_es_query.mdx | 2 +- api_docs/kbn_es_types.mdx | 2 +- api_docs/kbn_eslint_plugin_imports.mdx | 2 +- api_docs/kbn_esql_utils.mdx | 2 +- api_docs/kbn_event_annotation_common.mdx | 2 +- api_docs/kbn_event_annotation_components.mdx | 2 +- api_docs/kbn_expandable_flyout.mdx | 2 +- api_docs/kbn_field_types.mdx | 2 +- api_docs/kbn_field_utils.mdx | 2 +- api_docs/kbn_find_used_node_modules.mdx | 2 +- .../kbn_ftr_common_functional_services.mdx | 2 +- .../kbn_ftr_common_functional_ui_services.mdx | 2 +- api_docs/kbn_generate.mdx | 2 +- api_docs/kbn_generate_console_definitions.mdx | 2 +- api_docs/kbn_generate_csv.mdx | 2 +- api_docs/kbn_guided_onboarding.mdx | 2 +- api_docs/kbn_handlebars.mdx | 2 +- api_docs/kbn_hapi_mocks.mdx | 2 +- api_docs/kbn_health_gateway_server.mdx | 2 +- api_docs/kbn_home_sample_data_card.mdx | 2 +- api_docs/kbn_home_sample_data_tab.mdx | 2 +- api_docs/kbn_i18n.mdx | 2 +- api_docs/kbn_i18n_react.mdx | 2 +- api_docs/kbn_import_resolver.mdx | 2 +- api_docs/kbn_infra_forge.mdx | 2 +- api_docs/kbn_interpreter.mdx | 2 +- api_docs/kbn_io_ts_utils.mdx | 2 +- api_docs/kbn_jest_serializers.mdx | 2 +- api_docs/kbn_journeys.mdx | 2 +- api_docs/kbn_json_ast.mdx | 2 +- api_docs/kbn_kibana_manifest_schema.mdx | 2 +- .../kbn_language_documentation_popover.mdx | 2 +- api_docs/kbn_lens_embeddable_utils.mdx | 2 +- api_docs/kbn_lens_formula_docs.mdx | 2 +- api_docs/kbn_logging.mdx | 2 +- api_docs/kbn_logging_mocks.mdx | 2 +- api_docs/kbn_managed_content_badge.mdx | 2 +- api_docs/kbn_managed_vscode_config.mdx | 2 +- api_docs/kbn_management_cards_navigation.mdx | 2 +- .../kbn_management_settings_application.mdx | 2 +- ...ent_settings_components_field_category.mdx | 2 +- ...gement_settings_components_field_input.mdx | 2 +- ...nagement_settings_components_field_row.mdx | 2 +- ...bn_management_settings_components_form.mdx | 2 +- ...n_management_settings_field_definition.mdx | 2 +- api_docs/kbn_management_settings_ids.mdx | 2 +- ...n_management_settings_section_registry.mdx | 2 +- api_docs/kbn_management_settings_types.mdx | 2 +- .../kbn_management_settings_utilities.mdx | 2 +- api_docs/kbn_management_storybook_config.mdx | 2 +- api_docs/kbn_mapbox_gl.mdx | 2 +- api_docs/kbn_maps_vector_tile_utils.mdx | 2 +- api_docs/kbn_ml_agg_utils.mdx | 2 +- api_docs/kbn_ml_anomaly_utils.mdx | 2 +- api_docs/kbn_ml_cancellable_search.mdx | 2 +- api_docs/kbn_ml_category_validator.mdx | 2 +- api_docs/kbn_ml_chi2test.mdx | 2 +- .../kbn_ml_data_frame_analytics_utils.mdx | 2 +- api_docs/kbn_ml_data_grid.mdx | 2 +- api_docs/kbn_ml_date_picker.mdx | 2 +- api_docs/kbn_ml_date_utils.mdx | 2 +- api_docs/kbn_ml_error_utils.mdx | 2 +- api_docs/kbn_ml_in_memory_table.mdx | 2 +- api_docs/kbn_ml_is_defined.mdx | 2 +- api_docs/kbn_ml_is_populated_object.mdx | 2 +- api_docs/kbn_ml_kibana_theme.mdx | 2 +- api_docs/kbn_ml_local_storage.mdx | 2 +- api_docs/kbn_ml_nested_property.mdx | 2 +- api_docs/kbn_ml_number_utils.mdx | 2 +- api_docs/kbn_ml_query_utils.mdx | 2 +- api_docs/kbn_ml_random_sampler_utils.mdx | 2 +- api_docs/kbn_ml_route_utils.mdx | 2 +- api_docs/kbn_ml_runtime_field_utils.mdx | 2 +- api_docs/kbn_ml_string_hash.mdx | 2 +- api_docs/kbn_ml_trained_models_utils.mdx | 2 +- api_docs/kbn_ml_ui_actions.mdx | 2 +- api_docs/kbn_ml_url_state.mdx | 2 +- api_docs/kbn_mock_idp_utils.mdx | 2 +- api_docs/kbn_monaco.mdx | 2 +- api_docs/kbn_object_versioning.mdx | 2 +- api_docs/kbn_observability_alert_details.mdx | 2 +- .../kbn_observability_alerting_test_data.mdx | 2 +- ...ility_get_padded_alert_time_range_util.mdx | 2 +- api_docs/kbn_openapi_bundler.mdx | 2 +- api_docs/kbn_openapi_generator.mdx | 2 +- api_docs/kbn_optimizer.mdx | 2 +- api_docs/kbn_optimizer_webpack_helpers.mdx | 2 +- api_docs/kbn_osquery_io_ts_types.mdx | 2 +- api_docs/kbn_panel_loader.mdx | 2 +- ..._performance_testing_dataset_extractor.mdx | 2 +- api_docs/kbn_plugin_check.mdx | 2 +- api_docs/kbn_plugin_generator.mdx | 2 +- api_docs/kbn_plugin_helpers.mdx | 2 +- .../kbn_presentation_containers.devdocs.json | 14 +- api_docs/kbn_presentation_containers.mdx | 2 +- api_docs/kbn_presentation_library.mdx | 2 +- .../kbn_presentation_publishing.devdocs.json | 70 + api_docs/kbn_presentation_publishing.mdx | 4 +- api_docs/kbn_profiling_utils.mdx | 2 +- api_docs/kbn_random_sampling.mdx | 2 +- api_docs/kbn_react_field.mdx | 2 +- api_docs/kbn_react_kibana_context_common.mdx | 2 +- api_docs/kbn_react_kibana_context_render.mdx | 2 +- api_docs/kbn_react_kibana_context_root.mdx | 2 +- api_docs/kbn_react_kibana_context_styled.mdx | 2 +- api_docs/kbn_react_kibana_context_theme.mdx | 2 +- api_docs/kbn_react_kibana_mount.mdx | 2 +- api_docs/kbn_repo_file_maps.mdx | 2 +- api_docs/kbn_repo_linter.mdx | 2 +- api_docs/kbn_repo_path.mdx | 2 +- api_docs/kbn_repo_source_classifier.mdx | 2 +- api_docs/kbn_reporting_common.mdx | 2 +- api_docs/kbn_reporting_export_types_csv.mdx | 2 +- .../kbn_reporting_export_types_csv_common.mdx | 2 +- api_docs/kbn_reporting_export_types_pdf.mdx | 2 +- .../kbn_reporting_export_types_pdf_common.mdx | 2 +- api_docs/kbn_reporting_export_types_png.mdx | 2 +- .../kbn_reporting_export_types_png_common.mdx | 2 +- api_docs/kbn_reporting_mocks_server.mdx | 2 +- api_docs/kbn_reporting_public.mdx | 2 +- api_docs/kbn_reporting_server.mdx | 2 +- api_docs/kbn_resizable_layout.mdx | 2 +- api_docs/kbn_rison.mdx | 2 +- api_docs/kbn_router_utils.mdx | 2 +- api_docs/kbn_rrule.mdx | 2 +- api_docs/kbn_rule_data_utils.mdx | 2 +- api_docs/kbn_saved_objects_settings.mdx | 2 +- api_docs/kbn_search_api_panels.mdx | 2 +- api_docs/kbn_search_connectors.mdx | 2 +- api_docs/kbn_search_errors.mdx | 2 +- api_docs/kbn_search_index_documents.mdx | 2 +- api_docs/kbn_search_response_warnings.mdx | 2 +- api_docs/kbn_security_hardening.mdx | 2 +- api_docs/kbn_security_plugin_types_common.mdx | 2 +- api_docs/kbn_security_plugin_types_public.mdx | 2 +- api_docs/kbn_security_plugin_types_server.mdx | 2 +- api_docs/kbn_security_solution_features.mdx | 2 +- api_docs/kbn_security_solution_navigation.mdx | 2 +- api_docs/kbn_security_solution_side_nav.mdx | 2 +- ...kbn_security_solution_storybook_config.mdx | 2 +- .../kbn_securitysolution_autocomplete.mdx | 2 +- api_docs/kbn_securitysolution_data_table.mdx | 2 +- api_docs/kbn_securitysolution_ecs.mdx | 2 +- api_docs/kbn_securitysolution_es_utils.mdx | 2 +- ...ritysolution_exception_list_components.mdx | 2 +- api_docs/kbn_securitysolution_grouping.mdx | 2 +- api_docs/kbn_securitysolution_hook_utils.mdx | 2 +- ..._securitysolution_io_ts_alerting_types.mdx | 2 +- .../kbn_securitysolution_io_ts_list_types.mdx | 2 +- api_docs/kbn_securitysolution_io_ts_types.mdx | 2 +- api_docs/kbn_securitysolution_io_ts_utils.mdx | 2 +- api_docs/kbn_securitysolution_list_api.mdx | 2 +- .../kbn_securitysolution_list_constants.mdx | 2 +- api_docs/kbn_securitysolution_list_hooks.mdx | 2 +- api_docs/kbn_securitysolution_list_utils.mdx | 2 +- api_docs/kbn_securitysolution_rules.mdx | 2 +- api_docs/kbn_securitysolution_t_grid.mdx | 2 +- api_docs/kbn_securitysolution_utils.mdx | 2 +- api_docs/kbn_server_http_tools.mdx | 2 +- api_docs/kbn_server_route_repository.mdx | 2 +- api_docs/kbn_serverless_common_settings.mdx | 2 +- .../kbn_serverless_observability_settings.mdx | 2 +- api_docs/kbn_serverless_project_switcher.mdx | 2 +- api_docs/kbn_serverless_search_settings.mdx | 2 +- api_docs/kbn_serverless_security_settings.mdx | 2 +- api_docs/kbn_serverless_storybook_config.mdx | 2 +- api_docs/kbn_shared_svg.mdx | 2 +- api_docs/kbn_shared_ux_avatar_solution.mdx | 2 +- .../kbn_shared_ux_button_exit_full_screen.mdx | 2 +- api_docs/kbn_shared_ux_button_toolbar.mdx | 2 +- api_docs/kbn_shared_ux_card_no_data.mdx | 2 +- api_docs/kbn_shared_ux_card_no_data_mocks.mdx | 2 +- api_docs/kbn_shared_ux_chrome_navigation.mdx | 2 +- api_docs/kbn_shared_ux_error_boundary.mdx | 2 +- api_docs/kbn_shared_ux_file_context.mdx | 2 +- api_docs/kbn_shared_ux_file_image.mdx | 2 +- api_docs/kbn_shared_ux_file_image_mocks.mdx | 2 +- api_docs/kbn_shared_ux_file_mocks.mdx | 2 +- api_docs/kbn_shared_ux_file_picker.mdx | 2 +- api_docs/kbn_shared_ux_file_types.mdx | 2 +- api_docs/kbn_shared_ux_file_upload.mdx | 2 +- api_docs/kbn_shared_ux_file_util.mdx | 2 +- api_docs/kbn_shared_ux_link_redirect_app.mdx | 2 +- .../kbn_shared_ux_link_redirect_app_mocks.mdx | 2 +- api_docs/kbn_shared_ux_markdown.mdx | 2 +- api_docs/kbn_shared_ux_markdown_mocks.mdx | 2 +- .../kbn_shared_ux_page_analytics_no_data.mdx | 2 +- ...shared_ux_page_analytics_no_data_mocks.mdx | 2 +- .../kbn_shared_ux_page_kibana_no_data.mdx | 2 +- ...bn_shared_ux_page_kibana_no_data_mocks.mdx | 2 +- .../kbn_shared_ux_page_kibana_template.mdx | 2 +- ...n_shared_ux_page_kibana_template_mocks.mdx | 2 +- api_docs/kbn_shared_ux_page_no_data.mdx | 2 +- .../kbn_shared_ux_page_no_data_config.mdx | 2 +- ...bn_shared_ux_page_no_data_config_mocks.mdx | 2 +- api_docs/kbn_shared_ux_page_no_data_mocks.mdx | 2 +- api_docs/kbn_shared_ux_page_solution_nav.mdx | 2 +- .../kbn_shared_ux_prompt_no_data_views.mdx | 2 +- ...n_shared_ux_prompt_no_data_views_mocks.mdx | 2 +- api_docs/kbn_shared_ux_prompt_not_found.mdx | 2 +- api_docs/kbn_shared_ux_router.mdx | 2 +- api_docs/kbn_shared_ux_router_mocks.mdx | 2 +- api_docs/kbn_shared_ux_storybook_config.mdx | 2 +- api_docs/kbn_shared_ux_storybook_mock.mdx | 2 +- api_docs/kbn_shared_ux_utility.mdx | 2 +- api_docs/kbn_slo_schema.mdx | 2 +- api_docs/kbn_some_dev_log.mdx | 2 +- api_docs/kbn_sort_predicates.mdx | 2 +- api_docs/kbn_std.mdx | 2 +- api_docs/kbn_stdio_dev_helpers.mdx | 2 +- api_docs/kbn_storybook.mdx | 2 +- api_docs/kbn_telemetry_tools.mdx | 2 +- api_docs/kbn_test.mdx | 2 +- api_docs/kbn_test_eui_helpers.mdx | 2 +- api_docs/kbn_test_jest_helpers.mdx | 2 +- api_docs/kbn_test_subj_selector.mdx | 2 +- api_docs/kbn_text_based_editor.mdx | 2 +- api_docs/kbn_tooling_log.mdx | 2 +- api_docs/kbn_triggers_actions_ui_types.mdx | 2 +- api_docs/kbn_ts_projects.mdx | 2 +- api_docs/kbn_typed_react_router_config.mdx | 2 +- api_docs/kbn_ui_actions_browser.mdx | 2 +- api_docs/kbn_ui_shared_deps_src.mdx | 2 +- api_docs/kbn_ui_theme.mdx | 2 +- api_docs/kbn_unified_data_table.devdocs.json | 334 ++++- api_docs/kbn_unified_data_table.mdx | 4 +- api_docs/kbn_unified_doc_viewer.mdx | 2 +- api_docs/kbn_unified_field_list.mdx | 2 +- api_docs/kbn_unsaved_changes_badge.mdx | 2 +- api_docs/kbn_use_tracked_promise.mdx | 2 +- api_docs/kbn_user_profile_components.mdx | 2 +- api_docs/kbn_utility_types.mdx | 2 +- api_docs/kbn_utility_types_jest.mdx | 2 +- api_docs/kbn_utils.mdx | 2 +- api_docs/kbn_visualization_ui_components.mdx | 2 +- api_docs/kbn_visualization_utils.mdx | 2 +- api_docs/kbn_xstate_utils.mdx | 2 +- api_docs/kbn_yarn_lock_validator.mdx | 2 +- api_docs/kbn_zod_helpers.mdx | 2 +- api_docs/kibana_overview.mdx | 2 +- api_docs/kibana_react.devdocs.json | 18 +- api_docs/kibana_react.mdx | 2 +- api_docs/kibana_utils.mdx | 2 +- api_docs/kubernetes_security.mdx | 2 +- api_docs/lens.devdocs.json | 4 +- api_docs/lens.mdx | 2 +- api_docs/license_api_guard.mdx | 2 +- api_docs/license_management.mdx | 2 +- api_docs/licensing.mdx | 2 +- api_docs/links.mdx | 2 +- api_docs/lists.mdx | 2 +- api_docs/logs_explorer.mdx | 2 +- api_docs/logs_shared.mdx | 2 +- api_docs/management.mdx | 2 +- api_docs/maps.mdx | 2 +- api_docs/maps_ems.mdx | 2 +- api_docs/metrics_data_access.mdx | 2 +- api_docs/ml.mdx | 2 +- api_docs/mock_idp_plugin.mdx | 2 +- api_docs/monitoring.mdx | 2 +- api_docs/monitoring_collection.mdx | 2 +- api_docs/navigation.mdx | 2 +- api_docs/newsfeed.mdx | 2 +- api_docs/no_data_page.mdx | 2 +- api_docs/notifications.mdx | 2 +- api_docs/observability.mdx | 2 +- api_docs/observability_a_i_assistant.mdx | 2 +- api_docs/observability_logs_explorer.mdx | 2 +- api_docs/observability_onboarding.mdx | 2 +- api_docs/observability_shared.mdx | 2 +- api_docs/osquery.mdx | 2 +- api_docs/painless_lab.mdx | 2 +- api_docs/plugin_directory.mdx | 24 +- api_docs/presentation_panel.devdocs.json | 8 +- api_docs/presentation_panel.mdx | 2 +- api_docs/presentation_util.mdx | 2 +- api_docs/profiling.mdx | 2 +- api_docs/profiling_data_access.mdx | 2 +- api_docs/remote_clusters.mdx | 2 +- api_docs/reporting.mdx | 2 +- api_docs/rollup.mdx | 2 +- api_docs/rule_registry.mdx | 2 +- api_docs/runtime_fields.mdx | 2 +- api_docs/saved_objects.mdx | 2 +- api_docs/saved_objects_finder.mdx | 2 +- api_docs/saved_objects_management.mdx | 2 +- api_docs/saved_objects_tagging.mdx | 2 +- api_docs/saved_objects_tagging_oss.mdx | 2 +- api_docs/saved_search.devdocs.json | 14 + api_docs/saved_search.mdx | 4 +- api_docs/screenshot_mode.mdx | 2 +- api_docs/screenshotting.mdx | 2 +- api_docs/security.mdx | 2 +- api_docs/security_solution.devdocs.json | 16 +- api_docs/security_solution.mdx | 2 +- api_docs/security_solution_ess.mdx | 2 +- api_docs/security_solution_serverless.mdx | 2 +- api_docs/serverless.mdx | 2 +- api_docs/serverless_observability.mdx | 2 +- api_docs/serverless_search.mdx | 2 +- api_docs/session_view.mdx | 2 +- api_docs/share.mdx | 2 +- api_docs/snapshot_restore.mdx | 2 +- api_docs/spaces.mdx | 2 +- api_docs/stack_alerts.mdx | 2 +- api_docs/stack_connectors.mdx | 2 +- api_docs/task_manager.mdx | 2 +- api_docs/telemetry.devdocs.json | 4 + api_docs/telemetry.mdx | 2 +- api_docs/telemetry_collection_manager.mdx | 2 +- api_docs/telemetry_collection_xpack.mdx | 2 +- api_docs/telemetry_management_section.mdx | 2 +- api_docs/text_based_languages.mdx | 2 +- api_docs/threat_intelligence.mdx | 2 +- api_docs/timelines.mdx | 2 +- api_docs/transform.mdx | 2 +- api_docs/triggers_actions_ui.devdocs.json | 14 + api_docs/triggers_actions_ui.mdx | 4 +- api_docs/ui_actions.mdx | 2 +- api_docs/ui_actions_enhanced.mdx | 2 +- api_docs/unified_doc_viewer.mdx | 2 +- api_docs/unified_histogram.mdx | 2 +- api_docs/unified_search.devdocs.json | 72 + api_docs/unified_search.mdx | 4 +- api_docs/unified_search_autocomplete.mdx | 4 +- api_docs/uptime.mdx | 2 +- api_docs/url_forwarding.mdx | 2 +- api_docs/usage_collection.mdx | 2 +- api_docs/ux.mdx | 2 +- api_docs/vis_default_editor.mdx | 2 +- api_docs/vis_type_gauge.mdx | 2 +- api_docs/vis_type_heatmap.mdx | 2 +- api_docs/vis_type_pie.mdx | 2 +- api_docs/vis_type_table.mdx | 2 +- api_docs/vis_type_timelion.mdx | 2 +- api_docs/vis_type_timeseries.mdx | 2 +- api_docs/vis_type_vega.mdx | 2 +- api_docs/vis_type_vislib.mdx | 2 +- api_docs/vis_type_xy.mdx | 2 +- api_docs/visualizations.devdocs.json | 46 +- api_docs/visualizations.mdx | 2 +- 674 files changed, 1555 insertions(+), 2180 deletions(-) diff --git a/api_docs/actions.mdx b/api_docs/actions.mdx index 5335996a00da4..56813e780ccae 100644 --- a/api_docs/actions.mdx +++ b/api_docs/actions.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/actions title: "actions" image: https://source.unsplash.com/400x175/?github description: API docs for the actions plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'actions'] --- import actionsObj from './actions.devdocs.json'; diff --git a/api_docs/advanced_settings.mdx b/api_docs/advanced_settings.mdx index 0de4edbfd4b8e..8280a97053053 100644 --- a/api_docs/advanced_settings.mdx +++ b/api_docs/advanced_settings.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/advancedSettings title: "advancedSettings" image: https://source.unsplash.com/400x175/?github description: API docs for the advancedSettings plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'advancedSettings'] --- import advancedSettingsObj from './advanced_settings.devdocs.json'; diff --git a/api_docs/ai_assistant_management_observability.mdx b/api_docs/ai_assistant_management_observability.mdx index 466acb2ef9875..09230320294a1 100644 --- a/api_docs/ai_assistant_management_observability.mdx +++ b/api_docs/ai_assistant_management_observability.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/aiAssistantManagementObservability title: "aiAssistantManagementObservability" image: https://source.unsplash.com/400x175/?github description: API docs for the aiAssistantManagementObservability plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'aiAssistantManagementObservability'] --- import aiAssistantManagementObservabilityObj from './ai_assistant_management_observability.devdocs.json'; diff --git a/api_docs/ai_assistant_management_selection.mdx b/api_docs/ai_assistant_management_selection.mdx index cf6aec47ee098..d40d656d103a8 100644 --- a/api_docs/ai_assistant_management_selection.mdx +++ b/api_docs/ai_assistant_management_selection.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/aiAssistantManagementSelection title: "aiAssistantManagementSelection" image: https://source.unsplash.com/400x175/?github description: API docs for the aiAssistantManagementSelection plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'aiAssistantManagementSelection'] --- import aiAssistantManagementSelectionObj from './ai_assistant_management_selection.devdocs.json'; diff --git a/api_docs/aiops.mdx b/api_docs/aiops.mdx index db453f5dde714..63538d56c20b9 100644 --- a/api_docs/aiops.mdx +++ b/api_docs/aiops.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/aiops title: "aiops" image: https://source.unsplash.com/400x175/?github description: API docs for the aiops plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'aiops'] --- import aiopsObj from './aiops.devdocs.json'; diff --git a/api_docs/alerting.devdocs.json b/api_docs/alerting.devdocs.json index dddb209e94645..e0f34b5e58cef 100644 --- a/api_docs/alerting.devdocs.json +++ b/api_docs/alerting.devdocs.json @@ -5102,7 +5102,7 @@ "section": "def-common.RuleTypeParams", "text": "RuleTypeParams" }, - ">, \"id\" | \"snoozeSchedule\">; version?: string | undefined; }) => Promise; unmuteAll: (options: { id: string; }) => Promise; muteInstance: (options: Readonly<{} & { alertId: string; alertInstanceId: string; }>) => Promise; unmuteInstance: (options: Readonly<{} & { alertId: string; alertInstanceId: string; }>) => Promise; bulkUntrackAlerts: (options: Readonly<{} & { indices: string[]; alertUuids: string[]; }>) => Promise; runSoon: (options: { id: string; }) => Promise; listRuleTypes: () => Promise, \"id\" | \"snoozeSchedule\">; version?: string | undefined; }) => Promise; unmuteAll: (options: { id: string; }) => Promise; muteInstance: (options: Readonly<{} & { alertId: string; alertInstanceId: string; }>) => Promise; unmuteInstance: (options: Readonly<{} & { alertId: string; alertInstanceId: string; }>) => Promise; bulkUntrackAlerts: (options: Readonly<{ indices?: string[] | undefined; alertUuids?: string[] | undefined; query?: any[] | undefined; featureIds?: string[] | undefined; } & { isUsingQuery: boolean; }>) => Promise; runSoon: (options: { id: string; }) => Promise; listRuleTypes: () => Promise>; getSpaceId: () => string | undefined; getAuthorization: () => ", { diff --git a/api_docs/alerting.mdx b/api_docs/alerting.mdx index 0337f6c6db5c7..68b544b490643 100644 --- a/api_docs/alerting.mdx +++ b/api_docs/alerting.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/alerting title: "alerting" image: https://source.unsplash.com/400x175/?github description: API docs for the alerting plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'alerting'] --- import alertingObj from './alerting.devdocs.json'; diff --git a/api_docs/apm.mdx b/api_docs/apm.mdx index 537a347f0d447..94253d79f29ab 100644 --- a/api_docs/apm.mdx +++ b/api_docs/apm.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/apm title: "apm" image: https://source.unsplash.com/400x175/?github description: API docs for the apm plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'apm'] --- import apmObj from './apm.devdocs.json'; diff --git a/api_docs/apm_data_access.mdx b/api_docs/apm_data_access.mdx index 66d94eb573387..9b4d464933299 100644 --- a/api_docs/apm_data_access.mdx +++ b/api_docs/apm_data_access.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/apmDataAccess title: "apmDataAccess" image: https://source.unsplash.com/400x175/?github description: API docs for the apmDataAccess plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'apmDataAccess'] --- import apmDataAccessObj from './apm_data_access.devdocs.json'; diff --git a/api_docs/asset_manager.mdx b/api_docs/asset_manager.mdx index 0808be24676bb..7e797ca1c89ff 100644 --- a/api_docs/asset_manager.mdx +++ b/api_docs/asset_manager.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/assetManager title: "assetManager" image: https://source.unsplash.com/400x175/?github description: API docs for the assetManager plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'assetManager'] --- import assetManagerObj from './asset_manager.devdocs.json'; diff --git a/api_docs/banners.mdx b/api_docs/banners.mdx index 2a7c4cac0b3eb..aa522193bcc14 100644 --- a/api_docs/banners.mdx +++ b/api_docs/banners.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/banners title: "banners" image: https://source.unsplash.com/400x175/?github description: API docs for the banners plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'banners'] --- import bannersObj from './banners.devdocs.json'; diff --git a/api_docs/bfetch.mdx b/api_docs/bfetch.mdx index 9af5b20517ca7..74377eebbf31d 100644 --- a/api_docs/bfetch.mdx +++ b/api_docs/bfetch.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/bfetch title: "bfetch" image: https://source.unsplash.com/400x175/?github description: API docs for the bfetch plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'bfetch'] --- import bfetchObj from './bfetch.devdocs.json'; diff --git a/api_docs/canvas.mdx b/api_docs/canvas.mdx index dd2de6ba367ec..0329b8a1f8017 100644 --- a/api_docs/canvas.mdx +++ b/api_docs/canvas.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/canvas title: "canvas" image: https://source.unsplash.com/400x175/?github description: API docs for the canvas plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'canvas'] --- import canvasObj from './canvas.devdocs.json'; diff --git a/api_docs/cases.mdx b/api_docs/cases.mdx index 028b45c94381c..42ab70fb6bb53 100644 --- a/api_docs/cases.mdx +++ b/api_docs/cases.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/cases title: "cases" image: https://source.unsplash.com/400x175/?github description: API docs for the cases plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'cases'] --- import casesObj from './cases.devdocs.json'; diff --git a/api_docs/charts.mdx b/api_docs/charts.mdx index 3e23f1bd985bf..4a1d31fab4de3 100644 --- a/api_docs/charts.mdx +++ b/api_docs/charts.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/charts title: "charts" image: https://source.unsplash.com/400x175/?github description: API docs for the charts plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'charts'] --- import chartsObj from './charts.devdocs.json'; diff --git a/api_docs/cloud.mdx b/api_docs/cloud.mdx index 6283f37938a16..2d1bd0fff24ed 100644 --- a/api_docs/cloud.mdx +++ b/api_docs/cloud.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/cloud title: "cloud" image: https://source.unsplash.com/400x175/?github description: API docs for the cloud plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'cloud'] --- import cloudObj from './cloud.devdocs.json'; diff --git a/api_docs/cloud_data_migration.mdx b/api_docs/cloud_data_migration.mdx index 52a47937aaefa..44f91181281d7 100644 --- a/api_docs/cloud_data_migration.mdx +++ b/api_docs/cloud_data_migration.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/cloudDataMigration title: "cloudDataMigration" image: https://source.unsplash.com/400x175/?github description: API docs for the cloudDataMigration plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'cloudDataMigration'] --- import cloudDataMigrationObj from './cloud_data_migration.devdocs.json'; diff --git a/api_docs/cloud_defend.mdx b/api_docs/cloud_defend.mdx index 33ffda83932f3..3530263fc461a 100644 --- a/api_docs/cloud_defend.mdx +++ b/api_docs/cloud_defend.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/cloudDefend title: "cloudDefend" image: https://source.unsplash.com/400x175/?github description: API docs for the cloudDefend plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'cloudDefend'] --- import cloudDefendObj from './cloud_defend.devdocs.json'; diff --git a/api_docs/cloud_experiments.mdx b/api_docs/cloud_experiments.mdx index 9db539e0e6989..1b42a0a0dcd8b 100644 --- a/api_docs/cloud_experiments.mdx +++ b/api_docs/cloud_experiments.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/cloudExperiments title: "cloudExperiments" image: https://source.unsplash.com/400x175/?github description: API docs for the cloudExperiments plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'cloudExperiments'] --- import cloudExperimentsObj from './cloud_experiments.devdocs.json'; diff --git a/api_docs/cloud_security_posture.mdx b/api_docs/cloud_security_posture.mdx index 1f8838191f66f..0e5b5516cb292 100644 --- a/api_docs/cloud_security_posture.mdx +++ b/api_docs/cloud_security_posture.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/cloudSecurityPosture title: "cloudSecurityPosture" image: https://source.unsplash.com/400x175/?github description: API docs for the cloudSecurityPosture plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'cloudSecurityPosture'] --- import cloudSecurityPostureObj from './cloud_security_posture.devdocs.json'; diff --git a/api_docs/console.mdx b/api_docs/console.mdx index 6d9de790a6e0e..e32987f314ba9 100644 --- a/api_docs/console.mdx +++ b/api_docs/console.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/console title: "console" image: https://source.unsplash.com/400x175/?github description: API docs for the console plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'console'] --- import consoleObj from './console.devdocs.json'; diff --git a/api_docs/content_management.mdx b/api_docs/content_management.mdx index f38f895a0130b..b20659d254b2f 100644 --- a/api_docs/content_management.mdx +++ b/api_docs/content_management.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/contentManagement title: "contentManagement" image: https://source.unsplash.com/400x175/?github description: API docs for the contentManagement plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'contentManagement'] --- import contentManagementObj from './content_management.devdocs.json'; diff --git a/api_docs/controls.mdx b/api_docs/controls.mdx index 18d97fa6e8149..0eb23d2d5c80c 100644 --- a/api_docs/controls.mdx +++ b/api_docs/controls.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/controls title: "controls" image: https://source.unsplash.com/400x175/?github description: API docs for the controls plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'controls'] --- import controlsObj from './controls.devdocs.json'; diff --git a/api_docs/custom_integrations.mdx b/api_docs/custom_integrations.mdx index 230aee420a98e..edf1891aa1b5a 100644 --- a/api_docs/custom_integrations.mdx +++ b/api_docs/custom_integrations.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/customIntegrations title: "customIntegrations" image: https://source.unsplash.com/400x175/?github description: API docs for the customIntegrations plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'customIntegrations'] --- import customIntegrationsObj from './custom_integrations.devdocs.json'; diff --git a/api_docs/dashboard.devdocs.json b/api_docs/dashboard.devdocs.json index 8a5e494119338..361449f99a883 100644 --- a/api_docs/dashboard.devdocs.json +++ b/api_docs/dashboard.devdocs.json @@ -204,23 +204,31 @@ "label": "getDashboardLocatorParamsFromEmbeddable", "description": [], "signature": [ - "(source: ", + "(api: Partial<", { - "pluginId": "embeddable", - "scope": "public", - "docId": "kibEmbeddablePluginApi", - "section": "def-public.IEmbeddable", - "text": "IEmbeddable" + "pluginId": "@kbn/presentation-publishing", + "scope": "common", + "docId": "kibKbnPresentationPublishingPluginApi", + "section": "def-common.PublishesLocalUnifiedSearch", + "text": "PublishesLocalUnifiedSearch" }, - ", options: ", + ">>>, options: ", { "pluginId": "presentationUtil", "scope": "public", @@ -247,25 +255,34 @@ "id": "def-public.getDashboardLocatorParamsFromEmbeddable.$1", "type": "Object", "tags": [], - "label": "source", + "label": "api", "description": [], "signature": [ + "Partial<", { - "pluginId": "embeddable", - "scope": "public", - "docId": "kibEmbeddablePluginApi", - "section": "def-public.IEmbeddable", - "text": "IEmbeddable" + "pluginId": "@kbn/presentation-publishing", + "scope": "common", + "docId": "kibKbnPresentationPublishingPluginApi", + "section": "def-common.PublishesLocalUnifiedSearch", + "text": "PublishesLocalUnifiedSearch" }, - "" + ">>>" ], "path": "src/plugins/dashboard/public/dashboard_app/locator/get_dashboard_locator_params.ts", "deprecated": false, @@ -2010,7 +2027,7 @@ "\nFor BWC reasons, dashboard state is stored with panels as an array instead of a map" ], "signature": [ - "{ id?: string | undefined; tags?: string[] | undefined; title?: string | undefined; version?: string | undefined; query?: ", + "{ id?: string | undefined; tags?: string[] | undefined; title?: string | undefined; query?: ", { "pluginId": "@kbn/es-query", "scope": "common", @@ -2018,7 +2035,7 @@ "section": "def-common.Query", "text": "Query" }, - " | undefined; filters?: ", + " | undefined; version?: string | undefined; filters?: ", { "pluginId": "@kbn/es-query", "scope": "common", diff --git a/api_docs/dashboard.mdx b/api_docs/dashboard.mdx index c5439ce28bea6..4f7f7db7932bc 100644 --- a/api_docs/dashboard.mdx +++ b/api_docs/dashboard.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/dashboard title: "dashboard" image: https://source.unsplash.com/400x175/?github description: API docs for the dashboard plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'dashboard'] --- import dashboardObj from './dashboard.devdocs.json'; diff --git a/api_docs/dashboard_enhanced.mdx b/api_docs/dashboard_enhanced.mdx index f33b0045e9e98..b1ce8e0a61334 100644 --- a/api_docs/dashboard_enhanced.mdx +++ b/api_docs/dashboard_enhanced.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/dashboardEnhanced title: "dashboardEnhanced" image: https://source.unsplash.com/400x175/?github description: API docs for the dashboardEnhanced plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'dashboardEnhanced'] --- import dashboardEnhancedObj from './dashboard_enhanced.devdocs.json'; diff --git a/api_docs/data.mdx b/api_docs/data.mdx index fb1fb0532bb40..a31046091d499 100644 --- a/api_docs/data.mdx +++ b/api_docs/data.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/data title: "data" image: https://source.unsplash.com/400x175/?github description: API docs for the data plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'data'] --- import dataObj from './data.devdocs.json'; @@ -21,7 +21,7 @@ Contact [@elastic/kibana-visualizations](https://github.com/orgs/elastic/teams/k | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 3237 | 31 | 2585 | 23 | +| 3239 | 31 | 2587 | 23 | ## Client diff --git a/api_docs/data_query.devdocs.json b/api_docs/data_query.devdocs.json index 68ae04e7173c3..6d06aa9a9655e 100644 --- a/api_docs/data_query.devdocs.json +++ b/api_docs/data_query.devdocs.json @@ -1460,7 +1460,7 @@ "section": "def-common.HttpSetup", "text": "HttpSetup" }, - ") => { createQuery: (attributes: ", + ") => { isDuplicateTitle: (title: string, id?: string | undefined) => Promise; createQuery: (attributes: ", { "pluginId": "data", "scope": "common", @@ -1492,15 +1492,7 @@ "section": "def-common.SavedQuery", "text": "SavedQuery" }, - ">; getAllSavedQueries: () => Promise<", - { - "pluginId": "data", - "scope": "common", - "docId": "kibDataQueryPluginApi", - "section": "def-common.SavedQuery", - "text": "SavedQuery" - }, - "[]>; findSavedQueries: (search?: string, perPage?: number, page?: number) => Promise<{ total: number; queries: ", + ">; findSavedQueries: (search?: string, perPage?: number, page?: number) => Promise<{ total: number; queries: ", { "pluginId": "data", "scope": "common", @@ -2840,7 +2832,7 @@ "label": "savedQueries", "description": [], "signature": [ - "{ createQuery: (attributes: ", + "{ isDuplicateTitle: (title: string, id?: string | undefined) => Promise; createQuery: (attributes: ", { "pluginId": "data", "scope": "common", @@ -2872,15 +2864,7 @@ "section": "def-common.SavedQuery", "text": "SavedQuery" }, - ">; getAllSavedQueries: () => Promise<", - { - "pluginId": "data", - "scope": "common", - "docId": "kibDataQueryPluginApi", - "section": "def-common.SavedQuery", - "text": "SavedQuery" - }, - "[]>; findSavedQueries: (search?: string, perPage?: number, page?: number) => Promise<{ total: number; queries: ", + ">; findSavedQueries: (search?: string, perPage?: number, page?: number) => Promise<{ total: number; queries: ", { "pluginId": "data", "scope": "common", @@ -3052,6 +3036,53 @@ "deprecated": false, "trackAdoption": false, "children": [ + { + "parentPluginId": "data", + "id": "def-public.SavedQueryService.isDuplicateTitle", + "type": "Function", + "tags": [], + "label": "isDuplicateTitle", + "description": [], + "signature": [ + "(title: string, id?: string | undefined) => Promise" + ], + "path": "src/plugins/data/public/query/saved_query/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "data", + "id": "def-public.SavedQueryService.isDuplicateTitle.$1", + "type": "string", + "tags": [], + "label": "title", + "description": [], + "signature": [ + "string" + ], + "path": "src/plugins/data/public/query/saved_query/types.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + }, + { + "parentPluginId": "data", + "id": "def-public.SavedQueryService.isDuplicateTitle.$2", + "type": "string", + "tags": [], + "label": "id", + "description": [], + "signature": [ + "string | undefined" + ], + "path": "src/plugins/data/public/query/saved_query/types.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": false + } + ], + "returnComment": [] + }, { "parentPluginId": "data", "id": "def-public.SavedQueryService.createQuery", @@ -3175,30 +3206,6 @@ ], "returnComment": [] }, - { - "parentPluginId": "data", - "id": "def-public.SavedQueryService.getAllSavedQueries", - "type": "Function", - "tags": [], - "label": "getAllSavedQueries", - "description": [], - "signature": [ - "() => Promise<", - { - "pluginId": "data", - "scope": "common", - "docId": "kibDataQueryPluginApi", - "section": "def-common.SavedQuery", - "text": "SavedQuery" - }, - "[]>" - ], - "path": "src/plugins/data/public/query/saved_query/types.ts", - "deprecated": false, - "trackAdoption": false, - "children": [], - "returnComment": [] - }, { "parentPluginId": "data", "id": "def-public.SavedQueryService.findSavedQueries", diff --git a/api_docs/data_query.mdx b/api_docs/data_query.mdx index c6be47430c2f0..0af57905fd88b 100644 --- a/api_docs/data_query.mdx +++ b/api_docs/data_query.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/data-query title: "data.query" image: https://source.unsplash.com/400x175/?github description: API docs for the data.query plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'data.query'] --- import dataQueryObj from './data_query.devdocs.json'; @@ -21,7 +21,7 @@ Contact [@elastic/kibana-visualizations](https://github.com/orgs/elastic/teams/k | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 3237 | 31 | 2585 | 23 | +| 3239 | 31 | 2587 | 23 | ## Client diff --git a/api_docs/data_search.mdx b/api_docs/data_search.mdx index 2ebc54b681b04..bcfc692fda279 100644 --- a/api_docs/data_search.mdx +++ b/api_docs/data_search.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/data-search title: "data.search" image: https://source.unsplash.com/400x175/?github description: API docs for the data.search plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'data.search'] --- import dataSearchObj from './data_search.devdocs.json'; @@ -21,7 +21,7 @@ Contact [@elastic/kibana-visualizations](https://github.com/orgs/elastic/teams/k | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 3237 | 31 | 2585 | 23 | +| 3239 | 31 | 2587 | 23 | ## Client diff --git a/api_docs/data_view_editor.mdx b/api_docs/data_view_editor.mdx index 244f739918ed6..a1d8577d1fe25 100644 --- a/api_docs/data_view_editor.mdx +++ b/api_docs/data_view_editor.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/dataViewEditor title: "dataViewEditor" image: https://source.unsplash.com/400x175/?github description: API docs for the dataViewEditor plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'dataViewEditor'] --- import dataViewEditorObj from './data_view_editor.devdocs.json'; diff --git a/api_docs/data_view_field_editor.mdx b/api_docs/data_view_field_editor.mdx index 580bd57c71b94..128f10817f585 100644 --- a/api_docs/data_view_field_editor.mdx +++ b/api_docs/data_view_field_editor.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/dataViewFieldEditor title: "dataViewFieldEditor" image: https://source.unsplash.com/400x175/?github description: API docs for the dataViewFieldEditor plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'dataViewFieldEditor'] --- import dataViewFieldEditorObj from './data_view_field_editor.devdocs.json'; diff --git a/api_docs/data_view_management.mdx b/api_docs/data_view_management.mdx index 9b6da54a22d75..baf48aeeec799 100644 --- a/api_docs/data_view_management.mdx +++ b/api_docs/data_view_management.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/dataViewManagement title: "dataViewManagement" image: https://source.unsplash.com/400x175/?github description: API docs for the dataViewManagement plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'dataViewManagement'] --- import dataViewManagementObj from './data_view_management.devdocs.json'; diff --git a/api_docs/data_views.mdx b/api_docs/data_views.mdx index 388572586381a..9fed3885888cc 100644 --- a/api_docs/data_views.mdx +++ b/api_docs/data_views.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/dataViews title: "dataViews" image: https://source.unsplash.com/400x175/?github description: API docs for the dataViews plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'dataViews'] --- import dataViewsObj from './data_views.devdocs.json'; diff --git a/api_docs/data_visualizer.mdx b/api_docs/data_visualizer.mdx index 1bbe52563efa7..c2150483cda46 100644 --- a/api_docs/data_visualizer.mdx +++ b/api_docs/data_visualizer.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/dataVisualizer title: "dataVisualizer" image: https://source.unsplash.com/400x175/?github description: API docs for the dataVisualizer plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'dataVisualizer'] --- import dataVisualizerObj from './data_visualizer.devdocs.json'; diff --git a/api_docs/dataset_quality.mdx b/api_docs/dataset_quality.mdx index d67d5b75ad2ac..65040bd3c1f6b 100644 --- a/api_docs/dataset_quality.mdx +++ b/api_docs/dataset_quality.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/datasetQuality title: "datasetQuality" image: https://source.unsplash.com/400x175/?github description: API docs for the datasetQuality plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'datasetQuality'] --- import datasetQualityObj from './dataset_quality.devdocs.json'; diff --git a/api_docs/deprecations_by_api.mdx b/api_docs/deprecations_by_api.mdx index 5dc7c3e90bb2d..36deb8c8839e2 100644 --- a/api_docs/deprecations_by_api.mdx +++ b/api_docs/deprecations_by_api.mdx @@ -7,7 +7,7 @@ id: kibDevDocsDeprecationsByApi slug: /kibana-dev-docs/api-meta/deprecated-api-list-by-api title: Deprecated API usage by API description: A list of deprecated APIs, which plugins are still referencing them, and when they need to be removed by. -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana'] --- @@ -110,6 +110,8 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | dataViewManagement | - | | | unifiedSearch | - | | | unifiedSearch | - | +| | embeddableEnhanced, discoverEnhanced | - | +| | dashboardEnhanced | - | | | visTypeGauge | - | | | visTypePie | - | | | visTypePie | - | diff --git a/api_docs/deprecations_by_plugin.mdx b/api_docs/deprecations_by_plugin.mdx index 408bea573c62d..fc2af60ef360d 100644 --- a/api_docs/deprecations_by_plugin.mdx +++ b/api_docs/deprecations_by_plugin.mdx @@ -7,7 +7,7 @@ id: kibDevDocsDeprecationsByPlugin slug: /kibana-dev-docs/api-meta/deprecated-api-list-by-plugin title: Deprecated API usage by plugin description: A list of deprecated APIs, which plugins are still referencing them, and when they need to be removed by. -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana'] --- @@ -507,6 +507,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | Deprecated API | Reference location(s) | Remove By | | ---------------|-----------|-----------| +| | [menu_item.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_edit_drilldown/menu_item.tsx#:~:text=EnhancedEmbeddableContext), [menu_item.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_edit_drilldown/menu_item.tsx#:~:text=EnhancedEmbeddableContext) | - | | | [flyout_create_drilldown.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_create_drilldown/flyout_create_drilldown.tsx#:~:text=toMountPoint), [flyout_create_drilldown.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_create_drilldown/flyout_create_drilldown.tsx#:~:text=toMountPoint), [flyout_edit_drilldown.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_edit_drilldown/flyout_edit_drilldown.tsx#:~:text=toMountPoint), [flyout_edit_drilldown.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_edit_drilldown/flyout_edit_drilldown.tsx#:~:text=toMountPoint) | - | | | [collect_config_container.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/abstract_dashboard_drilldown/components/collect_config_container.tsx#:~:text=savedObjects), [collect_config_container.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/abstract_dashboard_drilldown/components/collect_config_container.tsx#:~:text=savedObjects) | - | | | [collect_config_container.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/abstract_dashboard_drilldown/components/collect_config_container.tsx#:~:text=find) | - | @@ -583,7 +584,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | ---------------|-----------|-----------| | | [document_stats.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/data_visualizer/public/application/common/components/stats_table/components/field_data_row/document_stats.tsx#:~:text=fieldFormats), [distinct_values.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/data_visualizer/public/application/common/components/stats_table/components/field_data_row/distinct_values.tsx#:~:text=fieldFormats), [top_values.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/data_visualizer/public/application/common/components/top_values/top_values.tsx#:~:text=fieldFormats), [choropleth_map.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/data_visualizer/public/application/common/components/stats_table/components/field_data_expanded_row/choropleth_map.tsx#:~:text=fieldFormats), [default_value_formatter.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/data_visualizer/public/application/data_drift/charts/default_value_formatter.ts#:~:text=fieldFormats) | - | | | [results_links.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/data_visualizer/public/application/common/components/results_links/results_links.tsx#:~:text=indexPatternId), [actions_panel.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/components/actions_panel/actions_panel.tsx#:~:text=indexPatternId) | - | -| | [file_data_visualizer.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/data_visualizer/public/application/file_data_visualizer/file_data_visualizer.tsx#:~:text=KibanaThemeProvider), [file_data_visualizer.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/data_visualizer/public/application/file_data_visualizer/file_data_visualizer.tsx#:~:text=KibanaThemeProvider), [file_data_visualizer.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/data_visualizer/public/application/file_data_visualizer/file_data_visualizer.tsx#:~:text=KibanaThemeProvider), [grid_embeddable.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/embeddables/grid_embeddable/grid_embeddable.tsx#:~:text=KibanaThemeProvider), [grid_embeddable.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/embeddables/grid_embeddable/grid_embeddable.tsx#:~:text=KibanaThemeProvider), [grid_embeddable.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/embeddables/grid_embeddable/grid_embeddable.tsx#:~:text=KibanaThemeProvider), [index_data_visualizer.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/index_data_visualizer.tsx#:~:text=KibanaThemeProvider), [index_data_visualizer.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/index_data_visualizer.tsx#:~:text=KibanaThemeProvider), [index_data_visualizer.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/index_data_visualizer.tsx#:~:text=KibanaThemeProvider), [data_drift_app_state.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/data_visualizer/public/application/data_drift/data_drift_app_state.tsx#:~:text=KibanaThemeProvider)+ 2 more | - | +| | [file_data_visualizer.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/data_visualizer/public/application/file_data_visualizer/file_data_visualizer.tsx#:~:text=KibanaThemeProvider), [file_data_visualizer.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/data_visualizer/public/application/file_data_visualizer/file_data_visualizer.tsx#:~:text=KibanaThemeProvider), [file_data_visualizer.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/data_visualizer/public/application/file_data_visualizer/file_data_visualizer.tsx#:~:text=KibanaThemeProvider), [index_data_visualizer.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/index_data_visualizer.tsx#:~:text=KibanaThemeProvider), [index_data_visualizer.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/index_data_visualizer.tsx#:~:text=KibanaThemeProvider), [index_data_visualizer.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/index_data_visualizer.tsx#:~:text=KibanaThemeProvider), [data_drift_app_state.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/data_visualizer/public/application/data_drift/data_drift_app_state.tsx#:~:text=KibanaThemeProvider), [data_drift_app_state.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/data_visualizer/public/application/data_drift/data_drift_app_state.tsx#:~:text=KibanaThemeProvider), [data_drift_app_state.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/data_visualizer/public/application/data_drift/data_drift_app_state.tsx#:~:text=KibanaThemeProvider), [grid_embeddable.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/embeddables/grid_embeddable/grid_embeddable.tsx#:~:text=KibanaThemeProvider)+ 2 more | - | | | [index_data_visualizer.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/index_data_visualizer.tsx#:~:text=savedObjects) | - | | | [index.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/data_visualizer/common/types/index.ts#:~:text=SimpleSavedObject), [index.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/data_visualizer/common/types/index.ts#:~:text=SimpleSavedObject) | - | @@ -616,6 +617,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | Deprecated API | Reference location(s) | Remove By | | ---------------|-----------|-----------| | | [explore_data_context_menu_action.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/discover_enhanced/public/actions/explore_data/explore_data_context_menu_action.ts#:~:text=indexPatternId), [explore_data_chart_action.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/discover_enhanced/public/actions/explore_data/explore_data_chart_action.ts#:~:text=indexPatternId) | - | +| | [explore_data_context_menu_action.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/discover_enhanced/public/actions/explore_data/explore_data_context_menu_action.ts#:~:text=EmbeddableContext), [explore_data_context_menu_action.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/discover_enhanced/public/actions/explore_data/explore_data_context_menu_action.ts#:~:text=EmbeddableContext) | - | @@ -631,6 +633,14 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] +## embeddableEnhanced + +| Deprecated API | Reference location(s) | Remove By | +| ---------------|-----------|-----------| +| | [plugin.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/embeddable_enhanced/public/plugin.ts#:~:text=EmbeddableContext), [plugin.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/embeddable_enhanced/public/plugin.ts#:~:text=EmbeddableContext), [plugin.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/embeddable_enhanced/public/plugin.ts#:~:text=EmbeddableContext) | - | + + + ## encryptedSavedObjects | Deprecated API | Reference location(s) | Remove By | diff --git a/api_docs/deprecations_by_team.mdx b/api_docs/deprecations_by_team.mdx index 271210f9c8af6..219cc401ba6d3 100644 --- a/api_docs/deprecations_by_team.mdx +++ b/api_docs/deprecations_by_team.mdx @@ -7,7 +7,7 @@ id: kibDevDocsDeprecationsDueByTeam slug: /kibana-dev-docs/api-meta/deprecations-due-by-team title: Deprecated APIs due to be removed, by team description: Lists the teams that are referencing deprecated APIs with a remove by date. -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana'] --- diff --git a/api_docs/dev_tools.mdx b/api_docs/dev_tools.mdx index 7fb22efdfe0bf..c8e56dfd58230 100644 --- a/api_docs/dev_tools.mdx +++ b/api_docs/dev_tools.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/devTools title: "devTools" image: https://source.unsplash.com/400x175/?github description: API docs for the devTools plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'devTools'] --- import devToolsObj from './dev_tools.devdocs.json'; diff --git a/api_docs/discover.devdocs.json b/api_docs/discover.devdocs.json index 60885dad84dc3..ca132c40f7d5a 100644 --- a/api_docs/discover.devdocs.json +++ b/api_docs/discover.devdocs.json @@ -401,6 +401,22 @@ "deprecated": false, "trackAdoption": false }, + { + "parentPluginId": "discover", + "id": "def-public.DiscoverAppState.headerRowHeight", + "type": "number", + "tags": [], + "label": "headerRowHeight", + "description": [ + "\nDocument explorer header row height option" + ], + "signature": [ + "number | undefined" + ], + "path": "src/plugins/discover/public/application/main/services/discover_app_state_container.ts", + "deprecated": false, + "trackAdoption": false + }, { "parentPluginId": "discover", "id": "def-public.DiscoverAppState.rowsPerPage", diff --git a/api_docs/discover.mdx b/api_docs/discover.mdx index c5caa8eecb980..c4e97b6439644 100644 --- a/api_docs/discover.mdx +++ b/api_docs/discover.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/discover title: "discover" image: https://source.unsplash.com/400x175/?github description: API docs for the discover plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'discover'] --- import discoverObj from './discover.devdocs.json'; @@ -21,7 +21,7 @@ Contact [@elastic/kibana-data-discovery](https://github.com/orgs/elastic/teams/k | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 156 | 0 | 109 | 23 | +| 157 | 0 | 109 | 23 | ## Client diff --git a/api_docs/discover_enhanced.mdx b/api_docs/discover_enhanced.mdx index 2891eee667f07..d6e75c0500e4e 100644 --- a/api_docs/discover_enhanced.mdx +++ b/api_docs/discover_enhanced.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/discoverEnhanced title: "discoverEnhanced" image: https://source.unsplash.com/400x175/?github description: API docs for the discoverEnhanced plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'discoverEnhanced'] --- import discoverEnhancedObj from './discover_enhanced.devdocs.json'; diff --git a/api_docs/ecs_data_quality_dashboard.mdx b/api_docs/ecs_data_quality_dashboard.mdx index 31b927f94f3f1..a6ddc90085de5 100644 --- a/api_docs/ecs_data_quality_dashboard.mdx +++ b/api_docs/ecs_data_quality_dashboard.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/ecsDataQualityDashboard title: "ecsDataQualityDashboard" image: https://source.unsplash.com/400x175/?github description: API docs for the ecsDataQualityDashboard plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'ecsDataQualityDashboard'] --- import ecsDataQualityDashboardObj from './ecs_data_quality_dashboard.devdocs.json'; diff --git a/api_docs/elastic_assistant.mdx b/api_docs/elastic_assistant.mdx index 38453150fe5ca..d96e812fc321d 100644 --- a/api_docs/elastic_assistant.mdx +++ b/api_docs/elastic_assistant.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/elasticAssistant title: "elasticAssistant" image: https://source.unsplash.com/400x175/?github description: API docs for the elasticAssistant plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'elasticAssistant'] --- import elasticAssistantObj from './elastic_assistant.devdocs.json'; diff --git a/api_docs/embeddable.devdocs.json b/api_docs/embeddable.devdocs.json index fc380952c04d6..2d4f65109a84c 100644 --- a/api_docs/embeddable.devdocs.json +++ b/api_docs/embeddable.devdocs.json @@ -2619,7 +2619,7 @@ "section": "def-common.PanelPackage", "text": "PanelPackage" }, - ") => Promise; } & Partial Promise; getChildIds: () => string[]; getChild: (childId: string) => unknown; } & Partial" ], "path": "src/plugins/embeddable/public/lib/triggers/triggers.ts", - "deprecated": false, + "deprecated": true, "trackAdoption": false, + "references": [ + { + "plugin": "embeddableEnhanced", + "path": "x-pack/plugins/embeddable_enhanced/public/plugin.ts" + }, + { + "plugin": "embeddableEnhanced", + "path": "x-pack/plugins/embeddable_enhanced/public/plugin.ts" + }, + { + "plugin": "embeddableEnhanced", + "path": "x-pack/plugins/embeddable_enhanced/public/plugin.ts" + }, + { + "plugin": "discoverEnhanced", + "path": "x-pack/plugins/discover_enhanced/public/actions/explore_data/explore_data_context_menu_action.ts" + }, + { + "plugin": "discoverEnhanced", + "path": "x-pack/plugins/discover_enhanced/public/actions/explore_data/explore_data_context_menu_action.ts" + } + ], "children": [ { "parentPluginId": "embeddable", @@ -13522,7 +13546,7 @@ "section": "def-common.PanelPackage", "text": "PanelPackage" }, - ") => Promise; } & Partial Promise; getChildIds: () => string[]; getChild: (childId: string) => unknown; } & Partial api is ", + { + "pluginId": "embeddableEnhanced", + "scope": "public", + "docId": "kibEmbeddableEnhancedPluginApi", + "section": "def-public.HasDynamicActions", + "text": "HasDynamicActions" + } + ], + "path": "x-pack/plugins/embeddable_enhanced/public/embeddables/interfaces/has_dynamic_actions.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "embeddableEnhanced", + "id": "def-public.apiHasDynamicActions.$1", + "type": "Unknown", + "tags": [], + "label": "api", + "description": [], + "signature": [ + "unknown" + ], + "path": "x-pack/plugins/embeddable_enhanced/public/embeddables/interfaces/has_dynamic_actions.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [], + "initialIsOpen": false + }, { "parentPluginId": "embeddableEnhanced", "id": "def-public.isEnhancedEmbeddable", @@ -74,12 +114,24 @@ "parentPluginId": "embeddableEnhanced", "id": "def-public.EnhancedEmbeddableContext", "type": "Interface", - "tags": [], + "tags": [ + "deprecated" + ], "label": "EnhancedEmbeddableContext", "description": [], "path": "x-pack/plugins/embeddable_enhanced/public/types.ts", - "deprecated": false, + "deprecated": true, "trackAdoption": false, + "references": [ + { + "plugin": "dashboardEnhanced", + "path": "x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_edit_drilldown/menu_item.tsx" + }, + { + "plugin": "dashboardEnhanced", + "path": "x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_edit_drilldown/menu_item.tsx" + } + ], "children": [ { "parentPluginId": "embeddableEnhanced", @@ -129,6 +181,42 @@ ], "initialIsOpen": false }, + { + "parentPluginId": "embeddableEnhanced", + "id": "def-public.HasDynamicActions", + "type": "Interface", + "tags": [], + "label": "HasDynamicActions", + "description": [], + "path": "x-pack/plugins/embeddable_enhanced/public/embeddables/interfaces/has_dynamic_actions.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "embeddableEnhanced", + "id": "def-public.HasDynamicActions.enhancements", + "type": "Object", + "tags": [], + "label": "enhancements", + "description": [], + "signature": [ + "{ dynamicActions: ", + { + "pluginId": "uiActionsEnhanced", + "scope": "public", + "docId": "kibUiActionsEnhancedPluginApi", + "section": "def-public.DynamicActionManager", + "text": "DynamicActionManager" + }, + "; }" + ], + "path": "x-pack/plugins/embeddable_enhanced/public/embeddables/interfaces/has_dynamic_actions.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, { "parentPluginId": "embeddableEnhanced", "id": "def-public.SetupDependencies", @@ -255,31 +343,7 @@ "section": "def-common.PresentableGroup", "text": "PresentableGroup" }, - "<{ embeddable?: ", - { - "pluginId": "embeddable", - "scope": "public", - "docId": "kibEmbeddablePluginApi", - "section": "def-public.IEmbeddable", - "text": "IEmbeddable" - }, - "<", - { - "pluginId": "embeddable", - "scope": "common", - "docId": "kibEmbeddablePluginApi", - "section": "def-common.EmbeddableInput", - "text": "EmbeddableInput" - }, - ", ", - { - "pluginId": "embeddable", - "scope": "public", - "docId": "kibEmbeddablePluginApi", - "section": "def-public.EmbeddableOutput", - "text": "EmbeddableOutput" - }, - ", any> | undefined; }>[]" + "[]" ], "path": "x-pack/plugins/embeddable_enhanced/public/actions/drilldown_grouping.ts", "deprecated": false, diff --git a/api_docs/embeddable_enhanced.mdx b/api_docs/embeddable_enhanced.mdx index ae65bcace01b2..1831de52b17e5 100644 --- a/api_docs/embeddable_enhanced.mdx +++ b/api_docs/embeddable_enhanced.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/embeddableEnhanced title: "embeddableEnhanced" image: https://source.unsplash.com/400x175/?github description: API docs for the embeddableEnhanced plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'embeddableEnhanced'] --- import embeddableEnhancedObj from './embeddable_enhanced.devdocs.json'; @@ -21,7 +21,7 @@ Contact [@elastic/kibana-presentation](https://github.com/orgs/elastic/teams/kib | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 14 | 0 | 14 | 0 | +| 18 | 0 | 18 | 0 | ## Client diff --git a/api_docs/encrypted_saved_objects.mdx b/api_docs/encrypted_saved_objects.mdx index 31b5c8ee2426b..19cef36a16a48 100644 --- a/api_docs/encrypted_saved_objects.mdx +++ b/api_docs/encrypted_saved_objects.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/encryptedSavedObjects title: "encryptedSavedObjects" image: https://source.unsplash.com/400x175/?github description: API docs for the encryptedSavedObjects plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'encryptedSavedObjects'] --- import encryptedSavedObjectsObj from './encrypted_saved_objects.devdocs.json'; diff --git a/api_docs/enterprise_search.mdx b/api_docs/enterprise_search.mdx index d510074089c80..2fe5c0cbf6464 100644 --- a/api_docs/enterprise_search.mdx +++ b/api_docs/enterprise_search.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/enterpriseSearch title: "enterpriseSearch" image: https://source.unsplash.com/400x175/?github description: API docs for the enterpriseSearch plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'enterpriseSearch'] --- import enterpriseSearchObj from './enterprise_search.devdocs.json'; diff --git a/api_docs/es_ui_shared.mdx b/api_docs/es_ui_shared.mdx index 237a2f9f6563e..76052c20e5894 100644 --- a/api_docs/es_ui_shared.mdx +++ b/api_docs/es_ui_shared.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/esUiShared title: "esUiShared" image: https://source.unsplash.com/400x175/?github description: API docs for the esUiShared plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'esUiShared'] --- import esUiSharedObj from './es_ui_shared.devdocs.json'; diff --git a/api_docs/event_annotation.mdx b/api_docs/event_annotation.mdx index 11fe08155d6d9..0e8a16a1c8505 100644 --- a/api_docs/event_annotation.mdx +++ b/api_docs/event_annotation.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/eventAnnotation title: "eventAnnotation" image: https://source.unsplash.com/400x175/?github description: API docs for the eventAnnotation plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'eventAnnotation'] --- import eventAnnotationObj from './event_annotation.devdocs.json'; diff --git a/api_docs/event_annotation_listing.mdx b/api_docs/event_annotation_listing.mdx index ffa7d9cf90240..8c25a48efcae2 100644 --- a/api_docs/event_annotation_listing.mdx +++ b/api_docs/event_annotation_listing.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/eventAnnotationListing title: "eventAnnotationListing" image: https://source.unsplash.com/400x175/?github description: API docs for the eventAnnotationListing plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'eventAnnotationListing'] --- import eventAnnotationListingObj from './event_annotation_listing.devdocs.json'; diff --git a/api_docs/event_log.devdocs.json b/api_docs/event_log.devdocs.json index cbb17ce11942b..47999cb234510 100644 --- a/api_docs/event_log.devdocs.json +++ b/api_docs/event_log.devdocs.json @@ -1450,7 +1450,7 @@ "label": "data", "description": [], "signature": [ - "(Readonly<{ log?: Readonly<{ logger?: string | undefined; level?: string | undefined; } & {}> | undefined; error?: Readonly<{ type?: string | undefined; id?: string | undefined; message?: string | undefined; code?: string | undefined; stack_trace?: string | undefined; } & {}> | undefined; '@timestamp'?: string | undefined; message?: string | undefined; tags?: string[] | undefined; rule?: Readonly<{ id?: string | undefined; name?: string | undefined; license?: string | undefined; uuid?: string | undefined; version?: string | undefined; category?: string | undefined; description?: string | undefined; reference?: string | undefined; author?: string[] | undefined; ruleset?: string | undefined; } & {}> | undefined; kibana?: Readonly<{ action?: Readonly<{ id?: string | undefined; name?: string | undefined; execution?: Readonly<{ source?: string | undefined; uuid?: string | undefined; gen_ai?: Readonly<{ usage?: Readonly<{ prompt_tokens?: string | number | undefined; completion_tokens?: string | number | undefined; total_tokens?: string | number | undefined; } & {}> | undefined; } & {}> | undefined; } & {}> | undefined; } & {}> | undefined; alerting?: Readonly<{ outcome?: string | undefined; summary?: Readonly<{ recovered?: Readonly<{ count?: string | number | undefined; } & {}> | undefined; new?: Readonly<{ count?: string | number | undefined; } & {}> | undefined; ongoing?: Readonly<{ count?: string | number | undefined; } & {}> | undefined; } & {}> | undefined; status?: string | undefined; instance_id?: string | undefined; action_group_id?: string | undefined; action_subgroup?: string | undefined; } & {}> | undefined; alert?: Readonly<{ rule?: Readonly<{ consumer?: string | undefined; revision?: string | number | undefined; execution?: Readonly<{ uuid?: string | undefined; status?: string | undefined; metrics?: Readonly<{ total_search_duration_ms?: string | number | undefined; total_indexing_duration_ms?: string | number | undefined; number_of_triggered_actions?: string | number | undefined; number_of_generated_actions?: string | number | undefined; alert_counts?: Readonly<{ recovered?: string | number | undefined; active?: string | number | undefined; new?: string | number | undefined; } & {}> | undefined; number_of_searches?: string | number | undefined; es_search_duration_ms?: string | number | undefined; execution_gap_duration_s?: string | number | undefined; rule_type_run_duration_ms?: string | number | undefined; process_alerts_duration_ms?: string | number | undefined; trigger_actions_duration_ms?: string | number | undefined; process_rule_duration_ms?: string | number | undefined; claim_to_start_duration_ms?: string | number | undefined; persist_alerts_duration_ms?: string | number | undefined; prepare_rule_duration_ms?: string | number | undefined; total_run_duration_ms?: string | number | undefined; total_enrichment_duration_ms?: string | number | undefined; } & {}> | undefined; status_order?: string | number | undefined; } & {}> | undefined; rule_type_id?: string | undefined; } & {}> | undefined; uuid?: string | undefined; flapping?: boolean | undefined; maintenance_window_ids?: string[] | undefined; } & {}> | undefined; version?: string | undefined; server_uuid?: string | undefined; task?: Readonly<{ id?: string | undefined; schedule_delay?: string | number | undefined; scheduled?: string | undefined; } & {}> | undefined; saved_objects?: Readonly<{ type?: string | undefined; id?: string | undefined; namespace?: string | undefined; rel?: string | undefined; type_id?: string | undefined; space_agnostic?: boolean | undefined; } & {}>[] | undefined; space_ids?: string[] | undefined; } & {}> | undefined; event?: Readonly<{ type?: string[] | undefined; reason?: string | undefined; action?: string | undefined; id?: string | undefined; start?: string | undefined; end?: string | undefined; outcome?: string | undefined; category?: string[] | undefined; duration?: string | number | undefined; timezone?: string | undefined; risk_score?: number | undefined; severity?: string | number | undefined; url?: string | undefined; created?: string | undefined; dataset?: string | undefined; code?: string | undefined; hash?: string | undefined; ingested?: string | undefined; kind?: string | undefined; module?: string | undefined; original?: string | undefined; provider?: string | undefined; reference?: string | undefined; risk_score_norm?: number | undefined; sequence?: string | number | undefined; } & {}> | undefined; ecs?: Readonly<{ version?: string | undefined; } & {}> | undefined; user?: Readonly<{ id?: string | undefined; name?: string | undefined; } & {}> | undefined; } & {}> | undefined)[]" + "(Readonly<{ log?: Readonly<{ logger?: string | undefined; level?: string | undefined; } & {}> | undefined; error?: Readonly<{ type?: string | undefined; id?: string | undefined; message?: string | undefined; code?: string | undefined; stack_trace?: string | undefined; } & {}> | undefined; '@timestamp'?: string | undefined; message?: string | undefined; tags?: string[] | undefined; rule?: Readonly<{ id?: string | undefined; name?: string | undefined; license?: string | undefined; uuid?: string | undefined; version?: string | undefined; category?: string | undefined; description?: string | undefined; reference?: string | undefined; author?: string[] | undefined; ruleset?: string | undefined; } & {}> | undefined; kibana?: Readonly<{ action?: Readonly<{ id?: string | undefined; name?: string | undefined; execution?: Readonly<{ source?: string | undefined; uuid?: string | undefined; gen_ai?: Readonly<{ usage?: Readonly<{ prompt_tokens?: string | number | undefined; completion_tokens?: string | number | undefined; total_tokens?: string | number | undefined; } & {}> | undefined; } & {}> | undefined; } & {}> | undefined; } & {}> | undefined; alerting?: Readonly<{ outcome?: string | undefined; summary?: Readonly<{ recovered?: Readonly<{ count?: string | number | undefined; } & {}> | undefined; new?: Readonly<{ count?: string | number | undefined; } & {}> | undefined; ongoing?: Readonly<{ count?: string | number | undefined; } & {}> | undefined; } & {}> | undefined; status?: string | undefined; instance_id?: string | undefined; action_group_id?: string | undefined; action_subgroup?: string | undefined; } & {}> | undefined; alert?: Readonly<{ rule?: Readonly<{ consumer?: string | undefined; revision?: string | number | undefined; execution?: Readonly<{ uuid?: string | undefined; status?: string | undefined; metrics?: Readonly<{ total_search_duration_ms?: string | number | undefined; total_indexing_duration_ms?: string | number | undefined; number_of_triggered_actions?: string | number | undefined; number_of_generated_actions?: string | number | undefined; alert_counts?: Readonly<{ recovered?: string | number | undefined; active?: string | number | undefined; new?: string | number | undefined; } & {}> | undefined; number_of_delayed_alerts?: string | number | undefined; number_of_searches?: string | number | undefined; es_search_duration_ms?: string | number | undefined; execution_gap_duration_s?: string | number | undefined; rule_type_run_duration_ms?: string | number | undefined; process_alerts_duration_ms?: string | number | undefined; trigger_actions_duration_ms?: string | number | undefined; process_rule_duration_ms?: string | number | undefined; claim_to_start_duration_ms?: string | number | undefined; persist_alerts_duration_ms?: string | number | undefined; prepare_rule_duration_ms?: string | number | undefined; total_run_duration_ms?: string | number | undefined; total_enrichment_duration_ms?: string | number | undefined; } & {}> | undefined; status_order?: string | number | undefined; } & {}> | undefined; rule_type_id?: string | undefined; } & {}> | undefined; uuid?: string | undefined; flapping?: boolean | undefined; maintenance_window_ids?: string[] | undefined; } & {}> | undefined; version?: string | undefined; server_uuid?: string | undefined; task?: Readonly<{ id?: string | undefined; schedule_delay?: string | number | undefined; scheduled?: string | undefined; } & {}> | undefined; saved_objects?: Readonly<{ type?: string | undefined; id?: string | undefined; namespace?: string | undefined; rel?: string | undefined; type_id?: string | undefined; space_agnostic?: boolean | undefined; } & {}>[] | undefined; space_ids?: string[] | undefined; } & {}> | undefined; event?: Readonly<{ type?: string[] | undefined; reason?: string | undefined; action?: string | undefined; id?: string | undefined; start?: string | undefined; end?: string | undefined; outcome?: string | undefined; category?: string[] | undefined; duration?: string | number | undefined; timezone?: string | undefined; risk_score?: number | undefined; severity?: string | number | undefined; url?: string | undefined; created?: string | undefined; dataset?: string | undefined; code?: string | undefined; hash?: string | undefined; ingested?: string | undefined; kind?: string | undefined; module?: string | undefined; original?: string | undefined; provider?: string | undefined; reference?: string | undefined; risk_score_norm?: number | undefined; sequence?: string | number | undefined; } & {}> | undefined; ecs?: Readonly<{ version?: string | undefined; } & {}> | undefined; user?: Readonly<{ id?: string | undefined; name?: string | undefined; } & {}> | undefined; } & {}> | undefined)[]" ], "path": "x-pack/plugins/event_log/server/es/cluster_client_adapter.ts", "deprecated": false, @@ -1470,7 +1470,7 @@ "label": "IEvent", "description": [], "signature": [ - "DeepPartial | undefined; error?: Readonly<{ type?: string | undefined; id?: string | undefined; message?: string | undefined; code?: string | undefined; stack_trace?: string | undefined; } & {}> | undefined; '@timestamp'?: string | undefined; message?: string | undefined; tags?: string[] | undefined; rule?: Readonly<{ id?: string | undefined; name?: string | undefined; license?: string | undefined; uuid?: string | undefined; version?: string | undefined; category?: string | undefined; description?: string | undefined; reference?: string | undefined; author?: string[] | undefined; ruleset?: string | undefined; } & {}> | undefined; kibana?: Readonly<{ action?: Readonly<{ id?: string | undefined; name?: string | undefined; execution?: Readonly<{ source?: string | undefined; uuid?: string | undefined; gen_ai?: Readonly<{ usage?: Readonly<{ prompt_tokens?: string | number | undefined; completion_tokens?: string | number | undefined; total_tokens?: string | number | undefined; } & {}> | undefined; } & {}> | undefined; } & {}> | undefined; } & {}> | undefined; alerting?: Readonly<{ outcome?: string | undefined; summary?: Readonly<{ recovered?: Readonly<{ count?: string | number | undefined; } & {}> | undefined; new?: Readonly<{ count?: string | number | undefined; } & {}> | undefined; ongoing?: Readonly<{ count?: string | number | undefined; } & {}> | undefined; } & {}> | undefined; status?: string | undefined; instance_id?: string | undefined; action_group_id?: string | undefined; action_subgroup?: string | undefined; } & {}> | undefined; alert?: Readonly<{ rule?: Readonly<{ consumer?: string | undefined; revision?: string | number | undefined; execution?: Readonly<{ uuid?: string | undefined; status?: string | undefined; metrics?: Readonly<{ total_search_duration_ms?: string | number | undefined; total_indexing_duration_ms?: string | number | undefined; number_of_triggered_actions?: string | number | undefined; number_of_generated_actions?: string | number | undefined; alert_counts?: Readonly<{ recovered?: string | number | undefined; active?: string | number | undefined; new?: string | number | undefined; } & {}> | undefined; number_of_searches?: string | number | undefined; es_search_duration_ms?: string | number | undefined; execution_gap_duration_s?: string | number | undefined; rule_type_run_duration_ms?: string | number | undefined; process_alerts_duration_ms?: string | number | undefined; trigger_actions_duration_ms?: string | number | undefined; process_rule_duration_ms?: string | number | undefined; claim_to_start_duration_ms?: string | number | undefined; persist_alerts_duration_ms?: string | number | undefined; prepare_rule_duration_ms?: string | number | undefined; total_run_duration_ms?: string | number | undefined; total_enrichment_duration_ms?: string | number | undefined; } & {}> | undefined; status_order?: string | number | undefined; } & {}> | undefined; rule_type_id?: string | undefined; } & {}> | undefined; uuid?: string | undefined; flapping?: boolean | undefined; maintenance_window_ids?: string[] | undefined; } & {}> | undefined; version?: string | undefined; server_uuid?: string | undefined; task?: Readonly<{ id?: string | undefined; schedule_delay?: string | number | undefined; scheduled?: string | undefined; } & {}> | undefined; saved_objects?: Readonly<{ type?: string | undefined; id?: string | undefined; namespace?: string | undefined; rel?: string | undefined; type_id?: string | undefined; space_agnostic?: boolean | undefined; } & {}>[] | undefined; space_ids?: string[] | undefined; } & {}> | undefined; event?: Readonly<{ type?: string[] | undefined; reason?: string | undefined; action?: string | undefined; id?: string | undefined; start?: string | undefined; end?: string | undefined; outcome?: string | undefined; category?: string[] | undefined; duration?: string | number | undefined; timezone?: string | undefined; risk_score?: number | undefined; severity?: string | number | undefined; url?: string | undefined; created?: string | undefined; dataset?: string | undefined; code?: string | undefined; hash?: string | undefined; ingested?: string | undefined; kind?: string | undefined; module?: string | undefined; original?: string | undefined; provider?: string | undefined; reference?: string | undefined; risk_score_norm?: number | undefined; sequence?: string | number | undefined; } & {}> | undefined; ecs?: Readonly<{ version?: string | undefined; } & {}> | undefined; user?: Readonly<{ id?: string | undefined; name?: string | undefined; } & {}> | undefined; } & {}>>> | undefined" + "DeepPartial | undefined; error?: Readonly<{ type?: string | undefined; id?: string | undefined; message?: string | undefined; code?: string | undefined; stack_trace?: string | undefined; } & {}> | undefined; '@timestamp'?: string | undefined; message?: string | undefined; tags?: string[] | undefined; rule?: Readonly<{ id?: string | undefined; name?: string | undefined; license?: string | undefined; uuid?: string | undefined; version?: string | undefined; category?: string | undefined; description?: string | undefined; reference?: string | undefined; author?: string[] | undefined; ruleset?: string | undefined; } & {}> | undefined; kibana?: Readonly<{ action?: Readonly<{ id?: string | undefined; name?: string | undefined; execution?: Readonly<{ source?: string | undefined; uuid?: string | undefined; gen_ai?: Readonly<{ usage?: Readonly<{ prompt_tokens?: string | number | undefined; completion_tokens?: string | number | undefined; total_tokens?: string | number | undefined; } & {}> | undefined; } & {}> | undefined; } & {}> | undefined; } & {}> | undefined; alerting?: Readonly<{ outcome?: string | undefined; summary?: Readonly<{ recovered?: Readonly<{ count?: string | number | undefined; } & {}> | undefined; new?: Readonly<{ count?: string | number | undefined; } & {}> | undefined; ongoing?: Readonly<{ count?: string | number | undefined; } & {}> | undefined; } & {}> | undefined; status?: string | undefined; instance_id?: string | undefined; action_group_id?: string | undefined; action_subgroup?: string | undefined; } & {}> | undefined; alert?: Readonly<{ rule?: Readonly<{ consumer?: string | undefined; revision?: string | number | undefined; execution?: Readonly<{ uuid?: string | undefined; status?: string | undefined; metrics?: Readonly<{ total_search_duration_ms?: string | number | undefined; total_indexing_duration_ms?: string | number | undefined; number_of_triggered_actions?: string | number | undefined; number_of_generated_actions?: string | number | undefined; alert_counts?: Readonly<{ recovered?: string | number | undefined; active?: string | number | undefined; new?: string | number | undefined; } & {}> | undefined; number_of_delayed_alerts?: string | number | undefined; number_of_searches?: string | number | undefined; es_search_duration_ms?: string | number | undefined; execution_gap_duration_s?: string | number | undefined; rule_type_run_duration_ms?: string | number | undefined; process_alerts_duration_ms?: string | number | undefined; trigger_actions_duration_ms?: string | number | undefined; process_rule_duration_ms?: string | number | undefined; claim_to_start_duration_ms?: string | number | undefined; persist_alerts_duration_ms?: string | number | undefined; prepare_rule_duration_ms?: string | number | undefined; total_run_duration_ms?: string | number | undefined; total_enrichment_duration_ms?: string | number | undefined; } & {}> | undefined; status_order?: string | number | undefined; } & {}> | undefined; rule_type_id?: string | undefined; } & {}> | undefined; uuid?: string | undefined; flapping?: boolean | undefined; maintenance_window_ids?: string[] | undefined; } & {}> | undefined; version?: string | undefined; server_uuid?: string | undefined; task?: Readonly<{ id?: string | undefined; schedule_delay?: string | number | undefined; scheduled?: string | undefined; } & {}> | undefined; saved_objects?: Readonly<{ type?: string | undefined; id?: string | undefined; namespace?: string | undefined; rel?: string | undefined; type_id?: string | undefined; space_agnostic?: boolean | undefined; } & {}>[] | undefined; space_ids?: string[] | undefined; } & {}> | undefined; event?: Readonly<{ type?: string[] | undefined; reason?: string | undefined; action?: string | undefined; id?: string | undefined; start?: string | undefined; end?: string | undefined; outcome?: string | undefined; category?: string[] | undefined; duration?: string | number | undefined; timezone?: string | undefined; risk_score?: number | undefined; severity?: string | number | undefined; url?: string | undefined; created?: string | undefined; dataset?: string | undefined; code?: string | undefined; hash?: string | undefined; ingested?: string | undefined; kind?: string | undefined; module?: string | undefined; original?: string | undefined; provider?: string | undefined; reference?: string | undefined; risk_score_norm?: number | undefined; sequence?: string | number | undefined; } & {}> | undefined; ecs?: Readonly<{ version?: string | undefined; } & {}> | undefined; user?: Readonly<{ id?: string | undefined; name?: string | undefined; } & {}> | undefined; } & {}>>> | undefined" ], "path": "x-pack/plugins/event_log/generated/schemas.ts", "deprecated": false, @@ -1485,7 +1485,7 @@ "label": "IValidatedEvent", "description": [], "signature": [ - "Readonly<{ log?: Readonly<{ logger?: string | undefined; level?: string | undefined; } & {}> | undefined; error?: Readonly<{ type?: string | undefined; id?: string | undefined; message?: string | undefined; code?: string | undefined; stack_trace?: string | undefined; } & {}> | undefined; '@timestamp'?: string | undefined; message?: string | undefined; tags?: string[] | undefined; rule?: Readonly<{ id?: string | undefined; name?: string | undefined; license?: string | undefined; uuid?: string | undefined; version?: string | undefined; category?: string | undefined; description?: string | undefined; reference?: string | undefined; author?: string[] | undefined; ruleset?: string | undefined; } & {}> | undefined; kibana?: Readonly<{ action?: Readonly<{ id?: string | undefined; name?: string | undefined; execution?: Readonly<{ source?: string | undefined; uuid?: string | undefined; gen_ai?: Readonly<{ usage?: Readonly<{ prompt_tokens?: string | number | undefined; completion_tokens?: string | number | undefined; total_tokens?: string | number | undefined; } & {}> | undefined; } & {}> | undefined; } & {}> | undefined; } & {}> | undefined; alerting?: Readonly<{ outcome?: string | undefined; summary?: Readonly<{ recovered?: Readonly<{ count?: string | number | undefined; } & {}> | undefined; new?: Readonly<{ count?: string | number | undefined; } & {}> | undefined; ongoing?: Readonly<{ count?: string | number | undefined; } & {}> | undefined; } & {}> | undefined; status?: string | undefined; instance_id?: string | undefined; action_group_id?: string | undefined; action_subgroup?: string | undefined; } & {}> | undefined; alert?: Readonly<{ rule?: Readonly<{ consumer?: string | undefined; revision?: string | number | undefined; execution?: Readonly<{ uuid?: string | undefined; status?: string | undefined; metrics?: Readonly<{ total_search_duration_ms?: string | number | undefined; total_indexing_duration_ms?: string | number | undefined; number_of_triggered_actions?: string | number | undefined; number_of_generated_actions?: string | number | undefined; alert_counts?: Readonly<{ recovered?: string | number | undefined; active?: string | number | undefined; new?: string | number | undefined; } & {}> | undefined; number_of_searches?: string | number | undefined; es_search_duration_ms?: string | number | undefined; execution_gap_duration_s?: string | number | undefined; rule_type_run_duration_ms?: string | number | undefined; process_alerts_duration_ms?: string | number | undefined; trigger_actions_duration_ms?: string | number | undefined; process_rule_duration_ms?: string | number | undefined; claim_to_start_duration_ms?: string | number | undefined; persist_alerts_duration_ms?: string | number | undefined; prepare_rule_duration_ms?: string | number | undefined; total_run_duration_ms?: string | number | undefined; total_enrichment_duration_ms?: string | number | undefined; } & {}> | undefined; status_order?: string | number | undefined; } & {}> | undefined; rule_type_id?: string | undefined; } & {}> | undefined; uuid?: string | undefined; flapping?: boolean | undefined; maintenance_window_ids?: string[] | undefined; } & {}> | undefined; version?: string | undefined; server_uuid?: string | undefined; task?: Readonly<{ id?: string | undefined; schedule_delay?: string | number | undefined; scheduled?: string | undefined; } & {}> | undefined; saved_objects?: Readonly<{ type?: string | undefined; id?: string | undefined; namespace?: string | undefined; rel?: string | undefined; type_id?: string | undefined; space_agnostic?: boolean | undefined; } & {}>[] | undefined; space_ids?: string[] | undefined; } & {}> | undefined; event?: Readonly<{ type?: string[] | undefined; reason?: string | undefined; action?: string | undefined; id?: string | undefined; start?: string | undefined; end?: string | undefined; outcome?: string | undefined; category?: string[] | undefined; duration?: string | number | undefined; timezone?: string | undefined; risk_score?: number | undefined; severity?: string | number | undefined; url?: string | undefined; created?: string | undefined; dataset?: string | undefined; code?: string | undefined; hash?: string | undefined; ingested?: string | undefined; kind?: string | undefined; module?: string | undefined; original?: string | undefined; provider?: string | undefined; reference?: string | undefined; risk_score_norm?: number | undefined; sequence?: string | number | undefined; } & {}> | undefined; ecs?: Readonly<{ version?: string | undefined; } & {}> | undefined; user?: Readonly<{ id?: string | undefined; name?: string | undefined; } & {}> | undefined; } & {}> | undefined" + "Readonly<{ log?: Readonly<{ logger?: string | undefined; level?: string | undefined; } & {}> | undefined; error?: Readonly<{ type?: string | undefined; id?: string | undefined; message?: string | undefined; code?: string | undefined; stack_trace?: string | undefined; } & {}> | undefined; '@timestamp'?: string | undefined; message?: string | undefined; tags?: string[] | undefined; rule?: Readonly<{ id?: string | undefined; name?: string | undefined; license?: string | undefined; uuid?: string | undefined; version?: string | undefined; category?: string | undefined; description?: string | undefined; reference?: string | undefined; author?: string[] | undefined; ruleset?: string | undefined; } & {}> | undefined; kibana?: Readonly<{ action?: Readonly<{ id?: string | undefined; name?: string | undefined; execution?: Readonly<{ source?: string | undefined; uuid?: string | undefined; gen_ai?: Readonly<{ usage?: Readonly<{ prompt_tokens?: string | number | undefined; completion_tokens?: string | number | undefined; total_tokens?: string | number | undefined; } & {}> | undefined; } & {}> | undefined; } & {}> | undefined; } & {}> | undefined; alerting?: Readonly<{ outcome?: string | undefined; summary?: Readonly<{ recovered?: Readonly<{ count?: string | number | undefined; } & {}> | undefined; new?: Readonly<{ count?: string | number | undefined; } & {}> | undefined; ongoing?: Readonly<{ count?: string | number | undefined; } & {}> | undefined; } & {}> | undefined; status?: string | undefined; instance_id?: string | undefined; action_group_id?: string | undefined; action_subgroup?: string | undefined; } & {}> | undefined; alert?: Readonly<{ rule?: Readonly<{ consumer?: string | undefined; revision?: string | number | undefined; execution?: Readonly<{ uuid?: string | undefined; status?: string | undefined; metrics?: Readonly<{ total_search_duration_ms?: string | number | undefined; total_indexing_duration_ms?: string | number | undefined; number_of_triggered_actions?: string | number | undefined; number_of_generated_actions?: string | number | undefined; alert_counts?: Readonly<{ recovered?: string | number | undefined; active?: string | number | undefined; new?: string | number | undefined; } & {}> | undefined; number_of_delayed_alerts?: string | number | undefined; number_of_searches?: string | number | undefined; es_search_duration_ms?: string | number | undefined; execution_gap_duration_s?: string | number | undefined; rule_type_run_duration_ms?: string | number | undefined; process_alerts_duration_ms?: string | number | undefined; trigger_actions_duration_ms?: string | number | undefined; process_rule_duration_ms?: string | number | undefined; claim_to_start_duration_ms?: string | number | undefined; persist_alerts_duration_ms?: string | number | undefined; prepare_rule_duration_ms?: string | number | undefined; total_run_duration_ms?: string | number | undefined; total_enrichment_duration_ms?: string | number | undefined; } & {}> | undefined; status_order?: string | number | undefined; } & {}> | undefined; rule_type_id?: string | undefined; } & {}> | undefined; uuid?: string | undefined; flapping?: boolean | undefined; maintenance_window_ids?: string[] | undefined; } & {}> | undefined; version?: string | undefined; server_uuid?: string | undefined; task?: Readonly<{ id?: string | undefined; schedule_delay?: string | number | undefined; scheduled?: string | undefined; } & {}> | undefined; saved_objects?: Readonly<{ type?: string | undefined; id?: string | undefined; namespace?: string | undefined; rel?: string | undefined; type_id?: string | undefined; space_agnostic?: boolean | undefined; } & {}>[] | undefined; space_ids?: string[] | undefined; } & {}> | undefined; event?: Readonly<{ type?: string[] | undefined; reason?: string | undefined; action?: string | undefined; id?: string | undefined; start?: string | undefined; end?: string | undefined; outcome?: string | undefined; category?: string[] | undefined; duration?: string | number | undefined; timezone?: string | undefined; risk_score?: number | undefined; severity?: string | number | undefined; url?: string | undefined; created?: string | undefined; dataset?: string | undefined; code?: string | undefined; hash?: string | undefined; ingested?: string | undefined; kind?: string | undefined; module?: string | undefined; original?: string | undefined; provider?: string | undefined; reference?: string | undefined; risk_score_norm?: number | undefined; sequence?: string | number | undefined; } & {}> | undefined; ecs?: Readonly<{ version?: string | undefined; } & {}> | undefined; user?: Readonly<{ id?: string | undefined; name?: string | undefined; } & {}> | undefined; } & {}> | undefined" ], "path": "x-pack/plugins/event_log/generated/schemas.ts", "deprecated": false, diff --git a/api_docs/event_log.mdx b/api_docs/event_log.mdx index f01720b45648b..a7574d88f99f2 100644 --- a/api_docs/event_log.mdx +++ b/api_docs/event_log.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/eventLog title: "eventLog" image: https://source.unsplash.com/400x175/?github description: API docs for the eventLog plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'eventLog'] --- import eventLogObj from './event_log.devdocs.json'; diff --git a/api_docs/exploratory_view.mdx b/api_docs/exploratory_view.mdx index e8b357488a260..3541f2f7ff1df 100644 --- a/api_docs/exploratory_view.mdx +++ b/api_docs/exploratory_view.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/exploratoryView title: "exploratoryView" image: https://source.unsplash.com/400x175/?github description: API docs for the exploratoryView plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'exploratoryView'] --- import exploratoryViewObj from './exploratory_view.devdocs.json'; diff --git a/api_docs/expression_error.mdx b/api_docs/expression_error.mdx index ccee2192e26b0..ff0851b3f5f4c 100644 --- a/api_docs/expression_error.mdx +++ b/api_docs/expression_error.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionError title: "expressionError" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionError plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionError'] --- import expressionErrorObj from './expression_error.devdocs.json'; diff --git a/api_docs/expression_gauge.mdx b/api_docs/expression_gauge.mdx index 0d5e721fe1beb..22439e8e77f03 100644 --- a/api_docs/expression_gauge.mdx +++ b/api_docs/expression_gauge.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionGauge title: "expressionGauge" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionGauge plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionGauge'] --- import expressionGaugeObj from './expression_gauge.devdocs.json'; diff --git a/api_docs/expression_heatmap.mdx b/api_docs/expression_heatmap.mdx index ce6fe74e8d5ea..6945775d2fdb8 100644 --- a/api_docs/expression_heatmap.mdx +++ b/api_docs/expression_heatmap.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionHeatmap title: "expressionHeatmap" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionHeatmap plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionHeatmap'] --- import expressionHeatmapObj from './expression_heatmap.devdocs.json'; diff --git a/api_docs/expression_image.mdx b/api_docs/expression_image.mdx index 3940a39d77032..518a30e20527b 100644 --- a/api_docs/expression_image.mdx +++ b/api_docs/expression_image.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionImage title: "expressionImage" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionImage plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionImage'] --- import expressionImageObj from './expression_image.devdocs.json'; diff --git a/api_docs/expression_legacy_metric_vis.mdx b/api_docs/expression_legacy_metric_vis.mdx index 72a554f7e9ff6..84f7d56f257a2 100644 --- a/api_docs/expression_legacy_metric_vis.mdx +++ b/api_docs/expression_legacy_metric_vis.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionLegacyMetricVis title: "expressionLegacyMetricVis" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionLegacyMetricVis plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionLegacyMetricVis'] --- import expressionLegacyMetricVisObj from './expression_legacy_metric_vis.devdocs.json'; diff --git a/api_docs/expression_metric.mdx b/api_docs/expression_metric.mdx index 4e62a3ea58602..0b085638e6858 100644 --- a/api_docs/expression_metric.mdx +++ b/api_docs/expression_metric.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionMetric title: "expressionMetric" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionMetric plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionMetric'] --- import expressionMetricObj from './expression_metric.devdocs.json'; diff --git a/api_docs/expression_metric_vis.mdx b/api_docs/expression_metric_vis.mdx index dc126c254290a..14441ab7e5d08 100644 --- a/api_docs/expression_metric_vis.mdx +++ b/api_docs/expression_metric_vis.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionMetricVis title: "expressionMetricVis" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionMetricVis plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionMetricVis'] --- import expressionMetricVisObj from './expression_metric_vis.devdocs.json'; diff --git a/api_docs/expression_partition_vis.mdx b/api_docs/expression_partition_vis.mdx index 8016520842706..109f7f794a5a6 100644 --- a/api_docs/expression_partition_vis.mdx +++ b/api_docs/expression_partition_vis.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionPartitionVis title: "expressionPartitionVis" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionPartitionVis plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionPartitionVis'] --- import expressionPartitionVisObj from './expression_partition_vis.devdocs.json'; diff --git a/api_docs/expression_repeat_image.mdx b/api_docs/expression_repeat_image.mdx index a4d41eff894a7..441908d9ea8de 100644 --- a/api_docs/expression_repeat_image.mdx +++ b/api_docs/expression_repeat_image.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionRepeatImage title: "expressionRepeatImage" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionRepeatImage plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionRepeatImage'] --- import expressionRepeatImageObj from './expression_repeat_image.devdocs.json'; diff --git a/api_docs/expression_reveal_image.mdx b/api_docs/expression_reveal_image.mdx index 5a59b28e8dcd9..43c2b254d5e00 100644 --- a/api_docs/expression_reveal_image.mdx +++ b/api_docs/expression_reveal_image.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionRevealImage title: "expressionRevealImage" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionRevealImage plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionRevealImage'] --- import expressionRevealImageObj from './expression_reveal_image.devdocs.json'; diff --git a/api_docs/expression_shape.mdx b/api_docs/expression_shape.mdx index 307984389da38..8488d91909781 100644 --- a/api_docs/expression_shape.mdx +++ b/api_docs/expression_shape.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionShape title: "expressionShape" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionShape plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionShape'] --- import expressionShapeObj from './expression_shape.devdocs.json'; diff --git a/api_docs/expression_tagcloud.mdx b/api_docs/expression_tagcloud.mdx index c73d8e14ee296..8518be940323b 100644 --- a/api_docs/expression_tagcloud.mdx +++ b/api_docs/expression_tagcloud.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionTagcloud title: "expressionTagcloud" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionTagcloud plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionTagcloud'] --- import expressionTagcloudObj from './expression_tagcloud.devdocs.json'; diff --git a/api_docs/expression_x_y.devdocs.json b/api_docs/expression_x_y.devdocs.json index 0d8facf97b737..d2799e04d3c49 100644 --- a/api_docs/expression_x_y.devdocs.json +++ b/api_docs/expression_x_y.devdocs.json @@ -1944,7 +1944,7 @@ }, "<", "YDomainRange", - " | undefined>; hide?: boolean | undefined; showOverlappingTicks?: boolean | undefined; showOverlappingLabels?: boolean | undefined; timeAxisLayerCount?: number | undefined; integersOnly?: boolean | undefined; tickFormat?: \"ignore\" | undefined; labelFormat?: \"ignore\" | undefined; showDuplicatedTicks?: boolean | undefined; }>> & Partial; hide?: boolean | undefined; showOverlappingTicks?: boolean | undefined; showOverlappingLabels?: boolean | undefined; timeAxisLayerCount?: number | undefined; maximumFractionDigits?: number | undefined; tickFormat?: \"ignore\" | undefined; integersOnly?: boolean | undefined; labelFormat?: \"ignore\" | undefined; showDuplicatedTicks?: boolean | undefined; }>> & Partial; hide?: boolean | undefined; showOverlappingTicks?: boolean | undefined; showOverlappingLabels?: boolean | undefined; timeAxisLayerCount?: number | undefined; integersOnly?: boolean | undefined; tickFormat?: \"ignore\" | undefined; labelFormat?: \"ignore\" | undefined; showDuplicatedTicks?: boolean | undefined; } | undefined; axisLeft?: { style?: ", + " | undefined>; hide?: boolean | undefined; showOverlappingTicks?: boolean | undefined; showOverlappingLabels?: boolean | undefined; timeAxisLayerCount?: number | undefined; maximumFractionDigits?: number | undefined; tickFormat?: \"ignore\" | undefined; integersOnly?: boolean | undefined; labelFormat?: \"ignore\" | undefined; showDuplicatedTicks?: boolean | undefined; } | undefined; axisLeft?: { style?: ", { "pluginId": "@kbn/chart-expressions-common", "scope": "common", @@ -2215,7 +2215,7 @@ }, "<", "YDomainRange", - " | undefined>; hide?: boolean | undefined; showOverlappingTicks?: boolean | undefined; showOverlappingLabels?: boolean | undefined; timeAxisLayerCount?: number | undefined; integersOnly?: boolean | undefined; tickFormat?: \"ignore\" | undefined; labelFormat?: \"ignore\" | undefined; showDuplicatedTicks?: boolean | undefined; } | undefined; axisRight?: { style?: ", + " | undefined>; hide?: boolean | undefined; showOverlappingTicks?: boolean | undefined; showOverlappingLabels?: boolean | undefined; timeAxisLayerCount?: number | undefined; maximumFractionDigits?: number | undefined; tickFormat?: \"ignore\" | undefined; integersOnly?: boolean | undefined; labelFormat?: \"ignore\" | undefined; showDuplicatedTicks?: boolean | undefined; } | undefined; axisRight?: { style?: ", { "pluginId": "@kbn/chart-expressions-common", "scope": "common", @@ -2249,7 +2249,7 @@ }, "<", "YDomainRange", - " | undefined>; hide?: boolean | undefined; showOverlappingTicks?: boolean | undefined; showOverlappingLabels?: boolean | undefined; timeAxisLayerCount?: number | undefined; integersOnly?: boolean | undefined; tickFormat?: \"ignore\" | undefined; labelFormat?: \"ignore\" | undefined; showDuplicatedTicks?: boolean | undefined; } | undefined; }" + " | undefined>; hide?: boolean | undefined; showOverlappingTicks?: boolean | undefined; showOverlappingLabels?: boolean | undefined; timeAxisLayerCount?: number | undefined; maximumFractionDigits?: number | undefined; tickFormat?: \"ignore\" | undefined; integersOnly?: boolean | undefined; labelFormat?: \"ignore\" | undefined; showDuplicatedTicks?: boolean | undefined; } | undefined; }" ], "path": "src/plugins/chart_expressions/expression_xy/common/types/expression_functions.ts", "deprecated": false, diff --git a/api_docs/expression_x_y.mdx b/api_docs/expression_x_y.mdx index 7a310af898265..db5fe542f53ae 100644 --- a/api_docs/expression_x_y.mdx +++ b/api_docs/expression_x_y.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionXY title: "expressionXY" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionXY plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionXY'] --- import expressionXYObj from './expression_x_y.devdocs.json'; diff --git a/api_docs/expressions.mdx b/api_docs/expressions.mdx index 298fccc021e7b..4b9c94a7ec504 100644 --- a/api_docs/expressions.mdx +++ b/api_docs/expressions.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressions title: "expressions" image: https://source.unsplash.com/400x175/?github description: API docs for the expressions plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressions'] --- import expressionsObj from './expressions.devdocs.json'; diff --git a/api_docs/features.mdx b/api_docs/features.mdx index 4a24eb1f7a95f..3a5d35143e195 100644 --- a/api_docs/features.mdx +++ b/api_docs/features.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/features title: "features" image: https://source.unsplash.com/400x175/?github description: API docs for the features plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'features'] --- import featuresObj from './features.devdocs.json'; diff --git a/api_docs/field_formats.mdx b/api_docs/field_formats.mdx index b2643d8bab33f..69a2ea4563557 100644 --- a/api_docs/field_formats.mdx +++ b/api_docs/field_formats.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/fieldFormats title: "fieldFormats" image: https://source.unsplash.com/400x175/?github description: API docs for the fieldFormats plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'fieldFormats'] --- import fieldFormatsObj from './field_formats.devdocs.json'; diff --git a/api_docs/file_upload.mdx b/api_docs/file_upload.mdx index 920a8228d88b0..42df490388969 100644 --- a/api_docs/file_upload.mdx +++ b/api_docs/file_upload.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/fileUpload title: "fileUpload" image: https://source.unsplash.com/400x175/?github description: API docs for the fileUpload plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'fileUpload'] --- import fileUploadObj from './file_upload.devdocs.json'; diff --git a/api_docs/files.mdx b/api_docs/files.mdx index 46c7c563b7260..271633e73c0f2 100644 --- a/api_docs/files.mdx +++ b/api_docs/files.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/files title: "files" image: https://source.unsplash.com/400x175/?github description: API docs for the files plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'files'] --- import filesObj from './files.devdocs.json'; diff --git a/api_docs/files_management.mdx b/api_docs/files_management.mdx index 4490584a13150..b41bdf76a0e59 100644 --- a/api_docs/files_management.mdx +++ b/api_docs/files_management.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/filesManagement title: "filesManagement" image: https://source.unsplash.com/400x175/?github description: API docs for the filesManagement plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'filesManagement'] --- import filesManagementObj from './files_management.devdocs.json'; diff --git a/api_docs/fleet.devdocs.json b/api_docs/fleet.devdocs.json index de83a55e605a0..365ef43c90ff5 100644 --- a/api_docs/fleet.devdocs.json +++ b/api_docs/fleet.devdocs.json @@ -5261,10 +5261,10 @@ }, { "parentPluginId": "fleet", - "id": "def-server.AgentPolicyServiceInterface.bumpRevision", + "id": "def-server.AgentPolicyServiceInterface.turnOffAgentTamperProtections", "type": "Function", "tags": [], - "label": "bumpRevision", + "label": "turnOffAgentTamperProtections", "description": [], "signature": [ "(soClient: ", @@ -5275,23 +5275,7 @@ "section": "def-common.SavedObjectsClientContract", "text": "SavedObjectsClientContract" }, - ", esClient: ", - { - "pluginId": "@kbn/core-elasticsearch-server", - "scope": "common", - "docId": "kibKbnCoreElasticsearchServerPluginApi", - "section": "def-common.ElasticsearchClient", - "text": "ElasticsearchClient" - }, - ", id: string, options?: { user?: ", - { - "pluginId": "@kbn/security-plugin-types-common", - "scope": "common", - "docId": "kibKbnSecurityPluginTypesCommonPluginApi", - "section": "def-common.AuthenticatedUser", - "text": "AuthenticatedUser" - }, - " | undefined; removeProtection?: boolean | undefined; } | undefined) => Promise<", + ") => Promise<{ updatedPolicies: Partial<", { "pluginId": "fleet", "scope": "common", @@ -5299,7 +5283,15 @@ "section": "def-common.AgentPolicy", "text": "AgentPolicy" }, - ">" + ">[] | null; failedPolicies: { id: string; error: Error | ", + { + "pluginId": "@kbn/core-saved-objects-common", + "scope": "common", + "docId": "kibKbnCoreSavedObjectsCommonPluginApi", + "section": "def-common.SavedObjectError", + "text": "SavedObjectError" + }, + "; }[]; }>" ], "path": "x-pack/plugins/fleet/server/services/index.ts", "deprecated": false, @@ -5308,7 +5300,7 @@ "children": [ { "parentPluginId": "fleet", - "id": "def-server.AgentPolicyServiceInterface.bumpRevision.$1", + "id": "def-server.AgentPolicyServiceInterface.turnOffAgentTamperProtections.$1", "type": "Object", "tags": [], "label": "soClient", @@ -5325,1269 +5317,6 @@ "path": "x-pack/plugins/fleet/server/services/agent_policy.ts", "deprecated": false, "trackAdoption": false - }, - { - "parentPluginId": "fleet", - "id": "def-server.AgentPolicyServiceInterface.bumpRevision.$2", - "type": "Object", - "tags": [], - "label": "esClient", - "description": [], - "signature": [ - "{ create: { (this: That, params: ", - "CreateRequest", - " | ", - "CreateRequest", - ", options?: ", - "TransportRequestOptionsWithOutMeta", - " | undefined): Promise<", - "WriteResponseBase", - ">; (this: That, params: ", - "CreateRequest", - " | ", - "CreateRequest", - ", options?: ", - "TransportRequestOptionsWithMeta", - " | undefined): Promise<", - "TransportResult", - "<", - "WriteResponseBase", - ", unknown>>; (this: That, params: ", - "CreateRequest", - " | ", - "CreateRequest", - ", options?: ", - "TransportRequestOptions", - " | undefined): Promise<", - "WriteResponseBase", - ">; }; update: { (this: That, params: ", - "UpdateRequest", - " | ", - "UpdateRequest", - ", options?: ", - "TransportRequestOptionsWithOutMeta", - " | undefined): Promise<", - "UpdateResponse", - ">; (this: That, params: ", - "UpdateRequest", - " | ", - "UpdateRequest", - ", options?: ", - "TransportRequestOptionsWithMeta", - " | undefined): Promise<", - "TransportResult", - "<", - "UpdateResponse", - ", unknown>>; (this: That, params: ", - "UpdateRequest", - " | ", - "UpdateRequest", - ", options?: ", - "TransportRequestOptions", - " | undefined): Promise<", - "UpdateResponse", - ">; }; get: { (this: That, params: ", - "GetRequest", - " | ", - "GetRequest", - ", options?: ", - "TransportRequestOptionsWithOutMeta", - " | undefined): Promise<", - "GetResponse", - ">; (this: That, params: ", - "GetRequest", - " | ", - "GetRequest", - ", options?: ", - "TransportRequestOptionsWithMeta", - " | undefined): Promise<", - "TransportResult", - "<", - "GetResponse", - ", unknown>>; (this: That, params: ", - "GetRequest", - " | ", - "GetRequest", - ", options?: ", - "TransportRequestOptions", - " | undefined): Promise<", - "GetResponse", - ">; }; delete: { (this: That, params: ", - "DeleteRequest", - " | ", - "DeleteRequest", - ", options?: ", - "TransportRequestOptionsWithOutMeta", - " | undefined): Promise<", - "WriteResponseBase", - ">; (this: That, params: ", - "DeleteRequest", - " | ", - "DeleteRequest", - ", options?: ", - "TransportRequestOptionsWithMeta", - " | undefined): Promise<", - "TransportResult", - "<", - "WriteResponseBase", - ", unknown>>; (this: That, params: ", - "DeleteRequest", - " | ", - "DeleteRequest", - ", options?: ", - "TransportRequestOptions", - " | undefined): Promise<", - "WriteResponseBase", - ">; }; search: { >(this: That, params?: ", - "SearchRequest", - " | ", - "SearchRequest", - " | undefined, options?: ", - "TransportRequestOptionsWithOutMeta", - " | undefined): Promise<", - "SearchResponse", - ">; >(this: That, params?: ", - "SearchRequest", - " | ", - "SearchRequest", - " | undefined, options?: ", - "TransportRequestOptionsWithMeta", - " | undefined): Promise<", - "TransportResult", - "<", - "SearchResponse", - ", unknown>>; >(this: That, params?: ", - "SearchRequest", - " | ", - "SearchRequest", - " | undefined, options?: ", - "TransportRequestOptions", - " | undefined): Promise<", - "SearchResponse", - ">; }; helpers: ", - "default", - "; name: string | symbol; [kAsyncSearch]: symbol | null; [kAutoscaling]: symbol | null; [kCat]: symbol | null; [kCcr]: symbol | null; [kCluster]: symbol | null; [kDanglingIndices]: symbol | null; [kEnrich]: symbol | null; [kEql]: symbol | null; [kFeatures]: symbol | null; [kFleet]: symbol | null; [kGraph]: symbol | null; [kIlm]: symbol | null; [kIndices]: symbol | null; [kIngest]: symbol | null; [kLicense]: symbol | null; [kLogstash]: symbol | null; [kMigration]: symbol | null; [kMl]: symbol | null; [kMonitoring]: symbol | null; [kNodes]: symbol | null; [kRollup]: symbol | null; [kSearchApplication]: symbol | null; [kSearchableSnapshots]: symbol | null; [kSecurity]: symbol | null; [kShutdown]: symbol | null; [kSlm]: symbol | null; [kSnapshot]: symbol | null; [kSql]: symbol | null; [kSsl]: symbol | null; [kSynonyms]: symbol | null; [kTasks]: symbol | null; [kTextStructure]: symbol | null; [kTransform]: symbol | null; [kWatcher]: symbol | null; [kXpack]: symbol | null; transport: ", - "default", - "; child: (opts: ", - "ClientOptions", - ") => ", - "default", - "; asyncSearch: ", - "default", - "; autoscaling: ", - "default", - "; bulk: { (this: That, params: ", - "BulkRequest", - " | ", - "BulkRequest", - ", options?: ", - "TransportRequestOptionsWithOutMeta", - " | undefined): Promise<", - "BulkResponse", - ">; (this: That, params: ", - "BulkRequest", - " | ", - "BulkRequest", - ", options?: ", - "TransportRequestOptionsWithMeta", - " | undefined): Promise<", - "TransportResult", - "<", - "BulkResponse", - ", unknown>>; (this: That, params: ", - "BulkRequest", - " | ", - "BulkRequest", - ", options?: ", - "TransportRequestOptions", - " | undefined): Promise<", - "BulkResponse", - ">; }; cat: ", - "default", - "; ccr: ", - "default", - "; clearScroll: { (this: That, params?: ", - "ClearScrollRequest", - " | ", - "ClearScrollRequest", - " | undefined, options?: ", - "TransportRequestOptionsWithOutMeta", - " | undefined): Promise<", - "ClearScrollResponse", - ">; (this: That, params?: ", - "ClearScrollRequest", - " | ", - "ClearScrollRequest", - " | undefined, options?: ", - "TransportRequestOptionsWithMeta", - " | undefined): Promise<", - "TransportResult", - "<", - "ClearScrollResponse", - ", unknown>>; (this: That, params?: ", - "ClearScrollRequest", - " | ", - "ClearScrollRequest", - " | undefined, options?: ", - "TransportRequestOptions", - " | undefined): Promise<", - "ClearScrollResponse", - ">; }; closePointInTime: { (this: That, params: ", - "ClosePointInTimeRequest", - " | ", - "ClosePointInTimeRequest", - ", options?: ", - "TransportRequestOptionsWithOutMeta", - " | undefined): Promise<", - "ClosePointInTimeResponse", - ">; (this: That, params: ", - "ClosePointInTimeRequest", - " | ", - "ClosePointInTimeRequest", - ", options?: ", - "TransportRequestOptionsWithMeta", - " | undefined): Promise<", - "TransportResult", - "<", - "ClosePointInTimeResponse", - ", unknown>>; (this: That, params: ", - "ClosePointInTimeRequest", - " | ", - "ClosePointInTimeRequest", - ", options?: ", - "TransportRequestOptions", - " | undefined): Promise<", - "ClosePointInTimeResponse", - ">; }; cluster: ", - "default", - "; count: { (this: That, params?: ", - "CountRequest", - " | ", - "CountRequest", - " | undefined, options?: ", - "TransportRequestOptionsWithOutMeta", - " | undefined): Promise<", - "CountResponse", - ">; (this: That, params?: ", - "CountRequest", - " | ", - "CountRequest", - " | undefined, options?: ", - "TransportRequestOptionsWithMeta", - " | undefined): Promise<", - "TransportResult", - "<", - "CountResponse", - ", unknown>>; (this: That, params?: ", - "CountRequest", - " | ", - "CountRequest", - " | undefined, options?: ", - "TransportRequestOptions", - " | undefined): Promise<", - "CountResponse", - ">; }; danglingIndices: ", - "default", - "; deleteByQuery: { (this: That, params: ", - "DeleteByQueryRequest", - " | ", - "DeleteByQueryRequest", - ", options?: ", - "TransportRequestOptionsWithOutMeta", - " | undefined): Promise<", - "DeleteByQueryResponse", - ">; (this: That, params: ", - "DeleteByQueryRequest", - " | ", - "DeleteByQueryRequest", - ", options?: ", - "TransportRequestOptionsWithMeta", - " | undefined): Promise<", - "TransportResult", - "<", - "DeleteByQueryResponse", - ", unknown>>; (this: That, params: ", - "DeleteByQueryRequest", - " | ", - "DeleteByQueryRequest", - ", options?: ", - "TransportRequestOptions", - " | undefined): Promise<", - "DeleteByQueryResponse", - ">; }; deleteByQueryRethrottle: { (this: That, params: ", - "DeleteByQueryRethrottleRequest", - " | ", - "DeleteByQueryRethrottleRequest", - ", options?: ", - "TransportRequestOptionsWithOutMeta", - " | undefined): Promise<", - "TasksTaskListResponseBase", - ">; (this: That, params: ", - "DeleteByQueryRethrottleRequest", - " | ", - "DeleteByQueryRethrottleRequest", - ", options?: ", - "TransportRequestOptionsWithMeta", - " | undefined): Promise<", - "TransportResult", - "<", - "TasksTaskListResponseBase", - ", unknown>>; (this: That, params: ", - "DeleteByQueryRethrottleRequest", - " | ", - "DeleteByQueryRethrottleRequest", - ", options?: ", - "TransportRequestOptions", - " | undefined): Promise<", - "TasksTaskListResponseBase", - ">; }; deleteScript: { (this: That, params: ", - "DeleteScriptRequest", - " | ", - "DeleteScriptRequest", - ", options?: ", - "TransportRequestOptionsWithOutMeta", - " | undefined): Promise<", - "AcknowledgedResponseBase", - ">; (this: That, params: ", - "DeleteScriptRequest", - " | ", - "DeleteScriptRequest", - ", options?: ", - "TransportRequestOptionsWithMeta", - " | undefined): Promise<", - "TransportResult", - "<", - "AcknowledgedResponseBase", - ", unknown>>; (this: That, params: ", - "DeleteScriptRequest", - " | ", - "DeleteScriptRequest", - ", options?: ", - "TransportRequestOptions", - " | undefined): Promise<", - "AcknowledgedResponseBase", - ">; }; enrich: ", - "default", - "; eql: ", - "default", - "; exists: { (this: That, params: ", - "ExistsRequest", - " | ", - "ExistsRequest", - ", options?: ", - "TransportRequestOptionsWithOutMeta", - " | undefined): Promise; (this: That, params: ", - "ExistsRequest", - " | ", - "ExistsRequest", - ", options?: ", - "TransportRequestOptionsWithMeta", - " | undefined): Promise<", - "TransportResult", - ">; (this: That, params: ", - "ExistsRequest", - " | ", - "ExistsRequest", - ", options?: ", - "TransportRequestOptions", - " | undefined): Promise; }; existsSource: { (this: That, params: ", - "ExistsSourceRequest", - " | ", - "ExistsSourceRequest", - ", options?: ", - "TransportRequestOptionsWithOutMeta", - " | undefined): Promise; (this: That, params: ", - "ExistsSourceRequest", - " | ", - "ExistsSourceRequest", - ", options?: ", - "TransportRequestOptionsWithMeta", - " | undefined): Promise<", - "TransportResult", - ">; (this: That, params: ", - "ExistsSourceRequest", - " | ", - "ExistsSourceRequest", - ", options?: ", - "TransportRequestOptions", - " | undefined): Promise; }; explain: { (this: That, params: ", - "ExplainRequest", - " | ", - "ExplainRequest", - ", options?: ", - "TransportRequestOptionsWithOutMeta", - " | undefined): Promise<", - "ExplainResponse", - ">; (this: That, params: ", - "ExplainRequest", - " | ", - "ExplainRequest", - ", options?: ", - "TransportRequestOptionsWithMeta", - " | undefined): Promise<", - "TransportResult", - "<", - "ExplainResponse", - ", unknown>>; (this: That, params: ", - "ExplainRequest", - " | ", - "ExplainRequest", - ", options?: ", - "TransportRequestOptions", - " | undefined): Promise<", - "ExplainResponse", - ">; }; features: ", - "default", - "; fieldCaps: { (this: That, params?: ", - "FieldCapsRequest", - " | ", - "FieldCapsRequest", - " | undefined, options?: ", - "TransportRequestOptionsWithOutMeta", - " | undefined): Promise<", - "FieldCapsResponse", - ">; (this: That, params?: ", - "FieldCapsRequest", - " | ", - "FieldCapsRequest", - " | undefined, options?: ", - "TransportRequestOptionsWithMeta", - " | undefined): Promise<", - "TransportResult", - "<", - "FieldCapsResponse", - ", unknown>>; (this: That, params?: ", - "FieldCapsRequest", - " | ", - "FieldCapsRequest", - " | undefined, options?: ", - "TransportRequestOptions", - " | undefined): Promise<", - "FieldCapsResponse", - ">; }; fleet: ", - "default", - "; getScript: { (this: That, params: ", - "GetScriptRequest", - " | ", - "GetScriptRequest", - ", options?: ", - "TransportRequestOptionsWithOutMeta", - " | undefined): Promise<", - "GetScriptResponse", - ">; (this: That, params: ", - "GetScriptRequest", - " | ", - "GetScriptRequest", - ", options?: ", - "TransportRequestOptionsWithMeta", - " | undefined): Promise<", - "TransportResult", - "<", - "GetScriptResponse", - ", unknown>>; (this: That, params: ", - "GetScriptRequest", - " | ", - "GetScriptRequest", - ", options?: ", - "TransportRequestOptions", - " | undefined): Promise<", - "GetScriptResponse", - ">; }; getScriptContext: { (this: That, params?: ", - "GetScriptContextRequest", - " | ", - "GetScriptContextRequest", - " | undefined, options?: ", - "TransportRequestOptionsWithOutMeta", - " | undefined): Promise<", - "GetScriptContextResponse", - ">; (this: That, params?: ", - "GetScriptContextRequest", - " | ", - "GetScriptContextRequest", - " | undefined, options?: ", - "TransportRequestOptionsWithMeta", - " | undefined): Promise<", - "TransportResult", - "<", - "GetScriptContextResponse", - ", unknown>>; (this: That, params?: ", - "GetScriptContextRequest", - " | ", - "GetScriptContextRequest", - " | undefined, options?: ", - "TransportRequestOptions", - " | undefined): Promise<", - "GetScriptContextResponse", - ">; }; getScriptLanguages: { (this: That, params?: ", - "GetScriptLanguagesRequest", - " | ", - "GetScriptLanguagesRequest", - " | undefined, options?: ", - "TransportRequestOptionsWithOutMeta", - " | undefined): Promise<", - "GetScriptLanguagesResponse", - ">; (this: That, params?: ", - "GetScriptLanguagesRequest", - " | ", - "GetScriptLanguagesRequest", - " | undefined, options?: ", - "TransportRequestOptionsWithMeta", - " | undefined): Promise<", - "TransportResult", - "<", - "GetScriptLanguagesResponse", - ", unknown>>; (this: That, params?: ", - "GetScriptLanguagesRequest", - " | ", - "GetScriptLanguagesRequest", - " | undefined, options?: ", - "TransportRequestOptions", - " | undefined): Promise<", - "GetScriptLanguagesResponse", - ">; }; getSource: { (this: That, params: ", - "GetSourceRequest", - " | ", - "GetSourceRequest", - ", options?: ", - "TransportRequestOptionsWithOutMeta", - " | undefined): Promise; (this: That, params: ", - "GetSourceRequest", - " | ", - "GetSourceRequest", - ", options?: ", - "TransportRequestOptionsWithMeta", - " | undefined): Promise<", - "TransportResult", - ">; (this: That, params: ", - "GetSourceRequest", - " | ", - "GetSourceRequest", - ", options?: ", - "TransportRequestOptions", - " | undefined): Promise; }; graph: ", - "default", - "; healthReport: { (this: That, params?: ", - "HealthReportRequest", - " | ", - "HealthReportRequest", - " | undefined, options?: ", - "TransportRequestOptionsWithOutMeta", - " | undefined): Promise<", - "HealthReportResponse", - ">; (this: That, params?: ", - "HealthReportRequest", - " | ", - "HealthReportRequest", - " | undefined, options?: ", - "TransportRequestOptionsWithMeta", - " | undefined): Promise<", - "TransportResult", - "<", - "HealthReportResponse", - ", unknown>>; (this: That, params?: ", - "HealthReportRequest", - " | ", - "HealthReportRequest", - " | undefined, options?: ", - "TransportRequestOptions", - " | undefined): Promise<", - "HealthReportResponse", - ">; }; ilm: ", - "default", - "; index: { (this: That, params: ", - "IndexRequest", - " | ", - "IndexRequest", - ", options?: ", - "TransportRequestOptionsWithOutMeta", - " | undefined): Promise<", - "WriteResponseBase", - ">; (this: That, params: ", - "IndexRequest", - " | ", - "IndexRequest", - ", options?: ", - "TransportRequestOptionsWithMeta", - " | undefined): Promise<", - "TransportResult", - "<", - "WriteResponseBase", - ", unknown>>; (this: That, params: ", - "IndexRequest", - " | ", - "IndexRequest", - ", options?: ", - "TransportRequestOptions", - " | undefined): Promise<", - "WriteResponseBase", - ">; }; indices: ", - "default", - "; info: { (this: That, params?: ", - "InfoRequest", - " | ", - "InfoRequest", - " | undefined, options?: ", - "TransportRequestOptionsWithOutMeta", - " | undefined): Promise<", - "InfoResponse", - ">; (this: That, params?: ", - "InfoRequest", - " | ", - "InfoRequest", - " | undefined, options?: ", - "TransportRequestOptionsWithMeta", - " | undefined): Promise<", - "TransportResult", - "<", - "InfoResponse", - ", unknown>>; (this: That, params?: ", - "InfoRequest", - " | ", - "InfoRequest", - " | undefined, options?: ", - "TransportRequestOptions", - " | undefined): Promise<", - "InfoResponse", - ">; }; ingest: ", - "default", - "; knnSearch: { (this: That, params: ", - "KnnSearchRequest", - " | ", - "KnnSearchRequest", - ", options?: ", - "TransportRequestOptionsWithOutMeta", - " | undefined): Promise<", - "KnnSearchResponse", - ">; (this: That, params: ", - "KnnSearchRequest", - " | ", - "KnnSearchRequest", - ", options?: ", - "TransportRequestOptionsWithMeta", - " | undefined): Promise<", - "TransportResult", - "<", - "KnnSearchResponse", - ", unknown>>; (this: That, params: ", - "KnnSearchRequest", - " | ", - "KnnSearchRequest", - ", options?: ", - "TransportRequestOptions", - " | undefined): Promise<", - "KnnSearchResponse", - ">; }; license: ", - "default", - "; logstash: ", - "default", - "; mget: { (this: That, params?: ", - "MgetRequest", - " | ", - "MgetRequest", - " | undefined, options?: ", - "TransportRequestOptionsWithOutMeta", - " | undefined): Promise<", - "MgetResponse", - ">; (this: That, params?: ", - "MgetRequest", - " | ", - "MgetRequest", - " | undefined, options?: ", - "TransportRequestOptionsWithMeta", - " | undefined): Promise<", - "TransportResult", - "<", - "MgetResponse", - ", unknown>>; (this: That, params?: ", - "MgetRequest", - " | ", - "MgetRequest", - " | undefined, options?: ", - "TransportRequestOptions", - " | undefined): Promise<", - "MgetResponse", - ">; }; migration: ", - "default", - "; ml: ", - "default", - "; monitoring: ", - "default", - "; msearch: { >(this: That, params: ", - "MsearchRequest", - " | ", - "MsearchRequest", - ", options?: ", - "TransportRequestOptionsWithOutMeta", - " | undefined): Promise<", - "MsearchResponse", - ">; >(this: That, params: ", - "MsearchRequest", - " | ", - "MsearchRequest", - ", options?: ", - "TransportRequestOptionsWithMeta", - " | undefined): Promise<", - "TransportResult", - "<", - "MsearchResponse", - ", unknown>>; >(this: That, params: ", - "MsearchRequest", - " | ", - "MsearchRequest", - ", options?: ", - "TransportRequestOptions", - " | undefined): Promise<", - "MsearchResponse", - ">; }; msearchTemplate: { >(this: That, params: ", - "MsearchTemplateRequest", - " | ", - "MsearchTemplateRequest", - ", options?: ", - "TransportRequestOptionsWithOutMeta", - " | undefined): Promise<", - "MsearchTemplateResponse", - ">; >(this: That, params: ", - "MsearchTemplateRequest", - " | ", - "MsearchTemplateRequest", - ", options?: ", - "TransportRequestOptionsWithMeta", - " | undefined): Promise<", - "TransportResult", - "<", - "MsearchTemplateResponse", - ", unknown>>; >(this: That, params: ", - "MsearchTemplateRequest", - " | ", - "MsearchTemplateRequest", - ", options?: ", - "TransportRequestOptions", - " | undefined): Promise<", - "MsearchTemplateResponse", - ">; }; mtermvectors: { (this: That, params?: ", - "MtermvectorsRequest", - " | ", - "MtermvectorsRequest", - " | undefined, options?: ", - "TransportRequestOptionsWithOutMeta", - " | undefined): Promise<", - "MtermvectorsResponse", - ">; (this: That, params?: ", - "MtermvectorsRequest", - " | ", - "MtermvectorsRequest", - " | undefined, options?: ", - "TransportRequestOptionsWithMeta", - " | undefined): Promise<", - "TransportResult", - "<", - "MtermvectorsResponse", - ", unknown>>; (this: That, params?: ", - "MtermvectorsRequest", - " | ", - "MtermvectorsRequest", - " | undefined, options?: ", - "TransportRequestOptions", - " | undefined): Promise<", - "MtermvectorsResponse", - ">; }; nodes: ", - "default", - "; openPointInTime: { (this: That, params: ", - "OpenPointInTimeRequest", - " | ", - "OpenPointInTimeRequest", - ", options?: ", - "TransportRequestOptionsWithOutMeta", - " | undefined): Promise<", - "OpenPointInTimeResponse", - ">; (this: That, params: ", - "OpenPointInTimeRequest", - " | ", - "OpenPointInTimeRequest", - ", options?: ", - "TransportRequestOptionsWithMeta", - " | undefined): Promise<", - "TransportResult", - "<", - "OpenPointInTimeResponse", - ", unknown>>; (this: That, params: ", - "OpenPointInTimeRequest", - " | ", - "OpenPointInTimeRequest", - ", options?: ", - "TransportRequestOptions", - " | undefined): Promise<", - "OpenPointInTimeResponse", - ">; }; ping: { (this: That, params?: ", - "PingRequest", - " | ", - "PingRequest", - " | undefined, options?: ", - "TransportRequestOptionsWithOutMeta", - " | undefined): Promise; (this: That, params?: ", - "PingRequest", - " | ", - "PingRequest", - " | undefined, options?: ", - "TransportRequestOptionsWithMeta", - " | undefined): Promise<", - "TransportResult", - ">; (this: That, params?: ", - "PingRequest", - " | ", - "PingRequest", - " | undefined, options?: ", - "TransportRequestOptions", - " | undefined): Promise; }; putScript: { (this: That, params: ", - "PutScriptRequest", - " | ", - "PutScriptRequest", - ", options?: ", - "TransportRequestOptionsWithOutMeta", - " | undefined): Promise<", - "AcknowledgedResponseBase", - ">; (this: That, params: ", - "PutScriptRequest", - " | ", - "PutScriptRequest", - ", options?: ", - "TransportRequestOptionsWithMeta", - " | undefined): Promise<", - "TransportResult", - "<", - "AcknowledgedResponseBase", - ", unknown>>; (this: That, params: ", - "PutScriptRequest", - " | ", - "PutScriptRequest", - ", options?: ", - "TransportRequestOptions", - " | undefined): Promise<", - "AcknowledgedResponseBase", - ">; }; rankEval: { (this: That, params: ", - "RankEvalRequest", - " | ", - "RankEvalRequest", - ", options?: ", - "TransportRequestOptionsWithOutMeta", - " | undefined): Promise<", - "RankEvalResponse", - ">; (this: That, params: ", - "RankEvalRequest", - " | ", - "RankEvalRequest", - ", options?: ", - "TransportRequestOptionsWithMeta", - " | undefined): Promise<", - "TransportResult", - "<", - "RankEvalResponse", - ", unknown>>; (this: That, params: ", - "RankEvalRequest", - " | ", - "RankEvalRequest", - ", options?: ", - "TransportRequestOptions", - " | undefined): Promise<", - "RankEvalResponse", - ">; }; reindex: { (this: That, params: ", - "ReindexRequest", - " | ", - "ReindexRequest", - ", options?: ", - "TransportRequestOptionsWithOutMeta", - " | undefined): Promise<", - "ReindexResponse", - ">; (this: That, params: ", - "ReindexRequest", - " | ", - "ReindexRequest", - ", options?: ", - "TransportRequestOptionsWithMeta", - " | undefined): Promise<", - "TransportResult", - "<", - "ReindexResponse", - ", unknown>>; (this: That, params: ", - "ReindexRequest", - " | ", - "ReindexRequest", - ", options?: ", - "TransportRequestOptions", - " | undefined): Promise<", - "ReindexResponse", - ">; }; reindexRethrottle: { (this: That, params: ", - "ReindexRethrottleRequest", - " | ", - "ReindexRethrottleRequest", - ", options?: ", - "TransportRequestOptionsWithOutMeta", - " | undefined): Promise<", - "ReindexRethrottleResponse", - ">; (this: That, params: ", - "ReindexRethrottleRequest", - " | ", - "ReindexRethrottleRequest", - ", options?: ", - "TransportRequestOptionsWithMeta", - " | undefined): Promise<", - "TransportResult", - "<", - "ReindexRethrottleResponse", - ", unknown>>; (this: That, params: ", - "ReindexRethrottleRequest", - " | ", - "ReindexRethrottleRequest", - ", options?: ", - "TransportRequestOptions", - " | undefined): Promise<", - "ReindexRethrottleResponse", - ">; }; renderSearchTemplate: { (this: That, params?: ", - "RenderSearchTemplateRequest", - " | ", - "RenderSearchTemplateRequest", - " | undefined, options?: ", - "TransportRequestOptionsWithOutMeta", - " | undefined): Promise<", - "RenderSearchTemplateResponse", - ">; (this: That, params?: ", - "RenderSearchTemplateRequest", - " | ", - "RenderSearchTemplateRequest", - " | undefined, options?: ", - "TransportRequestOptionsWithMeta", - " | undefined): Promise<", - "TransportResult", - "<", - "RenderSearchTemplateResponse", - ", unknown>>; (this: That, params?: ", - "RenderSearchTemplateRequest", - " | ", - "RenderSearchTemplateRequest", - " | undefined, options?: ", - "TransportRequestOptions", - " | undefined): Promise<", - "RenderSearchTemplateResponse", - ">; }; rollup: ", - "default", - "; scriptsPainlessExecute: { (this: That, params?: ", - "ScriptsPainlessExecuteRequest", - " | ", - "ScriptsPainlessExecuteRequest", - " | undefined, options?: ", - "TransportRequestOptionsWithOutMeta", - " | undefined): Promise<", - "ScriptsPainlessExecuteResponse", - ">; (this: That, params?: ", - "ScriptsPainlessExecuteRequest", - " | ", - "ScriptsPainlessExecuteRequest", - " | undefined, options?: ", - "TransportRequestOptionsWithMeta", - " | undefined): Promise<", - "TransportResult", - "<", - "ScriptsPainlessExecuteResponse", - ", unknown>>; (this: That, params?: ", - "ScriptsPainlessExecuteRequest", - " | ", - "ScriptsPainlessExecuteRequest", - " | undefined, options?: ", - "TransportRequestOptions", - " | undefined): Promise<", - "ScriptsPainlessExecuteResponse", - ">; }; scroll: { >(this: That, params: ", - "ScrollRequest", - " | ", - "ScrollRequest", - ", options?: ", - "TransportRequestOptionsWithOutMeta", - " | undefined): Promise<", - "ScrollResponse", - ">; >(this: That, params: ", - "ScrollRequest", - " | ", - "ScrollRequest", - ", options?: ", - "TransportRequestOptionsWithMeta", - " | undefined): Promise<", - "TransportResult", - "<", - "ScrollResponse", - ", unknown>>; >(this: That, params: ", - "ScrollRequest", - " | ", - "ScrollRequest", - ", options?: ", - "TransportRequestOptions", - " | undefined): Promise<", - "ScrollResponse", - ">; }; searchApplication: ", - "default", - "; searchMvt: { (this: That, params: ", - "SearchMvtRequest", - " | ", - "SearchMvtRequest", - ", options?: ", - "TransportRequestOptionsWithOutMeta", - " | undefined): Promise; (this: That, params: ", - "SearchMvtRequest", - " | ", - "SearchMvtRequest", - ", options?: ", - "TransportRequestOptionsWithMeta", - " | undefined): Promise<", - "TransportResult", - ">; (this: That, params: ", - "SearchMvtRequest", - " | ", - "SearchMvtRequest", - ", options?: ", - "TransportRequestOptions", - " | undefined): Promise; }; searchShards: { (this: That, params?: ", - "SearchShardsRequest", - " | ", - "SearchShardsRequest", - " | undefined, options?: ", - "TransportRequestOptionsWithOutMeta", - " | undefined): Promise<", - "SearchShardsResponse", - ">; (this: That, params?: ", - "SearchShardsRequest", - " | ", - "SearchShardsRequest", - " | undefined, options?: ", - "TransportRequestOptionsWithMeta", - " | undefined): Promise<", - "TransportResult", - "<", - "SearchShardsResponse", - ", unknown>>; (this: That, params?: ", - "SearchShardsRequest", - " | ", - "SearchShardsRequest", - " | undefined, options?: ", - "TransportRequestOptions", - " | undefined): Promise<", - "SearchShardsResponse", - ">; }; searchTemplate: { (this: That, params?: ", - "SearchTemplateRequest", - " | ", - "SearchTemplateRequest", - " | undefined, options?: ", - "TransportRequestOptionsWithOutMeta", - " | undefined): Promise<", - "SearchTemplateResponse", - ">; (this: That, params?: ", - "SearchTemplateRequest", - " | ", - "SearchTemplateRequest", - " | undefined, options?: ", - "TransportRequestOptionsWithMeta", - " | undefined): Promise<", - "TransportResult", - "<", - "SearchTemplateResponse", - ", unknown>>; (this: That, params?: ", - "SearchTemplateRequest", - " | ", - "SearchTemplateRequest", - " | undefined, options?: ", - "TransportRequestOptions", - " | undefined): Promise<", - "SearchTemplateResponse", - ">; }; searchableSnapshots: ", - "default", - "; security: ", - "default", - "; shutdown: ", - "default", - "; slm: ", - "default", - "; snapshot: ", - "default", - "; sql: ", - "default", - "; ssl: ", - "default", - "; synonyms: ", - "default", - "; tasks: ", - "default", - "; termsEnum: { (this: That, params: ", - "TermsEnumRequest", - " | ", - "TermsEnumRequest", - ", options?: ", - "TransportRequestOptionsWithOutMeta", - " | undefined): Promise<", - "TermsEnumResponse", - ">; (this: That, params: ", - "TermsEnumRequest", - " | ", - "TermsEnumRequest", - ", options?: ", - "TransportRequestOptionsWithMeta", - " | undefined): Promise<", - "TransportResult", - "<", - "TermsEnumResponse", - ", unknown>>; (this: That, params: ", - "TermsEnumRequest", - " | ", - "TermsEnumRequest", - ", options?: ", - "TransportRequestOptions", - " | undefined): Promise<", - "TermsEnumResponse", - ">; }; termvectors: { (this: That, params: ", - "TermvectorsRequest", - " | ", - "TermvectorsRequest", - ", options?: ", - "TransportRequestOptionsWithOutMeta", - " | undefined): Promise<", - "TermvectorsResponse", - ">; (this: That, params: ", - "TermvectorsRequest", - " | ", - "TermvectorsRequest", - ", options?: ", - "TransportRequestOptionsWithMeta", - " | undefined): Promise<", - "TransportResult", - "<", - "TermvectorsResponse", - ", unknown>>; (this: That, params: ", - "TermvectorsRequest", - " | ", - "TermvectorsRequest", - ", options?: ", - "TransportRequestOptions", - " | undefined): Promise<", - "TermvectorsResponse", - ">; }; textStructure: ", - "default", - "; transform: ", - "default", - "; updateByQuery: { (this: That, params: ", - "UpdateByQueryRequest", - " | ", - "UpdateByQueryRequest", - ", options?: ", - "TransportRequestOptionsWithOutMeta", - " | undefined): Promise<", - "UpdateByQueryResponse", - ">; (this: That, params: ", - "UpdateByQueryRequest", - " | ", - "UpdateByQueryRequest", - ", options?: ", - "TransportRequestOptionsWithMeta", - " | undefined): Promise<", - "TransportResult", - "<", - "UpdateByQueryResponse", - ", unknown>>; (this: That, params: ", - "UpdateByQueryRequest", - " | ", - "UpdateByQueryRequest", - ", options?: ", - "TransportRequestOptions", - " | undefined): Promise<", - "UpdateByQueryResponse", - ">; }; updateByQueryRethrottle: { (this: That, params: ", - "UpdateByQueryRethrottleRequest", - " | ", - "UpdateByQueryRethrottleRequest", - ", options?: ", - "TransportRequestOptionsWithOutMeta", - " | undefined): Promise<", - "UpdateByQueryRethrottleResponse", - ">; (this: That, params: ", - "UpdateByQueryRethrottleRequest", - " | ", - "UpdateByQueryRethrottleRequest", - ", options?: ", - "TransportRequestOptionsWithMeta", - " | undefined): Promise<", - "TransportResult", - "<", - "UpdateByQueryRethrottleResponse", - ", unknown>>; (this: That, params: ", - "UpdateByQueryRethrottleRequest", - " | ", - "UpdateByQueryRethrottleRequest", - ", options?: ", - "TransportRequestOptions", - " | undefined): Promise<", - "UpdateByQueryRethrottleResponse", - ">; }; watcher: ", - "default", - "; xpack: ", - "default", - "; }" - ], - "path": "x-pack/plugins/fleet/server/services/agent_policy.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "fleet", - "id": "def-server.AgentPolicyServiceInterface.bumpRevision.$3", - "type": "string", - "tags": [], - "label": "id", - "description": [], - "path": "x-pack/plugins/fleet/server/services/agent_policy.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "fleet", - "id": "def-server.AgentPolicyServiceInterface.bumpRevision.$4", - "type": "Object", - "tags": [], - "label": "options", - "description": [], - "signature": [ - "{ user?: ", - { - "pluginId": "@kbn/security-plugin-types-common", - "scope": "common", - "docId": "kibKbnSecurityPluginTypesCommonPluginApi", - "section": "def-common.AuthenticatedUser", - "text": "AuthenticatedUser" - }, - " | undefined; removeProtection?: boolean | undefined; } | undefined" - ], - "path": "x-pack/plugins/fleet/server/services/agent_policy.ts", - "deprecated": false, - "trackAdoption": false } ] } diff --git a/api_docs/fleet.mdx b/api_docs/fleet.mdx index 389984effb4a6..3fa5b6645eddc 100644 --- a/api_docs/fleet.mdx +++ b/api_docs/fleet.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/fleet title: "fleet" image: https://source.unsplash.com/400x175/?github description: API docs for the fleet plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'fleet'] --- import fleetObj from './fleet.devdocs.json'; @@ -21,7 +21,7 @@ Contact [@elastic/fleet](https://github.com/orgs/elastic/teams/fleet) for questi | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 1229 | 3 | 1110 | 54 | +| 1226 | 3 | 1107 | 54 | ## Client diff --git a/api_docs/global_search.devdocs.json b/api_docs/global_search.devdocs.json index 2131cc5d23d95..698f2bfcca06f 100644 --- a/api_docs/global_search.devdocs.json +++ b/api_docs/global_search.devdocs.json @@ -82,6 +82,29 @@ "path": "x-pack/plugins/global_search/public/services/types.ts", "deprecated": false, "trackAdoption": false + }, + { + "parentPluginId": "globalSearch", + "id": "def-public.GlobalSearchFindOptions.client", + "type": "Object", + "tags": [], + "label": "client", + "description": [ + "\nA ES client of type IScopedClusterClient is passed to the `find` call.\nWhen performing calls to ES, the interested provider can utilize this parameter to identify the specific cluster." + ], + "signature": [ + { + "pluginId": "@kbn/core-elasticsearch-server", + "scope": "common", + "docId": "kibKbnCoreElasticsearchServerPluginApi", + "section": "def-common.IScopedClusterClient", + "text": "IScopedClusterClient" + }, + " | undefined" + ], + "path": "x-pack/plugins/global_search/public/services/types.ts", + "deprecated": false, + "trackAdoption": false } ], "initialIsOpen": false @@ -193,6 +216,29 @@ "deprecated": false, "trackAdoption": false }, + { + "parentPluginId": "globalSearch", + "id": "def-public.GlobalSearchProviderFindOptions.client", + "type": "Object", + "tags": [], + "label": "client", + "description": [ + "\nA ES client of type IScopedClusterClient is passed to the `find` call.\nWhen performing calls to ES, the interested provider can utilize this parameter to identify the specific cluster." + ], + "signature": [ + { + "pluginId": "@kbn/core-elasticsearch-server", + "scope": "common", + "docId": "kibKbnCoreElasticsearchServerPluginApi", + "section": "def-common.IScopedClusterClient", + "text": "IScopedClusterClient" + }, + " | undefined" + ], + "path": "x-pack/plugins/global_search/common/types.ts", + "deprecated": false, + "trackAdoption": false + }, { "parentPluginId": "globalSearch", "id": "def-public.GlobalSearchProviderFindOptions.maxResults", @@ -630,6 +676,29 @@ "path": "x-pack/plugins/global_search/server/types.ts", "deprecated": false, "trackAdoption": false + }, + { + "parentPluginId": "globalSearch", + "id": "def-server.GlobalSearchFindOptions.client", + "type": "Object", + "tags": [], + "label": "client", + "description": [ + "\nA ES client of type IScopedClusterClient is passed to the `find` call.\nWhen performing calls to ES, the interested provider can utilize this parameter to identify the specific cluster." + ], + "signature": [ + { + "pluginId": "@kbn/core-elasticsearch-server", + "scope": "common", + "docId": "kibKbnCoreElasticsearchServerPluginApi", + "section": "def-common.IScopedClusterClient", + "text": "IScopedClusterClient" + }, + " | undefined" + ], + "path": "x-pack/plugins/global_search/server/types.ts", + "deprecated": false, + "trackAdoption": false } ], "initialIsOpen": false @@ -741,6 +810,29 @@ "deprecated": false, "trackAdoption": false }, + { + "parentPluginId": "globalSearch", + "id": "def-server.GlobalSearchProviderFindOptions.client", + "type": "Object", + "tags": [], + "label": "client", + "description": [ + "\nA ES client of type IScopedClusterClient is passed to the `find` call.\nWhen performing calls to ES, the interested provider can utilize this parameter to identify the specific cluster." + ], + "signature": [ + { + "pluginId": "@kbn/core-elasticsearch-server", + "scope": "common", + "docId": "kibKbnCoreElasticsearchServerPluginApi", + "section": "def-common.IScopedClusterClient", + "text": "IScopedClusterClient" + }, + " | undefined" + ], + "path": "x-pack/plugins/global_search/common/types.ts", + "deprecated": false, + "trackAdoption": false + }, { "parentPluginId": "globalSearch", "id": "def-server.GlobalSearchProviderFindOptions.maxResults", diff --git a/api_docs/global_search.mdx b/api_docs/global_search.mdx index a7a467c1dbcde..335b3cffffac0 100644 --- a/api_docs/global_search.mdx +++ b/api_docs/global_search.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/globalSearch title: "globalSearch" image: https://source.unsplash.com/400x175/?github description: API docs for the globalSearch plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'globalSearch'] --- import globalSearchObj from './global_search.devdocs.json'; @@ -21,7 +21,7 @@ Contact [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sh | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 68 | 0 | 14 | 5 | +| 72 | 0 | 14 | 5 | ## Client diff --git a/api_docs/guided_onboarding.mdx b/api_docs/guided_onboarding.mdx index cd14e07a29534..36ad71a2de3eb 100644 --- a/api_docs/guided_onboarding.mdx +++ b/api_docs/guided_onboarding.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/guidedOnboarding title: "guidedOnboarding" image: https://source.unsplash.com/400x175/?github description: API docs for the guidedOnboarding plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'guidedOnboarding'] --- import guidedOnboardingObj from './guided_onboarding.devdocs.json'; diff --git a/api_docs/home.mdx b/api_docs/home.mdx index 71bb2c33ca874..b94ce256e93c0 100644 --- a/api_docs/home.mdx +++ b/api_docs/home.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/home title: "home" image: https://source.unsplash.com/400x175/?github description: API docs for the home plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'home'] --- import homeObj from './home.devdocs.json'; diff --git a/api_docs/image_embeddable.mdx b/api_docs/image_embeddable.mdx index b7191bd881032..7844bc44ad0f5 100644 --- a/api_docs/image_embeddable.mdx +++ b/api_docs/image_embeddable.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/imageEmbeddable title: "imageEmbeddable" image: https://source.unsplash.com/400x175/?github description: API docs for the imageEmbeddable plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'imageEmbeddable'] --- import imageEmbeddableObj from './image_embeddable.devdocs.json'; diff --git a/api_docs/index_lifecycle_management.mdx b/api_docs/index_lifecycle_management.mdx index 7040da45958a6..6368835d3f659 100644 --- a/api_docs/index_lifecycle_management.mdx +++ b/api_docs/index_lifecycle_management.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/indexLifecycleManagement title: "indexLifecycleManagement" image: https://source.unsplash.com/400x175/?github description: API docs for the indexLifecycleManagement plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'indexLifecycleManagement'] --- import indexLifecycleManagementObj from './index_lifecycle_management.devdocs.json'; diff --git a/api_docs/index_management.mdx b/api_docs/index_management.mdx index 45cc2ecbd526c..78c80e9d97c8a 100644 --- a/api_docs/index_management.mdx +++ b/api_docs/index_management.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/indexManagement title: "indexManagement" image: https://source.unsplash.com/400x175/?github description: API docs for the indexManagement plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'indexManagement'] --- import indexManagementObj from './index_management.devdocs.json'; diff --git a/api_docs/infra.mdx b/api_docs/infra.mdx index 0abb9692de9b1..14ae56978d681 100644 --- a/api_docs/infra.mdx +++ b/api_docs/infra.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/infra title: "infra" image: https://source.unsplash.com/400x175/?github description: API docs for the infra plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'infra'] --- import infraObj from './infra.devdocs.json'; diff --git a/api_docs/ingest_pipelines.mdx b/api_docs/ingest_pipelines.mdx index 2d19631475c3e..13a2625238af0 100644 --- a/api_docs/ingest_pipelines.mdx +++ b/api_docs/ingest_pipelines.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/ingestPipelines title: "ingestPipelines" image: https://source.unsplash.com/400x175/?github description: API docs for the ingestPipelines plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'ingestPipelines'] --- import ingestPipelinesObj from './ingest_pipelines.devdocs.json'; diff --git a/api_docs/inspector.mdx b/api_docs/inspector.mdx index a75fe60a5ee3d..73667404aad4d 100644 --- a/api_docs/inspector.mdx +++ b/api_docs/inspector.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/inspector title: "inspector" image: https://source.unsplash.com/400x175/?github description: API docs for the inspector plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'inspector'] --- import inspectorObj from './inspector.devdocs.json'; diff --git a/api_docs/interactive_setup.mdx b/api_docs/interactive_setup.mdx index 962c8dc89ef78..7ecba0a943374 100644 --- a/api_docs/interactive_setup.mdx +++ b/api_docs/interactive_setup.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/interactiveSetup title: "interactiveSetup" image: https://source.unsplash.com/400x175/?github description: API docs for the interactiveSetup plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'interactiveSetup'] --- import interactiveSetupObj from './interactive_setup.devdocs.json'; diff --git a/api_docs/kbn_ace.mdx b/api_docs/kbn_ace.mdx index 70e9e31b5b2c0..da012b08c022e 100644 --- a/api_docs/kbn_ace.mdx +++ b/api_docs/kbn_ace.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ace title: "@kbn/ace" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ace plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ace'] --- import kbnAceObj from './kbn_ace.devdocs.json'; diff --git a/api_docs/kbn_actions_types.mdx b/api_docs/kbn_actions_types.mdx index 63336ad85d404..b4b4d23623ce4 100644 --- a/api_docs/kbn_actions_types.mdx +++ b/api_docs/kbn_actions_types.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-actions-types title: "@kbn/actions-types" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/actions-types plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/actions-types'] --- import kbnActionsTypesObj from './kbn_actions_types.devdocs.json'; diff --git a/api_docs/kbn_aiops_components.mdx b/api_docs/kbn_aiops_components.mdx index c8c4e9b8127a2..74e9b23d912cc 100644 --- a/api_docs/kbn_aiops_components.mdx +++ b/api_docs/kbn_aiops_components.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-aiops-components title: "@kbn/aiops-components" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/aiops-components plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/aiops-components'] --- import kbnAiopsComponentsObj from './kbn_aiops_components.devdocs.json'; diff --git a/api_docs/kbn_aiops_utils.mdx b/api_docs/kbn_aiops_utils.mdx index 8de7c0a86b8e2..0401ba58c1312 100644 --- a/api_docs/kbn_aiops_utils.mdx +++ b/api_docs/kbn_aiops_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-aiops-utils title: "@kbn/aiops-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/aiops-utils plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/aiops-utils'] --- import kbnAiopsUtilsObj from './kbn_aiops_utils.devdocs.json'; diff --git a/api_docs/kbn_alerting_api_integration_helpers.mdx b/api_docs/kbn_alerting_api_integration_helpers.mdx index 7f59d2bf479c9..2e421a66dfebb 100644 --- a/api_docs/kbn_alerting_api_integration_helpers.mdx +++ b/api_docs/kbn_alerting_api_integration_helpers.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-alerting-api-integration-helpers title: "@kbn/alerting-api-integration-helpers" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/alerting-api-integration-helpers plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/alerting-api-integration-helpers'] --- import kbnAlertingApiIntegrationHelpersObj from './kbn_alerting_api_integration_helpers.devdocs.json'; diff --git a/api_docs/kbn_alerting_state_types.mdx b/api_docs/kbn_alerting_state_types.mdx index c767b34c616dc..0cf50635fd16b 100644 --- a/api_docs/kbn_alerting_state_types.mdx +++ b/api_docs/kbn_alerting_state_types.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-alerting-state-types title: "@kbn/alerting-state-types" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/alerting-state-types plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/alerting-state-types'] --- import kbnAlertingStateTypesObj from './kbn_alerting_state_types.devdocs.json'; diff --git a/api_docs/kbn_alerting_types.mdx b/api_docs/kbn_alerting_types.mdx index bbe6b89aa51a2..a9a15f8bc1ddf 100644 --- a/api_docs/kbn_alerting_types.mdx +++ b/api_docs/kbn_alerting_types.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-alerting-types title: "@kbn/alerting-types" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/alerting-types plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/alerting-types'] --- import kbnAlertingTypesObj from './kbn_alerting_types.devdocs.json'; diff --git a/api_docs/kbn_alerts_as_data_utils.mdx b/api_docs/kbn_alerts_as_data_utils.mdx index 6a87bdbcd826a..4708a140bdf87 100644 --- a/api_docs/kbn_alerts_as_data_utils.mdx +++ b/api_docs/kbn_alerts_as_data_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-alerts-as-data-utils title: "@kbn/alerts-as-data-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/alerts-as-data-utils plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/alerts-as-data-utils'] --- import kbnAlertsAsDataUtilsObj from './kbn_alerts_as_data_utils.devdocs.json'; diff --git a/api_docs/kbn_alerts_ui_shared.mdx b/api_docs/kbn_alerts_ui_shared.mdx index 86fe0688a6881..d061529be4256 100644 --- a/api_docs/kbn_alerts_ui_shared.mdx +++ b/api_docs/kbn_alerts_ui_shared.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-alerts-ui-shared title: "@kbn/alerts-ui-shared" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/alerts-ui-shared plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/alerts-ui-shared'] --- import kbnAlertsUiSharedObj from './kbn_alerts_ui_shared.devdocs.json'; diff --git a/api_docs/kbn_analytics.mdx b/api_docs/kbn_analytics.mdx index 6568f971edec0..3e29a0afc28c8 100644 --- a/api_docs/kbn_analytics.mdx +++ b/api_docs/kbn_analytics.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-analytics title: "@kbn/analytics" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/analytics plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/analytics'] --- import kbnAnalyticsObj from './kbn_analytics.devdocs.json'; diff --git a/api_docs/kbn_analytics_client.mdx b/api_docs/kbn_analytics_client.mdx index 2efcb54df1b00..14bbca6ebe834 100644 --- a/api_docs/kbn_analytics_client.mdx +++ b/api_docs/kbn_analytics_client.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-analytics-client title: "@kbn/analytics-client" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/analytics-client plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/analytics-client'] --- import kbnAnalyticsClientObj from './kbn_analytics_client.devdocs.json'; diff --git a/api_docs/kbn_analytics_collection_utils.mdx b/api_docs/kbn_analytics_collection_utils.mdx index 1b17ae5fc67f6..a19a6155ee41a 100644 --- a/api_docs/kbn_analytics_collection_utils.mdx +++ b/api_docs/kbn_analytics_collection_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-analytics-collection-utils title: "@kbn/analytics-collection-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/analytics-collection-utils plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/analytics-collection-utils'] --- import kbnAnalyticsCollectionUtilsObj from './kbn_analytics_collection_utils.devdocs.json'; diff --git a/api_docs/kbn_analytics_shippers_elastic_v3_browser.mdx b/api_docs/kbn_analytics_shippers_elastic_v3_browser.mdx index 590d3de55c1d7..179b8dc9bf238 100644 --- a/api_docs/kbn_analytics_shippers_elastic_v3_browser.mdx +++ b/api_docs/kbn_analytics_shippers_elastic_v3_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-analytics-shippers-elastic-v3-browser title: "@kbn/analytics-shippers-elastic-v3-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/analytics-shippers-elastic-v3-browser plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/analytics-shippers-elastic-v3-browser'] --- import kbnAnalyticsShippersElasticV3BrowserObj from './kbn_analytics_shippers_elastic_v3_browser.devdocs.json'; diff --git a/api_docs/kbn_analytics_shippers_elastic_v3_common.mdx b/api_docs/kbn_analytics_shippers_elastic_v3_common.mdx index 090b486a67f01..aa95d34dc0adf 100644 --- a/api_docs/kbn_analytics_shippers_elastic_v3_common.mdx +++ b/api_docs/kbn_analytics_shippers_elastic_v3_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-analytics-shippers-elastic-v3-common title: "@kbn/analytics-shippers-elastic-v3-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/analytics-shippers-elastic-v3-common plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/analytics-shippers-elastic-v3-common'] --- import kbnAnalyticsShippersElasticV3CommonObj from './kbn_analytics_shippers_elastic_v3_common.devdocs.json'; diff --git a/api_docs/kbn_analytics_shippers_elastic_v3_server.mdx b/api_docs/kbn_analytics_shippers_elastic_v3_server.mdx index 73829e2ecfd13..d358c7bc6f5d7 100644 --- a/api_docs/kbn_analytics_shippers_elastic_v3_server.mdx +++ b/api_docs/kbn_analytics_shippers_elastic_v3_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-analytics-shippers-elastic-v3-server title: "@kbn/analytics-shippers-elastic-v3-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/analytics-shippers-elastic-v3-server plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/analytics-shippers-elastic-v3-server'] --- import kbnAnalyticsShippersElasticV3ServerObj from './kbn_analytics_shippers_elastic_v3_server.devdocs.json'; diff --git a/api_docs/kbn_analytics_shippers_fullstory.mdx b/api_docs/kbn_analytics_shippers_fullstory.mdx index b386f05b197d6..99b1c48f7cd27 100644 --- a/api_docs/kbn_analytics_shippers_fullstory.mdx +++ b/api_docs/kbn_analytics_shippers_fullstory.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-analytics-shippers-fullstory title: "@kbn/analytics-shippers-fullstory" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/analytics-shippers-fullstory plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/analytics-shippers-fullstory'] --- import kbnAnalyticsShippersFullstoryObj from './kbn_analytics_shippers_fullstory.devdocs.json'; diff --git a/api_docs/kbn_apm_config_loader.mdx b/api_docs/kbn_apm_config_loader.mdx index 168150a2640b8..4488b1d531d65 100644 --- a/api_docs/kbn_apm_config_loader.mdx +++ b/api_docs/kbn_apm_config_loader.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-apm-config-loader title: "@kbn/apm-config-loader" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/apm-config-loader plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/apm-config-loader'] --- import kbnApmConfigLoaderObj from './kbn_apm_config_loader.devdocs.json'; diff --git a/api_docs/kbn_apm_synthtrace.mdx b/api_docs/kbn_apm_synthtrace.mdx index f2e954583b777..e9f234e3e858c 100644 --- a/api_docs/kbn_apm_synthtrace.mdx +++ b/api_docs/kbn_apm_synthtrace.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-apm-synthtrace title: "@kbn/apm-synthtrace" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/apm-synthtrace plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/apm-synthtrace'] --- import kbnApmSynthtraceObj from './kbn_apm_synthtrace.devdocs.json'; diff --git a/api_docs/kbn_apm_synthtrace_client.mdx b/api_docs/kbn_apm_synthtrace_client.mdx index fa0c9eade6bc4..f54c2c23503ea 100644 --- a/api_docs/kbn_apm_synthtrace_client.mdx +++ b/api_docs/kbn_apm_synthtrace_client.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-apm-synthtrace-client title: "@kbn/apm-synthtrace-client" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/apm-synthtrace-client plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/apm-synthtrace-client'] --- import kbnApmSynthtraceClientObj from './kbn_apm_synthtrace_client.devdocs.json'; diff --git a/api_docs/kbn_apm_utils.mdx b/api_docs/kbn_apm_utils.mdx index bdb8bcb8c00db..fb03c239aa01c 100644 --- a/api_docs/kbn_apm_utils.mdx +++ b/api_docs/kbn_apm_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-apm-utils title: "@kbn/apm-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/apm-utils plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/apm-utils'] --- import kbnApmUtilsObj from './kbn_apm_utils.devdocs.json'; diff --git a/api_docs/kbn_axe_config.mdx b/api_docs/kbn_axe_config.mdx index 44f180729e240..5b12bc3f1a4e8 100644 --- a/api_docs/kbn_axe_config.mdx +++ b/api_docs/kbn_axe_config.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-axe-config title: "@kbn/axe-config" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/axe-config plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/axe-config'] --- import kbnAxeConfigObj from './kbn_axe_config.devdocs.json'; diff --git a/api_docs/kbn_bfetch_error.mdx b/api_docs/kbn_bfetch_error.mdx index defd8e06bccf2..adbcbac7298ad 100644 --- a/api_docs/kbn_bfetch_error.mdx +++ b/api_docs/kbn_bfetch_error.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-bfetch-error title: "@kbn/bfetch-error" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/bfetch-error plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/bfetch-error'] --- import kbnBfetchErrorObj from './kbn_bfetch_error.devdocs.json'; diff --git a/api_docs/kbn_calculate_auto.mdx b/api_docs/kbn_calculate_auto.mdx index 568ebb4d82bcd..55aae903d79a6 100644 --- a/api_docs/kbn_calculate_auto.mdx +++ b/api_docs/kbn_calculate_auto.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-calculate-auto title: "@kbn/calculate-auto" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/calculate-auto plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/calculate-auto'] --- import kbnCalculateAutoObj from './kbn_calculate_auto.devdocs.json'; diff --git a/api_docs/kbn_calculate_width_from_char_count.mdx b/api_docs/kbn_calculate_width_from_char_count.mdx index cf2397319f82b..514aaf67f5938 100644 --- a/api_docs/kbn_calculate_width_from_char_count.mdx +++ b/api_docs/kbn_calculate_width_from_char_count.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-calculate-width-from-char-count title: "@kbn/calculate-width-from-char-count" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/calculate-width-from-char-count plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/calculate-width-from-char-count'] --- import kbnCalculateWidthFromCharCountObj from './kbn_calculate_width_from_char_count.devdocs.json'; diff --git a/api_docs/kbn_cases_components.mdx b/api_docs/kbn_cases_components.mdx index 0a301678d7d86..3554ee978b1f1 100644 --- a/api_docs/kbn_cases_components.mdx +++ b/api_docs/kbn_cases_components.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-cases-components title: "@kbn/cases-components" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/cases-components plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/cases-components'] --- import kbnCasesComponentsObj from './kbn_cases_components.devdocs.json'; diff --git a/api_docs/kbn_cell_actions.mdx b/api_docs/kbn_cell_actions.mdx index 60a5b435df038..ecb7e12785e74 100644 --- a/api_docs/kbn_cell_actions.mdx +++ b/api_docs/kbn_cell_actions.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-cell-actions title: "@kbn/cell-actions" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/cell-actions plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/cell-actions'] --- import kbnCellActionsObj from './kbn_cell_actions.devdocs.json'; diff --git a/api_docs/kbn_chart_expressions_common.mdx b/api_docs/kbn_chart_expressions_common.mdx index a88eacc028538..87325595c901f 100644 --- a/api_docs/kbn_chart_expressions_common.mdx +++ b/api_docs/kbn_chart_expressions_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-chart-expressions-common title: "@kbn/chart-expressions-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/chart-expressions-common plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/chart-expressions-common'] --- import kbnChartExpressionsCommonObj from './kbn_chart_expressions_common.devdocs.json'; diff --git a/api_docs/kbn_chart_icons.mdx b/api_docs/kbn_chart_icons.mdx index 144686854d95b..700cccecaab50 100644 --- a/api_docs/kbn_chart_icons.mdx +++ b/api_docs/kbn_chart_icons.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-chart-icons title: "@kbn/chart-icons" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/chart-icons plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/chart-icons'] --- import kbnChartIconsObj from './kbn_chart_icons.devdocs.json'; diff --git a/api_docs/kbn_ci_stats_core.mdx b/api_docs/kbn_ci_stats_core.mdx index c2f6768fa6e24..efbc000d28b34 100644 --- a/api_docs/kbn_ci_stats_core.mdx +++ b/api_docs/kbn_ci_stats_core.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ci-stats-core title: "@kbn/ci-stats-core" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ci-stats-core plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ci-stats-core'] --- import kbnCiStatsCoreObj from './kbn_ci_stats_core.devdocs.json'; diff --git a/api_docs/kbn_ci_stats_performance_metrics.mdx b/api_docs/kbn_ci_stats_performance_metrics.mdx index 436eecd6bd339..6de2252894082 100644 --- a/api_docs/kbn_ci_stats_performance_metrics.mdx +++ b/api_docs/kbn_ci_stats_performance_metrics.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ci-stats-performance-metrics title: "@kbn/ci-stats-performance-metrics" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ci-stats-performance-metrics plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ci-stats-performance-metrics'] --- import kbnCiStatsPerformanceMetricsObj from './kbn_ci_stats_performance_metrics.devdocs.json'; diff --git a/api_docs/kbn_ci_stats_reporter.mdx b/api_docs/kbn_ci_stats_reporter.mdx index e2f386a073bcd..45d92b2c6a81c 100644 --- a/api_docs/kbn_ci_stats_reporter.mdx +++ b/api_docs/kbn_ci_stats_reporter.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ci-stats-reporter title: "@kbn/ci-stats-reporter" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ci-stats-reporter plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ci-stats-reporter'] --- import kbnCiStatsReporterObj from './kbn_ci_stats_reporter.devdocs.json'; diff --git a/api_docs/kbn_cli_dev_mode.mdx b/api_docs/kbn_cli_dev_mode.mdx index d7efb2ced942e..76c99a6286a0f 100644 --- a/api_docs/kbn_cli_dev_mode.mdx +++ b/api_docs/kbn_cli_dev_mode.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-cli-dev-mode title: "@kbn/cli-dev-mode" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/cli-dev-mode plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/cli-dev-mode'] --- import kbnCliDevModeObj from './kbn_cli_dev_mode.devdocs.json'; diff --git a/api_docs/kbn_code_editor.mdx b/api_docs/kbn_code_editor.mdx index 135b0a9dcf811..70fd4bf8db21a 100644 --- a/api_docs/kbn_code_editor.mdx +++ b/api_docs/kbn_code_editor.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-code-editor title: "@kbn/code-editor" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/code-editor plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/code-editor'] --- import kbnCodeEditorObj from './kbn_code_editor.devdocs.json'; diff --git a/api_docs/kbn_code_editor_mock.mdx b/api_docs/kbn_code_editor_mock.mdx index 1001c9c34d914..f57211f1e5718 100644 --- a/api_docs/kbn_code_editor_mock.mdx +++ b/api_docs/kbn_code_editor_mock.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-code-editor-mock title: "@kbn/code-editor-mock" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/code-editor-mock plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/code-editor-mock'] --- import kbnCodeEditorMockObj from './kbn_code_editor_mock.devdocs.json'; diff --git a/api_docs/kbn_code_owners.mdx b/api_docs/kbn_code_owners.mdx index 3896b2eb6fa51..855077e6d9b8b 100644 --- a/api_docs/kbn_code_owners.mdx +++ b/api_docs/kbn_code_owners.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-code-owners title: "@kbn/code-owners" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/code-owners plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/code-owners'] --- import kbnCodeOwnersObj from './kbn_code_owners.devdocs.json'; diff --git a/api_docs/kbn_coloring.mdx b/api_docs/kbn_coloring.mdx index 85b0592b9883a..d76690d280ee5 100644 --- a/api_docs/kbn_coloring.mdx +++ b/api_docs/kbn_coloring.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-coloring title: "@kbn/coloring" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/coloring plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/coloring'] --- import kbnColoringObj from './kbn_coloring.devdocs.json'; diff --git a/api_docs/kbn_config.mdx b/api_docs/kbn_config.mdx index 371dff3e477dd..52fb5a63943ce 100644 --- a/api_docs/kbn_config.mdx +++ b/api_docs/kbn_config.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-config title: "@kbn/config" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/config plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/config'] --- import kbnConfigObj from './kbn_config.devdocs.json'; diff --git a/api_docs/kbn_config_mocks.mdx b/api_docs/kbn_config_mocks.mdx index fc10a39e88a72..db1862fb476ca 100644 --- a/api_docs/kbn_config_mocks.mdx +++ b/api_docs/kbn_config_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-config-mocks title: "@kbn/config-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/config-mocks plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/config-mocks'] --- import kbnConfigMocksObj from './kbn_config_mocks.devdocs.json'; diff --git a/api_docs/kbn_config_schema.mdx b/api_docs/kbn_config_schema.mdx index 4ea0e5fa24208..368c8004fd589 100644 --- a/api_docs/kbn_config_schema.mdx +++ b/api_docs/kbn_config_schema.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-config-schema title: "@kbn/config-schema" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/config-schema plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/config-schema'] --- import kbnConfigSchemaObj from './kbn_config_schema.devdocs.json'; diff --git a/api_docs/kbn_content_management_content_editor.mdx b/api_docs/kbn_content_management_content_editor.mdx index cb2804de5b5dd..b804a89417590 100644 --- a/api_docs/kbn_content_management_content_editor.mdx +++ b/api_docs/kbn_content_management_content_editor.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-content-management-content-editor title: "@kbn/content-management-content-editor" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/content-management-content-editor plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/content-management-content-editor'] --- import kbnContentManagementContentEditorObj from './kbn_content_management_content_editor.devdocs.json'; diff --git a/api_docs/kbn_content_management_tabbed_table_list_view.mdx b/api_docs/kbn_content_management_tabbed_table_list_view.mdx index 02e2aa418dc40..fa569c5f502f8 100644 --- a/api_docs/kbn_content_management_tabbed_table_list_view.mdx +++ b/api_docs/kbn_content_management_tabbed_table_list_view.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-content-management-tabbed-table-list-view title: "@kbn/content-management-tabbed-table-list-view" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/content-management-tabbed-table-list-view plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/content-management-tabbed-table-list-view'] --- import kbnContentManagementTabbedTableListViewObj from './kbn_content_management_tabbed_table_list_view.devdocs.json'; diff --git a/api_docs/kbn_content_management_table_list_view.mdx b/api_docs/kbn_content_management_table_list_view.mdx index 20b31037b991e..d9bf8d6ba5a70 100644 --- a/api_docs/kbn_content_management_table_list_view.mdx +++ b/api_docs/kbn_content_management_table_list_view.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-content-management-table-list-view title: "@kbn/content-management-table-list-view" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/content-management-table-list-view plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/content-management-table-list-view'] --- import kbnContentManagementTableListViewObj from './kbn_content_management_table_list_view.devdocs.json'; diff --git a/api_docs/kbn_content_management_table_list_view_common.mdx b/api_docs/kbn_content_management_table_list_view_common.mdx index aa76abab44ef6..c7a7370f1aca7 100644 --- a/api_docs/kbn_content_management_table_list_view_common.mdx +++ b/api_docs/kbn_content_management_table_list_view_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-content-management-table-list-view-common title: "@kbn/content-management-table-list-view-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/content-management-table-list-view-common plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/content-management-table-list-view-common'] --- import kbnContentManagementTableListViewCommonObj from './kbn_content_management_table_list_view_common.devdocs.json'; diff --git a/api_docs/kbn_content_management_table_list_view_table.mdx b/api_docs/kbn_content_management_table_list_view_table.mdx index 709d00348be2f..28dbd054c5e61 100644 --- a/api_docs/kbn_content_management_table_list_view_table.mdx +++ b/api_docs/kbn_content_management_table_list_view_table.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-content-management-table-list-view-table title: "@kbn/content-management-table-list-view-table" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/content-management-table-list-view-table plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/content-management-table-list-view-table'] --- import kbnContentManagementTableListViewTableObj from './kbn_content_management_table_list_view_table.devdocs.json'; diff --git a/api_docs/kbn_content_management_utils.mdx b/api_docs/kbn_content_management_utils.mdx index 03f882e74e38f..8d14ba030b8f8 100644 --- a/api_docs/kbn_content_management_utils.mdx +++ b/api_docs/kbn_content_management_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-content-management-utils title: "@kbn/content-management-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/content-management-utils plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/content-management-utils'] --- import kbnContentManagementUtilsObj from './kbn_content_management_utils.devdocs.json'; diff --git a/api_docs/kbn_core_analytics_browser.mdx b/api_docs/kbn_core_analytics_browser.mdx index b0d0ecdb4a5a7..aff6901b30bdb 100644 --- a/api_docs/kbn_core_analytics_browser.mdx +++ b/api_docs/kbn_core_analytics_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-analytics-browser title: "@kbn/core-analytics-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-analytics-browser plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-analytics-browser'] --- import kbnCoreAnalyticsBrowserObj from './kbn_core_analytics_browser.devdocs.json'; diff --git a/api_docs/kbn_core_analytics_browser_internal.mdx b/api_docs/kbn_core_analytics_browser_internal.mdx index 97b981ee76ed9..12c50552c753c 100644 --- a/api_docs/kbn_core_analytics_browser_internal.mdx +++ b/api_docs/kbn_core_analytics_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-analytics-browser-internal title: "@kbn/core-analytics-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-analytics-browser-internal plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-analytics-browser-internal'] --- import kbnCoreAnalyticsBrowserInternalObj from './kbn_core_analytics_browser_internal.devdocs.json'; diff --git a/api_docs/kbn_core_analytics_browser_mocks.mdx b/api_docs/kbn_core_analytics_browser_mocks.mdx index d047af528a242..03a329539fd00 100644 --- a/api_docs/kbn_core_analytics_browser_mocks.mdx +++ b/api_docs/kbn_core_analytics_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-analytics-browser-mocks title: "@kbn/core-analytics-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-analytics-browser-mocks plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-analytics-browser-mocks'] --- import kbnCoreAnalyticsBrowserMocksObj from './kbn_core_analytics_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_analytics_server.mdx b/api_docs/kbn_core_analytics_server.mdx index 7f31439174caf..9250d3e328071 100644 --- a/api_docs/kbn_core_analytics_server.mdx +++ b/api_docs/kbn_core_analytics_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-analytics-server title: "@kbn/core-analytics-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-analytics-server plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-analytics-server'] --- import kbnCoreAnalyticsServerObj from './kbn_core_analytics_server.devdocs.json'; diff --git a/api_docs/kbn_core_analytics_server_internal.mdx b/api_docs/kbn_core_analytics_server_internal.mdx index b5ceaa0b92c18..16a3d3b333699 100644 --- a/api_docs/kbn_core_analytics_server_internal.mdx +++ b/api_docs/kbn_core_analytics_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-analytics-server-internal title: "@kbn/core-analytics-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-analytics-server-internal plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-analytics-server-internal'] --- import kbnCoreAnalyticsServerInternalObj from './kbn_core_analytics_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_analytics_server_mocks.mdx b/api_docs/kbn_core_analytics_server_mocks.mdx index 63493d178ee24..e534d463bb3e0 100644 --- a/api_docs/kbn_core_analytics_server_mocks.mdx +++ b/api_docs/kbn_core_analytics_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-analytics-server-mocks title: "@kbn/core-analytics-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-analytics-server-mocks plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-analytics-server-mocks'] --- import kbnCoreAnalyticsServerMocksObj from './kbn_core_analytics_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_application_browser.mdx b/api_docs/kbn_core_application_browser.mdx index 82bdf467d5c11..bdc90c7d93448 100644 --- a/api_docs/kbn_core_application_browser.mdx +++ b/api_docs/kbn_core_application_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-application-browser title: "@kbn/core-application-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-application-browser plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-application-browser'] --- import kbnCoreApplicationBrowserObj from './kbn_core_application_browser.devdocs.json'; diff --git a/api_docs/kbn_core_application_browser_internal.mdx b/api_docs/kbn_core_application_browser_internal.mdx index e1802c419e807..e5df09088ed11 100644 --- a/api_docs/kbn_core_application_browser_internal.mdx +++ b/api_docs/kbn_core_application_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-application-browser-internal title: "@kbn/core-application-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-application-browser-internal plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-application-browser-internal'] --- import kbnCoreApplicationBrowserInternalObj from './kbn_core_application_browser_internal.devdocs.json'; diff --git a/api_docs/kbn_core_application_browser_mocks.mdx b/api_docs/kbn_core_application_browser_mocks.mdx index 43a5e97abe893..ab50d29aa1c17 100644 --- a/api_docs/kbn_core_application_browser_mocks.mdx +++ b/api_docs/kbn_core_application_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-application-browser-mocks title: "@kbn/core-application-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-application-browser-mocks plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-application-browser-mocks'] --- import kbnCoreApplicationBrowserMocksObj from './kbn_core_application_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_application_common.mdx b/api_docs/kbn_core_application_common.mdx index a779832686eca..e633abbdb8b50 100644 --- a/api_docs/kbn_core_application_common.mdx +++ b/api_docs/kbn_core_application_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-application-common title: "@kbn/core-application-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-application-common plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-application-common'] --- import kbnCoreApplicationCommonObj from './kbn_core_application_common.devdocs.json'; diff --git a/api_docs/kbn_core_apps_browser_internal.mdx b/api_docs/kbn_core_apps_browser_internal.mdx index c654627e6b124..be00de56a9cc1 100644 --- a/api_docs/kbn_core_apps_browser_internal.mdx +++ b/api_docs/kbn_core_apps_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-apps-browser-internal title: "@kbn/core-apps-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-apps-browser-internal plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-apps-browser-internal'] --- import kbnCoreAppsBrowserInternalObj from './kbn_core_apps_browser_internal.devdocs.json'; diff --git a/api_docs/kbn_core_apps_browser_mocks.mdx b/api_docs/kbn_core_apps_browser_mocks.mdx index 14b5430f54cc3..eb7214a62a13a 100644 --- a/api_docs/kbn_core_apps_browser_mocks.mdx +++ b/api_docs/kbn_core_apps_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-apps-browser-mocks title: "@kbn/core-apps-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-apps-browser-mocks plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-apps-browser-mocks'] --- import kbnCoreAppsBrowserMocksObj from './kbn_core_apps_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_apps_server_internal.mdx b/api_docs/kbn_core_apps_server_internal.mdx index 35549e84c8915..91c39f1909e04 100644 --- a/api_docs/kbn_core_apps_server_internal.mdx +++ b/api_docs/kbn_core_apps_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-apps-server-internal title: "@kbn/core-apps-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-apps-server-internal plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-apps-server-internal'] --- import kbnCoreAppsServerInternalObj from './kbn_core_apps_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_base_browser_mocks.mdx b/api_docs/kbn_core_base_browser_mocks.mdx index d9683f27e4d95..a3d71774075ed 100644 --- a/api_docs/kbn_core_base_browser_mocks.mdx +++ b/api_docs/kbn_core_base_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-base-browser-mocks title: "@kbn/core-base-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-base-browser-mocks plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-base-browser-mocks'] --- import kbnCoreBaseBrowserMocksObj from './kbn_core_base_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_base_common.mdx b/api_docs/kbn_core_base_common.mdx index 9213a09b30e66..8b65fbb3082bb 100644 --- a/api_docs/kbn_core_base_common.mdx +++ b/api_docs/kbn_core_base_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-base-common title: "@kbn/core-base-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-base-common plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-base-common'] --- import kbnCoreBaseCommonObj from './kbn_core_base_common.devdocs.json'; diff --git a/api_docs/kbn_core_base_server_internal.mdx b/api_docs/kbn_core_base_server_internal.mdx index 05c31db8d4e52..b89791b6e5d56 100644 --- a/api_docs/kbn_core_base_server_internal.mdx +++ b/api_docs/kbn_core_base_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-base-server-internal title: "@kbn/core-base-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-base-server-internal plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-base-server-internal'] --- import kbnCoreBaseServerInternalObj from './kbn_core_base_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_base_server_mocks.mdx b/api_docs/kbn_core_base_server_mocks.mdx index 2e706f82c9776..4d73217615d6e 100644 --- a/api_docs/kbn_core_base_server_mocks.mdx +++ b/api_docs/kbn_core_base_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-base-server-mocks title: "@kbn/core-base-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-base-server-mocks plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-base-server-mocks'] --- import kbnCoreBaseServerMocksObj from './kbn_core_base_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_capabilities_browser_mocks.mdx b/api_docs/kbn_core_capabilities_browser_mocks.mdx index fd0e82681a2d7..27610a1eefdb3 100644 --- a/api_docs/kbn_core_capabilities_browser_mocks.mdx +++ b/api_docs/kbn_core_capabilities_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-capabilities-browser-mocks title: "@kbn/core-capabilities-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-capabilities-browser-mocks plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-capabilities-browser-mocks'] --- import kbnCoreCapabilitiesBrowserMocksObj from './kbn_core_capabilities_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_capabilities_common.mdx b/api_docs/kbn_core_capabilities_common.mdx index 2437a682dcb93..e177cdc737a30 100644 --- a/api_docs/kbn_core_capabilities_common.mdx +++ b/api_docs/kbn_core_capabilities_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-capabilities-common title: "@kbn/core-capabilities-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-capabilities-common plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-capabilities-common'] --- import kbnCoreCapabilitiesCommonObj from './kbn_core_capabilities_common.devdocs.json'; diff --git a/api_docs/kbn_core_capabilities_server.mdx b/api_docs/kbn_core_capabilities_server.mdx index 3d631ee4ab5cd..b37ec316c7264 100644 --- a/api_docs/kbn_core_capabilities_server.mdx +++ b/api_docs/kbn_core_capabilities_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-capabilities-server title: "@kbn/core-capabilities-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-capabilities-server plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-capabilities-server'] --- import kbnCoreCapabilitiesServerObj from './kbn_core_capabilities_server.devdocs.json'; diff --git a/api_docs/kbn_core_capabilities_server_mocks.mdx b/api_docs/kbn_core_capabilities_server_mocks.mdx index 04e8abf69683e..996036ef1b0a7 100644 --- a/api_docs/kbn_core_capabilities_server_mocks.mdx +++ b/api_docs/kbn_core_capabilities_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-capabilities-server-mocks title: "@kbn/core-capabilities-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-capabilities-server-mocks plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-capabilities-server-mocks'] --- import kbnCoreCapabilitiesServerMocksObj from './kbn_core_capabilities_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_chrome_browser.mdx b/api_docs/kbn_core_chrome_browser.mdx index 6cf991de6d5c9..5538aa2ca2c48 100644 --- a/api_docs/kbn_core_chrome_browser.mdx +++ b/api_docs/kbn_core_chrome_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-chrome-browser title: "@kbn/core-chrome-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-chrome-browser plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-chrome-browser'] --- import kbnCoreChromeBrowserObj from './kbn_core_chrome_browser.devdocs.json'; diff --git a/api_docs/kbn_core_chrome_browser_mocks.mdx b/api_docs/kbn_core_chrome_browser_mocks.mdx index 20afa1c49c27e..32f4cacf486da 100644 --- a/api_docs/kbn_core_chrome_browser_mocks.mdx +++ b/api_docs/kbn_core_chrome_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-chrome-browser-mocks title: "@kbn/core-chrome-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-chrome-browser-mocks plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-chrome-browser-mocks'] --- import kbnCoreChromeBrowserMocksObj from './kbn_core_chrome_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_config_server_internal.mdx b/api_docs/kbn_core_config_server_internal.mdx index 384307af8492d..37290db795e51 100644 --- a/api_docs/kbn_core_config_server_internal.mdx +++ b/api_docs/kbn_core_config_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-config-server-internal title: "@kbn/core-config-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-config-server-internal plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-config-server-internal'] --- import kbnCoreConfigServerInternalObj from './kbn_core_config_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_custom_branding_browser.mdx b/api_docs/kbn_core_custom_branding_browser.mdx index cd6d387f6f3ee..e3e4818d2fc6d 100644 --- a/api_docs/kbn_core_custom_branding_browser.mdx +++ b/api_docs/kbn_core_custom_branding_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-custom-branding-browser title: "@kbn/core-custom-branding-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-custom-branding-browser plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-custom-branding-browser'] --- import kbnCoreCustomBrandingBrowserObj from './kbn_core_custom_branding_browser.devdocs.json'; diff --git a/api_docs/kbn_core_custom_branding_browser_internal.mdx b/api_docs/kbn_core_custom_branding_browser_internal.mdx index 9dd8d3c0dc607..6aadb5232bdcc 100644 --- a/api_docs/kbn_core_custom_branding_browser_internal.mdx +++ b/api_docs/kbn_core_custom_branding_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-custom-branding-browser-internal title: "@kbn/core-custom-branding-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-custom-branding-browser-internal plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-custom-branding-browser-internal'] --- import kbnCoreCustomBrandingBrowserInternalObj from './kbn_core_custom_branding_browser_internal.devdocs.json'; diff --git a/api_docs/kbn_core_custom_branding_browser_mocks.mdx b/api_docs/kbn_core_custom_branding_browser_mocks.mdx index 9bc025bc1bdd8..85ea226a61443 100644 --- a/api_docs/kbn_core_custom_branding_browser_mocks.mdx +++ b/api_docs/kbn_core_custom_branding_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-custom-branding-browser-mocks title: "@kbn/core-custom-branding-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-custom-branding-browser-mocks plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-custom-branding-browser-mocks'] --- import kbnCoreCustomBrandingBrowserMocksObj from './kbn_core_custom_branding_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_custom_branding_common.mdx b/api_docs/kbn_core_custom_branding_common.mdx index eb962c2fd7d6c..ca26e32450eec 100644 --- a/api_docs/kbn_core_custom_branding_common.mdx +++ b/api_docs/kbn_core_custom_branding_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-custom-branding-common title: "@kbn/core-custom-branding-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-custom-branding-common plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-custom-branding-common'] --- import kbnCoreCustomBrandingCommonObj from './kbn_core_custom_branding_common.devdocs.json'; diff --git a/api_docs/kbn_core_custom_branding_server.mdx b/api_docs/kbn_core_custom_branding_server.mdx index 7478bdfea5b7c..357b89d65c144 100644 --- a/api_docs/kbn_core_custom_branding_server.mdx +++ b/api_docs/kbn_core_custom_branding_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-custom-branding-server title: "@kbn/core-custom-branding-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-custom-branding-server plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-custom-branding-server'] --- import kbnCoreCustomBrandingServerObj from './kbn_core_custom_branding_server.devdocs.json'; diff --git a/api_docs/kbn_core_custom_branding_server_internal.mdx b/api_docs/kbn_core_custom_branding_server_internal.mdx index 91b02bfd616a4..7175d7b77c951 100644 --- a/api_docs/kbn_core_custom_branding_server_internal.mdx +++ b/api_docs/kbn_core_custom_branding_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-custom-branding-server-internal title: "@kbn/core-custom-branding-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-custom-branding-server-internal plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-custom-branding-server-internal'] --- import kbnCoreCustomBrandingServerInternalObj from './kbn_core_custom_branding_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_custom_branding_server_mocks.mdx b/api_docs/kbn_core_custom_branding_server_mocks.mdx index de6c22036ada2..1e82a2f0e1972 100644 --- a/api_docs/kbn_core_custom_branding_server_mocks.mdx +++ b/api_docs/kbn_core_custom_branding_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-custom-branding-server-mocks title: "@kbn/core-custom-branding-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-custom-branding-server-mocks plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-custom-branding-server-mocks'] --- import kbnCoreCustomBrandingServerMocksObj from './kbn_core_custom_branding_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_deprecations_browser.mdx b/api_docs/kbn_core_deprecations_browser.mdx index b5d55eb6e4053..4171a1ac12755 100644 --- a/api_docs/kbn_core_deprecations_browser.mdx +++ b/api_docs/kbn_core_deprecations_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-deprecations-browser title: "@kbn/core-deprecations-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-deprecations-browser plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-deprecations-browser'] --- import kbnCoreDeprecationsBrowserObj from './kbn_core_deprecations_browser.devdocs.json'; diff --git a/api_docs/kbn_core_deprecations_browser_internal.mdx b/api_docs/kbn_core_deprecations_browser_internal.mdx index 9bc32e87cecb8..6ede7a92d0d03 100644 --- a/api_docs/kbn_core_deprecations_browser_internal.mdx +++ b/api_docs/kbn_core_deprecations_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-deprecations-browser-internal title: "@kbn/core-deprecations-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-deprecations-browser-internal plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-deprecations-browser-internal'] --- import kbnCoreDeprecationsBrowserInternalObj from './kbn_core_deprecations_browser_internal.devdocs.json'; diff --git a/api_docs/kbn_core_deprecations_browser_mocks.mdx b/api_docs/kbn_core_deprecations_browser_mocks.mdx index 8df4b2d7fa43d..ba5be1aabbbed 100644 --- a/api_docs/kbn_core_deprecations_browser_mocks.mdx +++ b/api_docs/kbn_core_deprecations_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-deprecations-browser-mocks title: "@kbn/core-deprecations-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-deprecations-browser-mocks plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-deprecations-browser-mocks'] --- import kbnCoreDeprecationsBrowserMocksObj from './kbn_core_deprecations_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_deprecations_common.mdx b/api_docs/kbn_core_deprecations_common.mdx index e946412476644..e55979dbc67a2 100644 --- a/api_docs/kbn_core_deprecations_common.mdx +++ b/api_docs/kbn_core_deprecations_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-deprecations-common title: "@kbn/core-deprecations-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-deprecations-common plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-deprecations-common'] --- import kbnCoreDeprecationsCommonObj from './kbn_core_deprecations_common.devdocs.json'; diff --git a/api_docs/kbn_core_deprecations_server.mdx b/api_docs/kbn_core_deprecations_server.mdx index eff0e87b160a5..3613bcf25608e 100644 --- a/api_docs/kbn_core_deprecations_server.mdx +++ b/api_docs/kbn_core_deprecations_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-deprecations-server title: "@kbn/core-deprecations-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-deprecations-server plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-deprecations-server'] --- import kbnCoreDeprecationsServerObj from './kbn_core_deprecations_server.devdocs.json'; diff --git a/api_docs/kbn_core_deprecations_server_internal.mdx b/api_docs/kbn_core_deprecations_server_internal.mdx index 48fdbed74ebd7..534e73cec7473 100644 --- a/api_docs/kbn_core_deprecations_server_internal.mdx +++ b/api_docs/kbn_core_deprecations_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-deprecations-server-internal title: "@kbn/core-deprecations-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-deprecations-server-internal plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-deprecations-server-internal'] --- import kbnCoreDeprecationsServerInternalObj from './kbn_core_deprecations_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_deprecations_server_mocks.mdx b/api_docs/kbn_core_deprecations_server_mocks.mdx index 5dd951e0e9ec7..ea50e7353a8bb 100644 --- a/api_docs/kbn_core_deprecations_server_mocks.mdx +++ b/api_docs/kbn_core_deprecations_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-deprecations-server-mocks title: "@kbn/core-deprecations-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-deprecations-server-mocks plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-deprecations-server-mocks'] --- import kbnCoreDeprecationsServerMocksObj from './kbn_core_deprecations_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_doc_links_browser.mdx b/api_docs/kbn_core_doc_links_browser.mdx index beacaf37361ef..fa20f2e35c9ae 100644 --- a/api_docs/kbn_core_doc_links_browser.mdx +++ b/api_docs/kbn_core_doc_links_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-doc-links-browser title: "@kbn/core-doc-links-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-doc-links-browser plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-doc-links-browser'] --- import kbnCoreDocLinksBrowserObj from './kbn_core_doc_links_browser.devdocs.json'; diff --git a/api_docs/kbn_core_doc_links_browser_mocks.mdx b/api_docs/kbn_core_doc_links_browser_mocks.mdx index 914862dca748a..8489514cbdd87 100644 --- a/api_docs/kbn_core_doc_links_browser_mocks.mdx +++ b/api_docs/kbn_core_doc_links_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-doc-links-browser-mocks title: "@kbn/core-doc-links-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-doc-links-browser-mocks plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-doc-links-browser-mocks'] --- import kbnCoreDocLinksBrowserMocksObj from './kbn_core_doc_links_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_doc_links_server.mdx b/api_docs/kbn_core_doc_links_server.mdx index b7f45f8e808f9..eca1b9bc2c041 100644 --- a/api_docs/kbn_core_doc_links_server.mdx +++ b/api_docs/kbn_core_doc_links_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-doc-links-server title: "@kbn/core-doc-links-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-doc-links-server plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-doc-links-server'] --- import kbnCoreDocLinksServerObj from './kbn_core_doc_links_server.devdocs.json'; diff --git a/api_docs/kbn_core_doc_links_server_mocks.mdx b/api_docs/kbn_core_doc_links_server_mocks.mdx index f5fbf4a53911e..7a44c9bb881c8 100644 --- a/api_docs/kbn_core_doc_links_server_mocks.mdx +++ b/api_docs/kbn_core_doc_links_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-doc-links-server-mocks title: "@kbn/core-doc-links-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-doc-links-server-mocks plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-doc-links-server-mocks'] --- import kbnCoreDocLinksServerMocksObj from './kbn_core_doc_links_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_elasticsearch_client_server_internal.mdx b/api_docs/kbn_core_elasticsearch_client_server_internal.mdx index e1f3cbc34858c..8042909528681 100644 --- a/api_docs/kbn_core_elasticsearch_client_server_internal.mdx +++ b/api_docs/kbn_core_elasticsearch_client_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-elasticsearch-client-server-internal title: "@kbn/core-elasticsearch-client-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-elasticsearch-client-server-internal plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-elasticsearch-client-server-internal'] --- import kbnCoreElasticsearchClientServerInternalObj from './kbn_core_elasticsearch_client_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_elasticsearch_client_server_mocks.mdx b/api_docs/kbn_core_elasticsearch_client_server_mocks.mdx index bfdc7edd8b9ad..9a76cb0228506 100644 --- a/api_docs/kbn_core_elasticsearch_client_server_mocks.mdx +++ b/api_docs/kbn_core_elasticsearch_client_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-elasticsearch-client-server-mocks title: "@kbn/core-elasticsearch-client-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-elasticsearch-client-server-mocks plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-elasticsearch-client-server-mocks'] --- import kbnCoreElasticsearchClientServerMocksObj from './kbn_core_elasticsearch_client_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_elasticsearch_server.mdx b/api_docs/kbn_core_elasticsearch_server.mdx index ab965ec2bbec5..6245f231aa861 100644 --- a/api_docs/kbn_core_elasticsearch_server.mdx +++ b/api_docs/kbn_core_elasticsearch_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-elasticsearch-server title: "@kbn/core-elasticsearch-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-elasticsearch-server plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-elasticsearch-server'] --- import kbnCoreElasticsearchServerObj from './kbn_core_elasticsearch_server.devdocs.json'; diff --git a/api_docs/kbn_core_elasticsearch_server_internal.mdx b/api_docs/kbn_core_elasticsearch_server_internal.mdx index 5f6eb1e06d6e0..349d3663211ed 100644 --- a/api_docs/kbn_core_elasticsearch_server_internal.mdx +++ b/api_docs/kbn_core_elasticsearch_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-elasticsearch-server-internal title: "@kbn/core-elasticsearch-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-elasticsearch-server-internal plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-elasticsearch-server-internal'] --- import kbnCoreElasticsearchServerInternalObj from './kbn_core_elasticsearch_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_elasticsearch_server_mocks.mdx b/api_docs/kbn_core_elasticsearch_server_mocks.mdx index 8bad91c27066a..bb69857412fe1 100644 --- a/api_docs/kbn_core_elasticsearch_server_mocks.mdx +++ b/api_docs/kbn_core_elasticsearch_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-elasticsearch-server-mocks title: "@kbn/core-elasticsearch-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-elasticsearch-server-mocks plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-elasticsearch-server-mocks'] --- import kbnCoreElasticsearchServerMocksObj from './kbn_core_elasticsearch_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_environment_server_internal.mdx b/api_docs/kbn_core_environment_server_internal.mdx index 0cc1d3c0705da..fa4955c97c373 100644 --- a/api_docs/kbn_core_environment_server_internal.mdx +++ b/api_docs/kbn_core_environment_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-environment-server-internal title: "@kbn/core-environment-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-environment-server-internal plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-environment-server-internal'] --- import kbnCoreEnvironmentServerInternalObj from './kbn_core_environment_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_environment_server_mocks.mdx b/api_docs/kbn_core_environment_server_mocks.mdx index 227250e0565e2..1330aa3c180a5 100644 --- a/api_docs/kbn_core_environment_server_mocks.mdx +++ b/api_docs/kbn_core_environment_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-environment-server-mocks title: "@kbn/core-environment-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-environment-server-mocks plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-environment-server-mocks'] --- import kbnCoreEnvironmentServerMocksObj from './kbn_core_environment_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_execution_context_browser.mdx b/api_docs/kbn_core_execution_context_browser.mdx index e9c32b7fab8bd..9a8005ff32b98 100644 --- a/api_docs/kbn_core_execution_context_browser.mdx +++ b/api_docs/kbn_core_execution_context_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-execution-context-browser title: "@kbn/core-execution-context-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-execution-context-browser plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-execution-context-browser'] --- import kbnCoreExecutionContextBrowserObj from './kbn_core_execution_context_browser.devdocs.json'; diff --git a/api_docs/kbn_core_execution_context_browser_internal.mdx b/api_docs/kbn_core_execution_context_browser_internal.mdx index c5a2c0f9c5705..f7f8598487985 100644 --- a/api_docs/kbn_core_execution_context_browser_internal.mdx +++ b/api_docs/kbn_core_execution_context_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-execution-context-browser-internal title: "@kbn/core-execution-context-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-execution-context-browser-internal plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-execution-context-browser-internal'] --- import kbnCoreExecutionContextBrowserInternalObj from './kbn_core_execution_context_browser_internal.devdocs.json'; diff --git a/api_docs/kbn_core_execution_context_browser_mocks.mdx b/api_docs/kbn_core_execution_context_browser_mocks.mdx index b3f9ad8b42812..b89dbb62b0c82 100644 --- a/api_docs/kbn_core_execution_context_browser_mocks.mdx +++ b/api_docs/kbn_core_execution_context_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-execution-context-browser-mocks title: "@kbn/core-execution-context-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-execution-context-browser-mocks plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-execution-context-browser-mocks'] --- import kbnCoreExecutionContextBrowserMocksObj from './kbn_core_execution_context_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_execution_context_common.mdx b/api_docs/kbn_core_execution_context_common.mdx index 5d304b8c13cae..3043f788a23c8 100644 --- a/api_docs/kbn_core_execution_context_common.mdx +++ b/api_docs/kbn_core_execution_context_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-execution-context-common title: "@kbn/core-execution-context-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-execution-context-common plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-execution-context-common'] --- import kbnCoreExecutionContextCommonObj from './kbn_core_execution_context_common.devdocs.json'; diff --git a/api_docs/kbn_core_execution_context_server.mdx b/api_docs/kbn_core_execution_context_server.mdx index 4f2ec7dfcc0f7..015669b6d0abd 100644 --- a/api_docs/kbn_core_execution_context_server.mdx +++ b/api_docs/kbn_core_execution_context_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-execution-context-server title: "@kbn/core-execution-context-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-execution-context-server plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-execution-context-server'] --- import kbnCoreExecutionContextServerObj from './kbn_core_execution_context_server.devdocs.json'; diff --git a/api_docs/kbn_core_execution_context_server_internal.mdx b/api_docs/kbn_core_execution_context_server_internal.mdx index bafad3db4a70f..0a6dd99fa89c3 100644 --- a/api_docs/kbn_core_execution_context_server_internal.mdx +++ b/api_docs/kbn_core_execution_context_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-execution-context-server-internal title: "@kbn/core-execution-context-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-execution-context-server-internal plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-execution-context-server-internal'] --- import kbnCoreExecutionContextServerInternalObj from './kbn_core_execution_context_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_execution_context_server_mocks.mdx b/api_docs/kbn_core_execution_context_server_mocks.mdx index 50323aa84e1cf..a9b5cbef415f2 100644 --- a/api_docs/kbn_core_execution_context_server_mocks.mdx +++ b/api_docs/kbn_core_execution_context_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-execution-context-server-mocks title: "@kbn/core-execution-context-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-execution-context-server-mocks plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-execution-context-server-mocks'] --- import kbnCoreExecutionContextServerMocksObj from './kbn_core_execution_context_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_fatal_errors_browser.mdx b/api_docs/kbn_core_fatal_errors_browser.mdx index 1c621fabeee51..5238c11fe04e6 100644 --- a/api_docs/kbn_core_fatal_errors_browser.mdx +++ b/api_docs/kbn_core_fatal_errors_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-fatal-errors-browser title: "@kbn/core-fatal-errors-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-fatal-errors-browser plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-fatal-errors-browser'] --- import kbnCoreFatalErrorsBrowserObj from './kbn_core_fatal_errors_browser.devdocs.json'; diff --git a/api_docs/kbn_core_fatal_errors_browser_mocks.mdx b/api_docs/kbn_core_fatal_errors_browser_mocks.mdx index b516c05621890..e1e46147d3461 100644 --- a/api_docs/kbn_core_fatal_errors_browser_mocks.mdx +++ b/api_docs/kbn_core_fatal_errors_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-fatal-errors-browser-mocks title: "@kbn/core-fatal-errors-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-fatal-errors-browser-mocks plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-fatal-errors-browser-mocks'] --- import kbnCoreFatalErrorsBrowserMocksObj from './kbn_core_fatal_errors_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_http_browser.mdx b/api_docs/kbn_core_http_browser.mdx index 8652d0187d41d..fe60fa257feb7 100644 --- a/api_docs/kbn_core_http_browser.mdx +++ b/api_docs/kbn_core_http_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-browser title: "@kbn/core-http-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-browser plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-http-browser'] --- import kbnCoreHttpBrowserObj from './kbn_core_http_browser.devdocs.json'; diff --git a/api_docs/kbn_core_http_browser_internal.mdx b/api_docs/kbn_core_http_browser_internal.mdx index e712580aba22e..9f7e3ff62f456 100644 --- a/api_docs/kbn_core_http_browser_internal.mdx +++ b/api_docs/kbn_core_http_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-browser-internal title: "@kbn/core-http-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-browser-internal plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-http-browser-internal'] --- import kbnCoreHttpBrowserInternalObj from './kbn_core_http_browser_internal.devdocs.json'; diff --git a/api_docs/kbn_core_http_browser_mocks.mdx b/api_docs/kbn_core_http_browser_mocks.mdx index 21d82ab1d24ba..fdb7e8e3f69e4 100644 --- a/api_docs/kbn_core_http_browser_mocks.mdx +++ b/api_docs/kbn_core_http_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-browser-mocks title: "@kbn/core-http-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-browser-mocks plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-http-browser-mocks'] --- import kbnCoreHttpBrowserMocksObj from './kbn_core_http_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_http_common.mdx b/api_docs/kbn_core_http_common.mdx index e8fd51f51f8bd..ceffe8106d733 100644 --- a/api_docs/kbn_core_http_common.mdx +++ b/api_docs/kbn_core_http_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-common title: "@kbn/core-http-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-common plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-http-common'] --- import kbnCoreHttpCommonObj from './kbn_core_http_common.devdocs.json'; diff --git a/api_docs/kbn_core_http_context_server_mocks.mdx b/api_docs/kbn_core_http_context_server_mocks.mdx index fe2fb0ab276c7..475b10aa64eeb 100644 --- a/api_docs/kbn_core_http_context_server_mocks.mdx +++ b/api_docs/kbn_core_http_context_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-context-server-mocks title: "@kbn/core-http-context-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-context-server-mocks plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-http-context-server-mocks'] --- import kbnCoreHttpContextServerMocksObj from './kbn_core_http_context_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_http_request_handler_context_server.mdx b/api_docs/kbn_core_http_request_handler_context_server.mdx index a0b9981d8a88d..8255d8f4ad47e 100644 --- a/api_docs/kbn_core_http_request_handler_context_server.mdx +++ b/api_docs/kbn_core_http_request_handler_context_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-request-handler-context-server title: "@kbn/core-http-request-handler-context-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-request-handler-context-server plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-http-request-handler-context-server'] --- import kbnCoreHttpRequestHandlerContextServerObj from './kbn_core_http_request_handler_context_server.devdocs.json'; diff --git a/api_docs/kbn_core_http_resources_server.mdx b/api_docs/kbn_core_http_resources_server.mdx index 5091937d35799..728150c5a5df5 100644 --- a/api_docs/kbn_core_http_resources_server.mdx +++ b/api_docs/kbn_core_http_resources_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-resources-server title: "@kbn/core-http-resources-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-resources-server plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-http-resources-server'] --- import kbnCoreHttpResourcesServerObj from './kbn_core_http_resources_server.devdocs.json'; diff --git a/api_docs/kbn_core_http_resources_server_internal.mdx b/api_docs/kbn_core_http_resources_server_internal.mdx index 2079460b43fa8..bc49e6b9dd9e0 100644 --- a/api_docs/kbn_core_http_resources_server_internal.mdx +++ b/api_docs/kbn_core_http_resources_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-resources-server-internal title: "@kbn/core-http-resources-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-resources-server-internal plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-http-resources-server-internal'] --- import kbnCoreHttpResourcesServerInternalObj from './kbn_core_http_resources_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_http_resources_server_mocks.mdx b/api_docs/kbn_core_http_resources_server_mocks.mdx index 8d83d9f85d0cc..598b5833020ef 100644 --- a/api_docs/kbn_core_http_resources_server_mocks.mdx +++ b/api_docs/kbn_core_http_resources_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-resources-server-mocks title: "@kbn/core-http-resources-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-resources-server-mocks plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-http-resources-server-mocks'] --- import kbnCoreHttpResourcesServerMocksObj from './kbn_core_http_resources_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_http_router_server_internal.mdx b/api_docs/kbn_core_http_router_server_internal.mdx index 1385aba188d94..3c0b5f4a2720d 100644 --- a/api_docs/kbn_core_http_router_server_internal.mdx +++ b/api_docs/kbn_core_http_router_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-router-server-internal title: "@kbn/core-http-router-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-router-server-internal plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-http-router-server-internal'] --- import kbnCoreHttpRouterServerInternalObj from './kbn_core_http_router_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_http_router_server_mocks.mdx b/api_docs/kbn_core_http_router_server_mocks.mdx index 9e5a8aab78eec..46fa53ce36d6d 100644 --- a/api_docs/kbn_core_http_router_server_mocks.mdx +++ b/api_docs/kbn_core_http_router_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-router-server-mocks title: "@kbn/core-http-router-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-router-server-mocks plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-http-router-server-mocks'] --- import kbnCoreHttpRouterServerMocksObj from './kbn_core_http_router_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_http_server.devdocs.json b/api_docs/kbn_core_http_server.devdocs.json index a7075e5be6a9f..fbde01a63dc77 100644 --- a/api_docs/kbn_core_http_server.devdocs.json +++ b/api_docs/kbn_core_http_server.devdocs.json @@ -6168,7 +6168,11 @@ }, { "plugin": "alerting", - "path": "x-pack/plugins/alerting/server/routes/rule/apis/bulk_untrack/bulk_untrack_alert_route.ts" + "path": "x-pack/plugins/alerting/server/routes/rule/apis/bulk_untrack/bulk_untrack_alerts_route.ts" + }, + { + "plugin": "alerting", + "path": "x-pack/plugins/alerting/server/routes/rule/apis/bulk_untrack_by_query/bulk_untrack_alerts_by_query_route.ts" }, { "plugin": "alerting", @@ -7618,6 +7622,14 @@ "plugin": "alerting", "path": "x-pack/plugins/alerting/server/routes/rule/apis/bulk_edit/bulk_edit_rules_route.test.ts" }, + { + "plugin": "alerting", + "path": "x-pack/plugins/alerting/server/routes/rule/apis/bulk_untrack/bulk_untrack_alerts_route.test.ts" + }, + { + "plugin": "alerting", + "path": "x-pack/plugins/alerting/server/routes/rule/apis/bulk_untrack_by_query/bulk_untrack_alerts_by_query_route.test.ts" + }, { "plugin": "alerting", "path": "x-pack/plugins/alerting/server/routes/rule/apis/create/create_rule_route.test.ts" @@ -13758,6 +13770,10 @@ "plugin": "securitySolution", "path": "x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/tags/read_tags/route.ts" }, + { + "plugin": "securitySolution", + "path": "x-pack/plugins/security_solution/server/endpoint/routes/agent/agent_status_handler.ts" + }, { "plugin": "securitySolution", "path": "x-pack/plugins/security_solution/server/lib/detection_engine/fleet_integrations/api/get_installed_integrations/route.ts" diff --git a/api_docs/kbn_core_http_server.mdx b/api_docs/kbn_core_http_server.mdx index 987b4cb83bd29..634f20690847b 100644 --- a/api_docs/kbn_core_http_server.mdx +++ b/api_docs/kbn_core_http_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-server title: "@kbn/core-http-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-server plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-http-server'] --- import kbnCoreHttpServerObj from './kbn_core_http_server.devdocs.json'; diff --git a/api_docs/kbn_core_http_server_internal.mdx b/api_docs/kbn_core_http_server_internal.mdx index 88e7c92aa4242..03d42e515e948 100644 --- a/api_docs/kbn_core_http_server_internal.mdx +++ b/api_docs/kbn_core_http_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-server-internal title: "@kbn/core-http-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-server-internal plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-http-server-internal'] --- import kbnCoreHttpServerInternalObj from './kbn_core_http_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_http_server_mocks.mdx b/api_docs/kbn_core_http_server_mocks.mdx index 30d50d94cd272..0e9f22b551d64 100644 --- a/api_docs/kbn_core_http_server_mocks.mdx +++ b/api_docs/kbn_core_http_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-server-mocks title: "@kbn/core-http-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-server-mocks plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-http-server-mocks'] --- import kbnCoreHttpServerMocksObj from './kbn_core_http_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_i18n_browser.mdx b/api_docs/kbn_core_i18n_browser.mdx index 7ec0ed2301a74..ee7885228f5ab 100644 --- a/api_docs/kbn_core_i18n_browser.mdx +++ b/api_docs/kbn_core_i18n_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-i18n-browser title: "@kbn/core-i18n-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-i18n-browser plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-i18n-browser'] --- import kbnCoreI18nBrowserObj from './kbn_core_i18n_browser.devdocs.json'; diff --git a/api_docs/kbn_core_i18n_browser_mocks.mdx b/api_docs/kbn_core_i18n_browser_mocks.mdx index 4153ffc5a2da4..6ea456cb794dd 100644 --- a/api_docs/kbn_core_i18n_browser_mocks.mdx +++ b/api_docs/kbn_core_i18n_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-i18n-browser-mocks title: "@kbn/core-i18n-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-i18n-browser-mocks plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-i18n-browser-mocks'] --- import kbnCoreI18nBrowserMocksObj from './kbn_core_i18n_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_i18n_server.mdx b/api_docs/kbn_core_i18n_server.mdx index 02b7378f0fb9a..7b1599ab7111d 100644 --- a/api_docs/kbn_core_i18n_server.mdx +++ b/api_docs/kbn_core_i18n_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-i18n-server title: "@kbn/core-i18n-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-i18n-server plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-i18n-server'] --- import kbnCoreI18nServerObj from './kbn_core_i18n_server.devdocs.json'; diff --git a/api_docs/kbn_core_i18n_server_internal.mdx b/api_docs/kbn_core_i18n_server_internal.mdx index 2e606b24b246a..a176b62dbc3fd 100644 --- a/api_docs/kbn_core_i18n_server_internal.mdx +++ b/api_docs/kbn_core_i18n_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-i18n-server-internal title: "@kbn/core-i18n-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-i18n-server-internal plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-i18n-server-internal'] --- import kbnCoreI18nServerInternalObj from './kbn_core_i18n_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_i18n_server_mocks.mdx b/api_docs/kbn_core_i18n_server_mocks.mdx index 3525f711a192e..963c4feef32df 100644 --- a/api_docs/kbn_core_i18n_server_mocks.mdx +++ b/api_docs/kbn_core_i18n_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-i18n-server-mocks title: "@kbn/core-i18n-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-i18n-server-mocks plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-i18n-server-mocks'] --- import kbnCoreI18nServerMocksObj from './kbn_core_i18n_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_injected_metadata_browser_mocks.mdx b/api_docs/kbn_core_injected_metadata_browser_mocks.mdx index d9303f529f922..134b2da17b1e7 100644 --- a/api_docs/kbn_core_injected_metadata_browser_mocks.mdx +++ b/api_docs/kbn_core_injected_metadata_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-injected-metadata-browser-mocks title: "@kbn/core-injected-metadata-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-injected-metadata-browser-mocks plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-injected-metadata-browser-mocks'] --- import kbnCoreInjectedMetadataBrowserMocksObj from './kbn_core_injected_metadata_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_integrations_browser_internal.mdx b/api_docs/kbn_core_integrations_browser_internal.mdx index 3c277f3f46c0c..5cdd830a4701d 100644 --- a/api_docs/kbn_core_integrations_browser_internal.mdx +++ b/api_docs/kbn_core_integrations_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-integrations-browser-internal title: "@kbn/core-integrations-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-integrations-browser-internal plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-integrations-browser-internal'] --- import kbnCoreIntegrationsBrowserInternalObj from './kbn_core_integrations_browser_internal.devdocs.json'; diff --git a/api_docs/kbn_core_integrations_browser_mocks.mdx b/api_docs/kbn_core_integrations_browser_mocks.mdx index c70f459edddf7..c00f68eb3a679 100644 --- a/api_docs/kbn_core_integrations_browser_mocks.mdx +++ b/api_docs/kbn_core_integrations_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-integrations-browser-mocks title: "@kbn/core-integrations-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-integrations-browser-mocks plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-integrations-browser-mocks'] --- import kbnCoreIntegrationsBrowserMocksObj from './kbn_core_integrations_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_lifecycle_browser.mdx b/api_docs/kbn_core_lifecycle_browser.mdx index a88a42622ec2a..67f5fe2a851e2 100644 --- a/api_docs/kbn_core_lifecycle_browser.mdx +++ b/api_docs/kbn_core_lifecycle_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-lifecycle-browser title: "@kbn/core-lifecycle-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-lifecycle-browser plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-lifecycle-browser'] --- import kbnCoreLifecycleBrowserObj from './kbn_core_lifecycle_browser.devdocs.json'; diff --git a/api_docs/kbn_core_lifecycle_browser_mocks.mdx b/api_docs/kbn_core_lifecycle_browser_mocks.mdx index 39826d56732cf..a106b3608f269 100644 --- a/api_docs/kbn_core_lifecycle_browser_mocks.mdx +++ b/api_docs/kbn_core_lifecycle_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-lifecycle-browser-mocks title: "@kbn/core-lifecycle-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-lifecycle-browser-mocks plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-lifecycle-browser-mocks'] --- import kbnCoreLifecycleBrowserMocksObj from './kbn_core_lifecycle_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_lifecycle_server.mdx b/api_docs/kbn_core_lifecycle_server.mdx index 4a8661f4a6cbd..c6f830177dc39 100644 --- a/api_docs/kbn_core_lifecycle_server.mdx +++ b/api_docs/kbn_core_lifecycle_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-lifecycle-server title: "@kbn/core-lifecycle-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-lifecycle-server plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-lifecycle-server'] --- import kbnCoreLifecycleServerObj from './kbn_core_lifecycle_server.devdocs.json'; diff --git a/api_docs/kbn_core_lifecycle_server_mocks.mdx b/api_docs/kbn_core_lifecycle_server_mocks.mdx index 427a6a4eb0c3e..c9c1ca66758f3 100644 --- a/api_docs/kbn_core_lifecycle_server_mocks.mdx +++ b/api_docs/kbn_core_lifecycle_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-lifecycle-server-mocks title: "@kbn/core-lifecycle-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-lifecycle-server-mocks plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-lifecycle-server-mocks'] --- import kbnCoreLifecycleServerMocksObj from './kbn_core_lifecycle_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_logging_browser_mocks.mdx b/api_docs/kbn_core_logging_browser_mocks.mdx index bb85246ab1c29..89629be8ad6c0 100644 --- a/api_docs/kbn_core_logging_browser_mocks.mdx +++ b/api_docs/kbn_core_logging_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-logging-browser-mocks title: "@kbn/core-logging-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-logging-browser-mocks plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-logging-browser-mocks'] --- import kbnCoreLoggingBrowserMocksObj from './kbn_core_logging_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_logging_common_internal.mdx b/api_docs/kbn_core_logging_common_internal.mdx index 8e1ff37a0b26b..f3ec279a52a19 100644 --- a/api_docs/kbn_core_logging_common_internal.mdx +++ b/api_docs/kbn_core_logging_common_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-logging-common-internal title: "@kbn/core-logging-common-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-logging-common-internal plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-logging-common-internal'] --- import kbnCoreLoggingCommonInternalObj from './kbn_core_logging_common_internal.devdocs.json'; diff --git a/api_docs/kbn_core_logging_server.mdx b/api_docs/kbn_core_logging_server.mdx index 3ee40f1a46734..3655499d1fe3b 100644 --- a/api_docs/kbn_core_logging_server.mdx +++ b/api_docs/kbn_core_logging_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-logging-server title: "@kbn/core-logging-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-logging-server plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-logging-server'] --- import kbnCoreLoggingServerObj from './kbn_core_logging_server.devdocs.json'; diff --git a/api_docs/kbn_core_logging_server_internal.mdx b/api_docs/kbn_core_logging_server_internal.mdx index 1601a2c1d56f6..8ca453415a4af 100644 --- a/api_docs/kbn_core_logging_server_internal.mdx +++ b/api_docs/kbn_core_logging_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-logging-server-internal title: "@kbn/core-logging-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-logging-server-internal plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-logging-server-internal'] --- import kbnCoreLoggingServerInternalObj from './kbn_core_logging_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_logging_server_mocks.mdx b/api_docs/kbn_core_logging_server_mocks.mdx index 5c1d73aa21411..a8e6dca8fd819 100644 --- a/api_docs/kbn_core_logging_server_mocks.mdx +++ b/api_docs/kbn_core_logging_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-logging-server-mocks title: "@kbn/core-logging-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-logging-server-mocks plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-logging-server-mocks'] --- import kbnCoreLoggingServerMocksObj from './kbn_core_logging_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_metrics_collectors_server_internal.mdx b/api_docs/kbn_core_metrics_collectors_server_internal.mdx index 0ffa7a4c2d941..aa378438052f8 100644 --- a/api_docs/kbn_core_metrics_collectors_server_internal.mdx +++ b/api_docs/kbn_core_metrics_collectors_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-metrics-collectors-server-internal title: "@kbn/core-metrics-collectors-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-metrics-collectors-server-internal plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-metrics-collectors-server-internal'] --- import kbnCoreMetricsCollectorsServerInternalObj from './kbn_core_metrics_collectors_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_metrics_collectors_server_mocks.mdx b/api_docs/kbn_core_metrics_collectors_server_mocks.mdx index d9f4c8e28323b..34c8a4ffcb2ac 100644 --- a/api_docs/kbn_core_metrics_collectors_server_mocks.mdx +++ b/api_docs/kbn_core_metrics_collectors_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-metrics-collectors-server-mocks title: "@kbn/core-metrics-collectors-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-metrics-collectors-server-mocks plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-metrics-collectors-server-mocks'] --- import kbnCoreMetricsCollectorsServerMocksObj from './kbn_core_metrics_collectors_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_metrics_server.mdx b/api_docs/kbn_core_metrics_server.mdx index 765bb64259584..5ed4e70790aa6 100644 --- a/api_docs/kbn_core_metrics_server.mdx +++ b/api_docs/kbn_core_metrics_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-metrics-server title: "@kbn/core-metrics-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-metrics-server plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-metrics-server'] --- import kbnCoreMetricsServerObj from './kbn_core_metrics_server.devdocs.json'; diff --git a/api_docs/kbn_core_metrics_server_internal.mdx b/api_docs/kbn_core_metrics_server_internal.mdx index 778e8139cb1b6..0ba7e8dbe4007 100644 --- a/api_docs/kbn_core_metrics_server_internal.mdx +++ b/api_docs/kbn_core_metrics_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-metrics-server-internal title: "@kbn/core-metrics-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-metrics-server-internal plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-metrics-server-internal'] --- import kbnCoreMetricsServerInternalObj from './kbn_core_metrics_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_metrics_server_mocks.mdx b/api_docs/kbn_core_metrics_server_mocks.mdx index 96ca566da4d46..5f1d86091c369 100644 --- a/api_docs/kbn_core_metrics_server_mocks.mdx +++ b/api_docs/kbn_core_metrics_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-metrics-server-mocks title: "@kbn/core-metrics-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-metrics-server-mocks plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-metrics-server-mocks'] --- import kbnCoreMetricsServerMocksObj from './kbn_core_metrics_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_mount_utils_browser.mdx b/api_docs/kbn_core_mount_utils_browser.mdx index d1ff5ca219fd8..e78faddfa31ad 100644 --- a/api_docs/kbn_core_mount_utils_browser.mdx +++ b/api_docs/kbn_core_mount_utils_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-mount-utils-browser title: "@kbn/core-mount-utils-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-mount-utils-browser plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-mount-utils-browser'] --- import kbnCoreMountUtilsBrowserObj from './kbn_core_mount_utils_browser.devdocs.json'; diff --git a/api_docs/kbn_core_node_server.mdx b/api_docs/kbn_core_node_server.mdx index 95c2b57ecaad1..6b150f8415764 100644 --- a/api_docs/kbn_core_node_server.mdx +++ b/api_docs/kbn_core_node_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-node-server title: "@kbn/core-node-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-node-server plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-node-server'] --- import kbnCoreNodeServerObj from './kbn_core_node_server.devdocs.json'; diff --git a/api_docs/kbn_core_node_server_internal.mdx b/api_docs/kbn_core_node_server_internal.mdx index 927af1248d108..1c27a3cf5d4a6 100644 --- a/api_docs/kbn_core_node_server_internal.mdx +++ b/api_docs/kbn_core_node_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-node-server-internal title: "@kbn/core-node-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-node-server-internal plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-node-server-internal'] --- import kbnCoreNodeServerInternalObj from './kbn_core_node_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_node_server_mocks.mdx b/api_docs/kbn_core_node_server_mocks.mdx index 85c72a7423801..0b0c54bb1fd02 100644 --- a/api_docs/kbn_core_node_server_mocks.mdx +++ b/api_docs/kbn_core_node_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-node-server-mocks title: "@kbn/core-node-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-node-server-mocks plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-node-server-mocks'] --- import kbnCoreNodeServerMocksObj from './kbn_core_node_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_notifications_browser.mdx b/api_docs/kbn_core_notifications_browser.mdx index 2e5e9c60827b1..ebe5a01b0b4f1 100644 --- a/api_docs/kbn_core_notifications_browser.mdx +++ b/api_docs/kbn_core_notifications_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-notifications-browser title: "@kbn/core-notifications-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-notifications-browser plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-notifications-browser'] --- import kbnCoreNotificationsBrowserObj from './kbn_core_notifications_browser.devdocs.json'; diff --git a/api_docs/kbn_core_notifications_browser_internal.mdx b/api_docs/kbn_core_notifications_browser_internal.mdx index 5cb7bd729bb8f..00cd323088757 100644 --- a/api_docs/kbn_core_notifications_browser_internal.mdx +++ b/api_docs/kbn_core_notifications_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-notifications-browser-internal title: "@kbn/core-notifications-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-notifications-browser-internal plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-notifications-browser-internal'] --- import kbnCoreNotificationsBrowserInternalObj from './kbn_core_notifications_browser_internal.devdocs.json'; diff --git a/api_docs/kbn_core_notifications_browser_mocks.mdx b/api_docs/kbn_core_notifications_browser_mocks.mdx index c1258a47410fb..bb4c49e5b848d 100644 --- a/api_docs/kbn_core_notifications_browser_mocks.mdx +++ b/api_docs/kbn_core_notifications_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-notifications-browser-mocks title: "@kbn/core-notifications-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-notifications-browser-mocks plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-notifications-browser-mocks'] --- import kbnCoreNotificationsBrowserMocksObj from './kbn_core_notifications_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_overlays_browser.mdx b/api_docs/kbn_core_overlays_browser.mdx index 0219f74a15a7e..349f4dd9e07dc 100644 --- a/api_docs/kbn_core_overlays_browser.mdx +++ b/api_docs/kbn_core_overlays_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-overlays-browser title: "@kbn/core-overlays-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-overlays-browser plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-overlays-browser'] --- import kbnCoreOverlaysBrowserObj from './kbn_core_overlays_browser.devdocs.json'; diff --git a/api_docs/kbn_core_overlays_browser_internal.mdx b/api_docs/kbn_core_overlays_browser_internal.mdx index 22ec84bb286f4..2efcda026ac9f 100644 --- a/api_docs/kbn_core_overlays_browser_internal.mdx +++ b/api_docs/kbn_core_overlays_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-overlays-browser-internal title: "@kbn/core-overlays-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-overlays-browser-internal plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-overlays-browser-internal'] --- import kbnCoreOverlaysBrowserInternalObj from './kbn_core_overlays_browser_internal.devdocs.json'; diff --git a/api_docs/kbn_core_overlays_browser_mocks.mdx b/api_docs/kbn_core_overlays_browser_mocks.mdx index eea8902ebf934..e3631d12efbf1 100644 --- a/api_docs/kbn_core_overlays_browser_mocks.mdx +++ b/api_docs/kbn_core_overlays_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-overlays-browser-mocks title: "@kbn/core-overlays-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-overlays-browser-mocks plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-overlays-browser-mocks'] --- import kbnCoreOverlaysBrowserMocksObj from './kbn_core_overlays_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_plugins_browser.mdx b/api_docs/kbn_core_plugins_browser.mdx index c31df9a64fe56..a5d9e129f3cba 100644 --- a/api_docs/kbn_core_plugins_browser.mdx +++ b/api_docs/kbn_core_plugins_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-plugins-browser title: "@kbn/core-plugins-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-plugins-browser plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-plugins-browser'] --- import kbnCorePluginsBrowserObj from './kbn_core_plugins_browser.devdocs.json'; diff --git a/api_docs/kbn_core_plugins_browser_mocks.mdx b/api_docs/kbn_core_plugins_browser_mocks.mdx index a7f8f143c7471..1e5490fe1cc16 100644 --- a/api_docs/kbn_core_plugins_browser_mocks.mdx +++ b/api_docs/kbn_core_plugins_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-plugins-browser-mocks title: "@kbn/core-plugins-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-plugins-browser-mocks plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-plugins-browser-mocks'] --- import kbnCorePluginsBrowserMocksObj from './kbn_core_plugins_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_plugins_contracts_browser.mdx b/api_docs/kbn_core_plugins_contracts_browser.mdx index b9707d92e0f6f..857165f58ca90 100644 --- a/api_docs/kbn_core_plugins_contracts_browser.mdx +++ b/api_docs/kbn_core_plugins_contracts_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-plugins-contracts-browser title: "@kbn/core-plugins-contracts-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-plugins-contracts-browser plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-plugins-contracts-browser'] --- import kbnCorePluginsContractsBrowserObj from './kbn_core_plugins_contracts_browser.devdocs.json'; diff --git a/api_docs/kbn_core_plugins_contracts_server.mdx b/api_docs/kbn_core_plugins_contracts_server.mdx index 99798dab77117..8e7c0012e98d5 100644 --- a/api_docs/kbn_core_plugins_contracts_server.mdx +++ b/api_docs/kbn_core_plugins_contracts_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-plugins-contracts-server title: "@kbn/core-plugins-contracts-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-plugins-contracts-server plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-plugins-contracts-server'] --- import kbnCorePluginsContractsServerObj from './kbn_core_plugins_contracts_server.devdocs.json'; diff --git a/api_docs/kbn_core_plugins_server.mdx b/api_docs/kbn_core_plugins_server.mdx index 5298c0275e95d..b2df7a313f559 100644 --- a/api_docs/kbn_core_plugins_server.mdx +++ b/api_docs/kbn_core_plugins_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-plugins-server title: "@kbn/core-plugins-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-plugins-server plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-plugins-server'] --- import kbnCorePluginsServerObj from './kbn_core_plugins_server.devdocs.json'; diff --git a/api_docs/kbn_core_plugins_server_mocks.mdx b/api_docs/kbn_core_plugins_server_mocks.mdx index 7babf3b2abbd9..2936217c4720b 100644 --- a/api_docs/kbn_core_plugins_server_mocks.mdx +++ b/api_docs/kbn_core_plugins_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-plugins-server-mocks title: "@kbn/core-plugins-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-plugins-server-mocks plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-plugins-server-mocks'] --- import kbnCorePluginsServerMocksObj from './kbn_core_plugins_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_preboot_server.mdx b/api_docs/kbn_core_preboot_server.mdx index 62a2868645c08..d752eeed3f0c9 100644 --- a/api_docs/kbn_core_preboot_server.mdx +++ b/api_docs/kbn_core_preboot_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-preboot-server title: "@kbn/core-preboot-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-preboot-server plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-preboot-server'] --- import kbnCorePrebootServerObj from './kbn_core_preboot_server.devdocs.json'; diff --git a/api_docs/kbn_core_preboot_server_mocks.mdx b/api_docs/kbn_core_preboot_server_mocks.mdx index 6769b10a44aab..f64f2b0d436fe 100644 --- a/api_docs/kbn_core_preboot_server_mocks.mdx +++ b/api_docs/kbn_core_preboot_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-preboot-server-mocks title: "@kbn/core-preboot-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-preboot-server-mocks plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-preboot-server-mocks'] --- import kbnCorePrebootServerMocksObj from './kbn_core_preboot_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_rendering_browser_mocks.mdx b/api_docs/kbn_core_rendering_browser_mocks.mdx index baaaae37d103c..f670376747a59 100644 --- a/api_docs/kbn_core_rendering_browser_mocks.mdx +++ b/api_docs/kbn_core_rendering_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-rendering-browser-mocks title: "@kbn/core-rendering-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-rendering-browser-mocks plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-rendering-browser-mocks'] --- import kbnCoreRenderingBrowserMocksObj from './kbn_core_rendering_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_rendering_server_internal.mdx b/api_docs/kbn_core_rendering_server_internal.mdx index 13c3a8ad969af..e2a2123290336 100644 --- a/api_docs/kbn_core_rendering_server_internal.mdx +++ b/api_docs/kbn_core_rendering_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-rendering-server-internal title: "@kbn/core-rendering-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-rendering-server-internal plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-rendering-server-internal'] --- import kbnCoreRenderingServerInternalObj from './kbn_core_rendering_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_rendering_server_mocks.mdx b/api_docs/kbn_core_rendering_server_mocks.mdx index f372ab8b2d3ab..744d0cdd5119d 100644 --- a/api_docs/kbn_core_rendering_server_mocks.mdx +++ b/api_docs/kbn_core_rendering_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-rendering-server-mocks title: "@kbn/core-rendering-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-rendering-server-mocks plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-rendering-server-mocks'] --- import kbnCoreRenderingServerMocksObj from './kbn_core_rendering_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_root_server_internal.mdx b/api_docs/kbn_core_root_server_internal.mdx index 4e5c72a5f8eea..e572a91414815 100644 --- a/api_docs/kbn_core_root_server_internal.mdx +++ b/api_docs/kbn_core_root_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-root-server-internal title: "@kbn/core-root-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-root-server-internal plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-root-server-internal'] --- import kbnCoreRootServerInternalObj from './kbn_core_root_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_api_browser.mdx b/api_docs/kbn_core_saved_objects_api_browser.mdx index d31da40bf23f9..d68450b1b4801 100644 --- a/api_docs/kbn_core_saved_objects_api_browser.mdx +++ b/api_docs/kbn_core_saved_objects_api_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-api-browser title: "@kbn/core-saved-objects-api-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-api-browser plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-api-browser'] --- import kbnCoreSavedObjectsApiBrowserObj from './kbn_core_saved_objects_api_browser.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_api_server.mdx b/api_docs/kbn_core_saved_objects_api_server.mdx index 1e49feeb2510e..1cf324af1423c 100644 --- a/api_docs/kbn_core_saved_objects_api_server.mdx +++ b/api_docs/kbn_core_saved_objects_api_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-api-server title: "@kbn/core-saved-objects-api-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-api-server plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-api-server'] --- import kbnCoreSavedObjectsApiServerObj from './kbn_core_saved_objects_api_server.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_api_server_mocks.mdx b/api_docs/kbn_core_saved_objects_api_server_mocks.mdx index 97e82d7aa7b76..70d6897e3edb3 100644 --- a/api_docs/kbn_core_saved_objects_api_server_mocks.mdx +++ b/api_docs/kbn_core_saved_objects_api_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-api-server-mocks title: "@kbn/core-saved-objects-api-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-api-server-mocks plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-api-server-mocks'] --- import kbnCoreSavedObjectsApiServerMocksObj from './kbn_core_saved_objects_api_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_base_server_internal.mdx b/api_docs/kbn_core_saved_objects_base_server_internal.mdx index 9057bcfe99d31..6f98d01406b50 100644 --- a/api_docs/kbn_core_saved_objects_base_server_internal.mdx +++ b/api_docs/kbn_core_saved_objects_base_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-base-server-internal title: "@kbn/core-saved-objects-base-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-base-server-internal plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-base-server-internal'] --- import kbnCoreSavedObjectsBaseServerInternalObj from './kbn_core_saved_objects_base_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_base_server_mocks.mdx b/api_docs/kbn_core_saved_objects_base_server_mocks.mdx index aaec42c56daf6..43f47de2f8245 100644 --- a/api_docs/kbn_core_saved_objects_base_server_mocks.mdx +++ b/api_docs/kbn_core_saved_objects_base_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-base-server-mocks title: "@kbn/core-saved-objects-base-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-base-server-mocks plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-base-server-mocks'] --- import kbnCoreSavedObjectsBaseServerMocksObj from './kbn_core_saved_objects_base_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_browser.mdx b/api_docs/kbn_core_saved_objects_browser.mdx index c8234544ccd1d..2aec6719620aa 100644 --- a/api_docs/kbn_core_saved_objects_browser.mdx +++ b/api_docs/kbn_core_saved_objects_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-browser title: "@kbn/core-saved-objects-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-browser plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-browser'] --- import kbnCoreSavedObjectsBrowserObj from './kbn_core_saved_objects_browser.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_browser_internal.mdx b/api_docs/kbn_core_saved_objects_browser_internal.mdx index 6582237200cbc..4f76aa75bd43b 100644 --- a/api_docs/kbn_core_saved_objects_browser_internal.mdx +++ b/api_docs/kbn_core_saved_objects_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-browser-internal title: "@kbn/core-saved-objects-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-browser-internal plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-browser-internal'] --- import kbnCoreSavedObjectsBrowserInternalObj from './kbn_core_saved_objects_browser_internal.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_browser_mocks.mdx b/api_docs/kbn_core_saved_objects_browser_mocks.mdx index c13ea0ff70969..21df295a9d630 100644 --- a/api_docs/kbn_core_saved_objects_browser_mocks.mdx +++ b/api_docs/kbn_core_saved_objects_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-browser-mocks title: "@kbn/core-saved-objects-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-browser-mocks plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-browser-mocks'] --- import kbnCoreSavedObjectsBrowserMocksObj from './kbn_core_saved_objects_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_common.mdx b/api_docs/kbn_core_saved_objects_common.mdx index 6ddf58954df9e..ba4df4f0891f6 100644 --- a/api_docs/kbn_core_saved_objects_common.mdx +++ b/api_docs/kbn_core_saved_objects_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-common title: "@kbn/core-saved-objects-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-common plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-common'] --- import kbnCoreSavedObjectsCommonObj from './kbn_core_saved_objects_common.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_import_export_server_internal.mdx b/api_docs/kbn_core_saved_objects_import_export_server_internal.mdx index 3fd0105711322..d7d4c3d9a4d84 100644 --- a/api_docs/kbn_core_saved_objects_import_export_server_internal.mdx +++ b/api_docs/kbn_core_saved_objects_import_export_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-import-export-server-internal title: "@kbn/core-saved-objects-import-export-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-import-export-server-internal plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-import-export-server-internal'] --- import kbnCoreSavedObjectsImportExportServerInternalObj from './kbn_core_saved_objects_import_export_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_import_export_server_mocks.mdx b/api_docs/kbn_core_saved_objects_import_export_server_mocks.mdx index 525b81ff6abc0..1aac90d0a6532 100644 --- a/api_docs/kbn_core_saved_objects_import_export_server_mocks.mdx +++ b/api_docs/kbn_core_saved_objects_import_export_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-import-export-server-mocks title: "@kbn/core-saved-objects-import-export-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-import-export-server-mocks plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-import-export-server-mocks'] --- import kbnCoreSavedObjectsImportExportServerMocksObj from './kbn_core_saved_objects_import_export_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_migration_server_internal.mdx b/api_docs/kbn_core_saved_objects_migration_server_internal.mdx index f1e92f95f67d2..724eb67a56a29 100644 --- a/api_docs/kbn_core_saved_objects_migration_server_internal.mdx +++ b/api_docs/kbn_core_saved_objects_migration_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-migration-server-internal title: "@kbn/core-saved-objects-migration-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-migration-server-internal plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-migration-server-internal'] --- import kbnCoreSavedObjectsMigrationServerInternalObj from './kbn_core_saved_objects_migration_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_migration_server_mocks.mdx b/api_docs/kbn_core_saved_objects_migration_server_mocks.mdx index 0cecc51f17938..16d93e5296738 100644 --- a/api_docs/kbn_core_saved_objects_migration_server_mocks.mdx +++ b/api_docs/kbn_core_saved_objects_migration_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-migration-server-mocks title: "@kbn/core-saved-objects-migration-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-migration-server-mocks plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-migration-server-mocks'] --- import kbnCoreSavedObjectsMigrationServerMocksObj from './kbn_core_saved_objects_migration_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_server.mdx b/api_docs/kbn_core_saved_objects_server.mdx index 8c5906b7ebea7..e5e53e1336d9c 100644 --- a/api_docs/kbn_core_saved_objects_server.mdx +++ b/api_docs/kbn_core_saved_objects_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-server title: "@kbn/core-saved-objects-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-server plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-server'] --- import kbnCoreSavedObjectsServerObj from './kbn_core_saved_objects_server.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_server_internal.mdx b/api_docs/kbn_core_saved_objects_server_internal.mdx index 5b5c7633d525f..c2a3c0c73e63e 100644 --- a/api_docs/kbn_core_saved_objects_server_internal.mdx +++ b/api_docs/kbn_core_saved_objects_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-server-internal title: "@kbn/core-saved-objects-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-server-internal plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-server-internal'] --- import kbnCoreSavedObjectsServerInternalObj from './kbn_core_saved_objects_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_server_mocks.mdx b/api_docs/kbn_core_saved_objects_server_mocks.mdx index f38f6fc827a06..a881e0eb34bf9 100644 --- a/api_docs/kbn_core_saved_objects_server_mocks.mdx +++ b/api_docs/kbn_core_saved_objects_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-server-mocks title: "@kbn/core-saved-objects-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-server-mocks plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-server-mocks'] --- import kbnCoreSavedObjectsServerMocksObj from './kbn_core_saved_objects_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_utils_server.mdx b/api_docs/kbn_core_saved_objects_utils_server.mdx index 65a35d82423ac..c2f27327b7777 100644 --- a/api_docs/kbn_core_saved_objects_utils_server.mdx +++ b/api_docs/kbn_core_saved_objects_utils_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-utils-server title: "@kbn/core-saved-objects-utils-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-utils-server plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-utils-server'] --- import kbnCoreSavedObjectsUtilsServerObj from './kbn_core_saved_objects_utils_server.devdocs.json'; diff --git a/api_docs/kbn_core_status_common.mdx b/api_docs/kbn_core_status_common.mdx index 665e64afdc526..ca036d1153da6 100644 --- a/api_docs/kbn_core_status_common.mdx +++ b/api_docs/kbn_core_status_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-status-common title: "@kbn/core-status-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-status-common plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-status-common'] --- import kbnCoreStatusCommonObj from './kbn_core_status_common.devdocs.json'; diff --git a/api_docs/kbn_core_status_common_internal.mdx b/api_docs/kbn_core_status_common_internal.mdx index c4c8b98931c54..d90e3c08786d0 100644 --- a/api_docs/kbn_core_status_common_internal.mdx +++ b/api_docs/kbn_core_status_common_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-status-common-internal title: "@kbn/core-status-common-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-status-common-internal plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-status-common-internal'] --- import kbnCoreStatusCommonInternalObj from './kbn_core_status_common_internal.devdocs.json'; diff --git a/api_docs/kbn_core_status_server.mdx b/api_docs/kbn_core_status_server.mdx index 18ad1540649f0..88307e567e839 100644 --- a/api_docs/kbn_core_status_server.mdx +++ b/api_docs/kbn_core_status_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-status-server title: "@kbn/core-status-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-status-server plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-status-server'] --- import kbnCoreStatusServerObj from './kbn_core_status_server.devdocs.json'; diff --git a/api_docs/kbn_core_status_server_internal.mdx b/api_docs/kbn_core_status_server_internal.mdx index 5f65fb4396d34..0afc9303f31d2 100644 --- a/api_docs/kbn_core_status_server_internal.mdx +++ b/api_docs/kbn_core_status_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-status-server-internal title: "@kbn/core-status-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-status-server-internal plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-status-server-internal'] --- import kbnCoreStatusServerInternalObj from './kbn_core_status_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_status_server_mocks.mdx b/api_docs/kbn_core_status_server_mocks.mdx index 0b0241c0539b1..e4fc548eab6ab 100644 --- a/api_docs/kbn_core_status_server_mocks.mdx +++ b/api_docs/kbn_core_status_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-status-server-mocks title: "@kbn/core-status-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-status-server-mocks plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-status-server-mocks'] --- import kbnCoreStatusServerMocksObj from './kbn_core_status_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_test_helpers_deprecations_getters.mdx b/api_docs/kbn_core_test_helpers_deprecations_getters.mdx index 9cb19b47f2f8d..9c87f6657c87c 100644 --- a/api_docs/kbn_core_test_helpers_deprecations_getters.mdx +++ b/api_docs/kbn_core_test_helpers_deprecations_getters.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-test-helpers-deprecations-getters title: "@kbn/core-test-helpers-deprecations-getters" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-test-helpers-deprecations-getters plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-test-helpers-deprecations-getters'] --- import kbnCoreTestHelpersDeprecationsGettersObj from './kbn_core_test_helpers_deprecations_getters.devdocs.json'; diff --git a/api_docs/kbn_core_test_helpers_http_setup_browser.mdx b/api_docs/kbn_core_test_helpers_http_setup_browser.mdx index 301bcefe248ed..86fbd097916a7 100644 --- a/api_docs/kbn_core_test_helpers_http_setup_browser.mdx +++ b/api_docs/kbn_core_test_helpers_http_setup_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-test-helpers-http-setup-browser title: "@kbn/core-test-helpers-http-setup-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-test-helpers-http-setup-browser plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-test-helpers-http-setup-browser'] --- import kbnCoreTestHelpersHttpSetupBrowserObj from './kbn_core_test_helpers_http_setup_browser.devdocs.json'; diff --git a/api_docs/kbn_core_test_helpers_kbn_server.mdx b/api_docs/kbn_core_test_helpers_kbn_server.mdx index c08bc5b05856e..c7a5027df32fc 100644 --- a/api_docs/kbn_core_test_helpers_kbn_server.mdx +++ b/api_docs/kbn_core_test_helpers_kbn_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-test-helpers-kbn-server title: "@kbn/core-test-helpers-kbn-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-test-helpers-kbn-server plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-test-helpers-kbn-server'] --- import kbnCoreTestHelpersKbnServerObj from './kbn_core_test_helpers_kbn_server.devdocs.json'; diff --git a/api_docs/kbn_core_test_helpers_model_versions.mdx b/api_docs/kbn_core_test_helpers_model_versions.mdx index 5ee3fb59095ee..ba2b6d41d3578 100644 --- a/api_docs/kbn_core_test_helpers_model_versions.mdx +++ b/api_docs/kbn_core_test_helpers_model_versions.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-test-helpers-model-versions title: "@kbn/core-test-helpers-model-versions" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-test-helpers-model-versions plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-test-helpers-model-versions'] --- import kbnCoreTestHelpersModelVersionsObj from './kbn_core_test_helpers_model_versions.devdocs.json'; diff --git a/api_docs/kbn_core_test_helpers_so_type_serializer.mdx b/api_docs/kbn_core_test_helpers_so_type_serializer.mdx index 23021ff0d34d6..a04866dcab741 100644 --- a/api_docs/kbn_core_test_helpers_so_type_serializer.mdx +++ b/api_docs/kbn_core_test_helpers_so_type_serializer.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-test-helpers-so-type-serializer title: "@kbn/core-test-helpers-so-type-serializer" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-test-helpers-so-type-serializer plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-test-helpers-so-type-serializer'] --- import kbnCoreTestHelpersSoTypeSerializerObj from './kbn_core_test_helpers_so_type_serializer.devdocs.json'; diff --git a/api_docs/kbn_core_test_helpers_test_utils.mdx b/api_docs/kbn_core_test_helpers_test_utils.mdx index 9326fc1fe9ae4..f9bd2a3a0273e 100644 --- a/api_docs/kbn_core_test_helpers_test_utils.mdx +++ b/api_docs/kbn_core_test_helpers_test_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-test-helpers-test-utils title: "@kbn/core-test-helpers-test-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-test-helpers-test-utils plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-test-helpers-test-utils'] --- import kbnCoreTestHelpersTestUtilsObj from './kbn_core_test_helpers_test_utils.devdocs.json'; diff --git a/api_docs/kbn_core_theme_browser.mdx b/api_docs/kbn_core_theme_browser.mdx index a18a9486f3042..c88b90d46a771 100644 --- a/api_docs/kbn_core_theme_browser.mdx +++ b/api_docs/kbn_core_theme_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-theme-browser title: "@kbn/core-theme-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-theme-browser plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-theme-browser'] --- import kbnCoreThemeBrowserObj from './kbn_core_theme_browser.devdocs.json'; diff --git a/api_docs/kbn_core_theme_browser_mocks.mdx b/api_docs/kbn_core_theme_browser_mocks.mdx index 37e887e982434..3453d2f7d3c9e 100644 --- a/api_docs/kbn_core_theme_browser_mocks.mdx +++ b/api_docs/kbn_core_theme_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-theme-browser-mocks title: "@kbn/core-theme-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-theme-browser-mocks plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-theme-browser-mocks'] --- import kbnCoreThemeBrowserMocksObj from './kbn_core_theme_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_ui_settings_browser.mdx b/api_docs/kbn_core_ui_settings_browser.mdx index c7cc9d91c627e..8f6f5d8511e9b 100644 --- a/api_docs/kbn_core_ui_settings_browser.mdx +++ b/api_docs/kbn_core_ui_settings_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-ui-settings-browser title: "@kbn/core-ui-settings-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-ui-settings-browser plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-ui-settings-browser'] --- import kbnCoreUiSettingsBrowserObj from './kbn_core_ui_settings_browser.devdocs.json'; diff --git a/api_docs/kbn_core_ui_settings_browser_internal.mdx b/api_docs/kbn_core_ui_settings_browser_internal.mdx index 566bd84058ef8..263f47475f03d 100644 --- a/api_docs/kbn_core_ui_settings_browser_internal.mdx +++ b/api_docs/kbn_core_ui_settings_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-ui-settings-browser-internal title: "@kbn/core-ui-settings-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-ui-settings-browser-internal plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-ui-settings-browser-internal'] --- import kbnCoreUiSettingsBrowserInternalObj from './kbn_core_ui_settings_browser_internal.devdocs.json'; diff --git a/api_docs/kbn_core_ui_settings_browser_mocks.mdx b/api_docs/kbn_core_ui_settings_browser_mocks.mdx index af488ffd2acd5..66b55efefa41d 100644 --- a/api_docs/kbn_core_ui_settings_browser_mocks.mdx +++ b/api_docs/kbn_core_ui_settings_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-ui-settings-browser-mocks title: "@kbn/core-ui-settings-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-ui-settings-browser-mocks plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-ui-settings-browser-mocks'] --- import kbnCoreUiSettingsBrowserMocksObj from './kbn_core_ui_settings_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_ui_settings_common.mdx b/api_docs/kbn_core_ui_settings_common.mdx index 66662d947c797..405cdd3a9d4c8 100644 --- a/api_docs/kbn_core_ui_settings_common.mdx +++ b/api_docs/kbn_core_ui_settings_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-ui-settings-common title: "@kbn/core-ui-settings-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-ui-settings-common plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-ui-settings-common'] --- import kbnCoreUiSettingsCommonObj from './kbn_core_ui_settings_common.devdocs.json'; diff --git a/api_docs/kbn_core_ui_settings_server.mdx b/api_docs/kbn_core_ui_settings_server.mdx index a6551d1d2659f..fe6b90de34cb2 100644 --- a/api_docs/kbn_core_ui_settings_server.mdx +++ b/api_docs/kbn_core_ui_settings_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-ui-settings-server title: "@kbn/core-ui-settings-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-ui-settings-server plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-ui-settings-server'] --- import kbnCoreUiSettingsServerObj from './kbn_core_ui_settings_server.devdocs.json'; diff --git a/api_docs/kbn_core_ui_settings_server_internal.mdx b/api_docs/kbn_core_ui_settings_server_internal.mdx index b013c5d412dc8..106ac8e26157e 100644 --- a/api_docs/kbn_core_ui_settings_server_internal.mdx +++ b/api_docs/kbn_core_ui_settings_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-ui-settings-server-internal title: "@kbn/core-ui-settings-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-ui-settings-server-internal plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-ui-settings-server-internal'] --- import kbnCoreUiSettingsServerInternalObj from './kbn_core_ui_settings_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_ui_settings_server_mocks.mdx b/api_docs/kbn_core_ui_settings_server_mocks.mdx index 5349a76a68d79..2408a9112abcd 100644 --- a/api_docs/kbn_core_ui_settings_server_mocks.mdx +++ b/api_docs/kbn_core_ui_settings_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-ui-settings-server-mocks title: "@kbn/core-ui-settings-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-ui-settings-server-mocks plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-ui-settings-server-mocks'] --- import kbnCoreUiSettingsServerMocksObj from './kbn_core_ui_settings_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_usage_data_server.mdx b/api_docs/kbn_core_usage_data_server.mdx index 5fdc316e118bf..48a0c49fc93ed 100644 --- a/api_docs/kbn_core_usage_data_server.mdx +++ b/api_docs/kbn_core_usage_data_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-usage-data-server title: "@kbn/core-usage-data-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-usage-data-server plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-usage-data-server'] --- import kbnCoreUsageDataServerObj from './kbn_core_usage_data_server.devdocs.json'; diff --git a/api_docs/kbn_core_usage_data_server_internal.mdx b/api_docs/kbn_core_usage_data_server_internal.mdx index c923e97a6aa42..1a26df321eb14 100644 --- a/api_docs/kbn_core_usage_data_server_internal.mdx +++ b/api_docs/kbn_core_usage_data_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-usage-data-server-internal title: "@kbn/core-usage-data-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-usage-data-server-internal plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-usage-data-server-internal'] --- import kbnCoreUsageDataServerInternalObj from './kbn_core_usage_data_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_usage_data_server_mocks.mdx b/api_docs/kbn_core_usage_data_server_mocks.mdx index aee3600da95ff..82209613a9b0f 100644 --- a/api_docs/kbn_core_usage_data_server_mocks.mdx +++ b/api_docs/kbn_core_usage_data_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-usage-data-server-mocks title: "@kbn/core-usage-data-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-usage-data-server-mocks plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-usage-data-server-mocks'] --- import kbnCoreUsageDataServerMocksObj from './kbn_core_usage_data_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_user_settings_server.mdx b/api_docs/kbn_core_user_settings_server.mdx index 0d38634d921ba..6c3a2fde37281 100644 --- a/api_docs/kbn_core_user_settings_server.mdx +++ b/api_docs/kbn_core_user_settings_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-user-settings-server title: "@kbn/core-user-settings-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-user-settings-server plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-user-settings-server'] --- import kbnCoreUserSettingsServerObj from './kbn_core_user_settings_server.devdocs.json'; diff --git a/api_docs/kbn_core_user_settings_server_internal.mdx b/api_docs/kbn_core_user_settings_server_internal.mdx index bc3b2b10527e8..beb92094d0220 100644 --- a/api_docs/kbn_core_user_settings_server_internal.mdx +++ b/api_docs/kbn_core_user_settings_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-user-settings-server-internal title: "@kbn/core-user-settings-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-user-settings-server-internal plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-user-settings-server-internal'] --- import kbnCoreUserSettingsServerInternalObj from './kbn_core_user_settings_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_user_settings_server_mocks.mdx b/api_docs/kbn_core_user_settings_server_mocks.mdx index 253faa7ce619c..66a88e27d6f2e 100644 --- a/api_docs/kbn_core_user_settings_server_mocks.mdx +++ b/api_docs/kbn_core_user_settings_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-user-settings-server-mocks title: "@kbn/core-user-settings-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-user-settings-server-mocks plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-user-settings-server-mocks'] --- import kbnCoreUserSettingsServerMocksObj from './kbn_core_user_settings_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_crypto.mdx b/api_docs/kbn_crypto.mdx index 78f3db826c596..84be38da7ca7a 100644 --- a/api_docs/kbn_crypto.mdx +++ b/api_docs/kbn_crypto.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-crypto title: "@kbn/crypto" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/crypto plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/crypto'] --- import kbnCryptoObj from './kbn_crypto.devdocs.json'; diff --git a/api_docs/kbn_crypto_browser.mdx b/api_docs/kbn_crypto_browser.mdx index 1a722b30d978f..796196ea247ff 100644 --- a/api_docs/kbn_crypto_browser.mdx +++ b/api_docs/kbn_crypto_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-crypto-browser title: "@kbn/crypto-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/crypto-browser plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/crypto-browser'] --- import kbnCryptoBrowserObj from './kbn_crypto_browser.devdocs.json'; diff --git a/api_docs/kbn_custom_icons.mdx b/api_docs/kbn_custom_icons.mdx index 0ed654d5aaf25..9b1d1c7cef796 100644 --- a/api_docs/kbn_custom_icons.mdx +++ b/api_docs/kbn_custom_icons.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-custom-icons title: "@kbn/custom-icons" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/custom-icons plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/custom-icons'] --- import kbnCustomIconsObj from './kbn_custom_icons.devdocs.json'; diff --git a/api_docs/kbn_custom_integrations.mdx b/api_docs/kbn_custom_integrations.mdx index 798d08ace5210..01540a5cd4089 100644 --- a/api_docs/kbn_custom_integrations.mdx +++ b/api_docs/kbn_custom_integrations.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-custom-integrations title: "@kbn/custom-integrations" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/custom-integrations plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/custom-integrations'] --- import kbnCustomIntegrationsObj from './kbn_custom_integrations.devdocs.json'; diff --git a/api_docs/kbn_cypress_config.mdx b/api_docs/kbn_cypress_config.mdx index e315e995406b6..2185c48e367ff 100644 --- a/api_docs/kbn_cypress_config.mdx +++ b/api_docs/kbn_cypress_config.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-cypress-config title: "@kbn/cypress-config" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/cypress-config plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/cypress-config'] --- import kbnCypressConfigObj from './kbn_cypress_config.devdocs.json'; diff --git a/api_docs/kbn_data_forge.mdx b/api_docs/kbn_data_forge.mdx index 793efe5bee8a3..b1ad2a5b28915 100644 --- a/api_docs/kbn_data_forge.mdx +++ b/api_docs/kbn_data_forge.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-data-forge title: "@kbn/data-forge" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/data-forge plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/data-forge'] --- import kbnDataForgeObj from './kbn_data_forge.devdocs.json'; diff --git a/api_docs/kbn_data_service.mdx b/api_docs/kbn_data_service.mdx index 599b04c8bc06a..2b0d43c039332 100644 --- a/api_docs/kbn_data_service.mdx +++ b/api_docs/kbn_data_service.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-data-service title: "@kbn/data-service" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/data-service plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/data-service'] --- import kbnDataServiceObj from './kbn_data_service.devdocs.json'; diff --git a/api_docs/kbn_data_stream_adapter.mdx b/api_docs/kbn_data_stream_adapter.mdx index e9be473007ba4..8e8315e60ce57 100644 --- a/api_docs/kbn_data_stream_adapter.mdx +++ b/api_docs/kbn_data_stream_adapter.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-data-stream-adapter title: "@kbn/data-stream-adapter" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/data-stream-adapter plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/data-stream-adapter'] --- import kbnDataStreamAdapterObj from './kbn_data_stream_adapter.devdocs.json'; diff --git a/api_docs/kbn_data_view_utils.mdx b/api_docs/kbn_data_view_utils.mdx index 74759e582d28d..88906f80b0a58 100644 --- a/api_docs/kbn_data_view_utils.mdx +++ b/api_docs/kbn_data_view_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-data-view-utils title: "@kbn/data-view-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/data-view-utils plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/data-view-utils'] --- import kbnDataViewUtilsObj from './kbn_data_view_utils.devdocs.json'; diff --git a/api_docs/kbn_datemath.mdx b/api_docs/kbn_datemath.mdx index bcac4b8857760..48625b038aa98 100644 --- a/api_docs/kbn_datemath.mdx +++ b/api_docs/kbn_datemath.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-datemath title: "@kbn/datemath" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/datemath plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/datemath'] --- import kbnDatemathObj from './kbn_datemath.devdocs.json'; diff --git a/api_docs/kbn_deeplinks_analytics.mdx b/api_docs/kbn_deeplinks_analytics.mdx index 177b67ed87b0c..fb49d97ba0dd4 100644 --- a/api_docs/kbn_deeplinks_analytics.mdx +++ b/api_docs/kbn_deeplinks_analytics.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-deeplinks-analytics title: "@kbn/deeplinks-analytics" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/deeplinks-analytics plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/deeplinks-analytics'] --- import kbnDeeplinksAnalyticsObj from './kbn_deeplinks_analytics.devdocs.json'; diff --git a/api_docs/kbn_deeplinks_devtools.mdx b/api_docs/kbn_deeplinks_devtools.mdx index 41e58be67130c..d5708e3c8fdc3 100644 --- a/api_docs/kbn_deeplinks_devtools.mdx +++ b/api_docs/kbn_deeplinks_devtools.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-deeplinks-devtools title: "@kbn/deeplinks-devtools" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/deeplinks-devtools plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/deeplinks-devtools'] --- import kbnDeeplinksDevtoolsObj from './kbn_deeplinks_devtools.devdocs.json'; diff --git a/api_docs/kbn_deeplinks_management.mdx b/api_docs/kbn_deeplinks_management.mdx index b9b0f7279aa11..e72bc30bb36a0 100644 --- a/api_docs/kbn_deeplinks_management.mdx +++ b/api_docs/kbn_deeplinks_management.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-deeplinks-management title: "@kbn/deeplinks-management" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/deeplinks-management plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/deeplinks-management'] --- import kbnDeeplinksManagementObj from './kbn_deeplinks_management.devdocs.json'; diff --git a/api_docs/kbn_deeplinks_ml.mdx b/api_docs/kbn_deeplinks_ml.mdx index 8d9b932e283e4..b59cd2d1ad099 100644 --- a/api_docs/kbn_deeplinks_ml.mdx +++ b/api_docs/kbn_deeplinks_ml.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-deeplinks-ml title: "@kbn/deeplinks-ml" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/deeplinks-ml plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/deeplinks-ml'] --- import kbnDeeplinksMlObj from './kbn_deeplinks_ml.devdocs.json'; diff --git a/api_docs/kbn_deeplinks_observability.mdx b/api_docs/kbn_deeplinks_observability.mdx index ac61e0f3080c3..90f87af38a4a4 100644 --- a/api_docs/kbn_deeplinks_observability.mdx +++ b/api_docs/kbn_deeplinks_observability.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-deeplinks-observability title: "@kbn/deeplinks-observability" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/deeplinks-observability plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/deeplinks-observability'] --- import kbnDeeplinksObservabilityObj from './kbn_deeplinks_observability.devdocs.json'; diff --git a/api_docs/kbn_deeplinks_search.mdx b/api_docs/kbn_deeplinks_search.mdx index 3b06d1ab07e00..51bc19981dc37 100644 --- a/api_docs/kbn_deeplinks_search.mdx +++ b/api_docs/kbn_deeplinks_search.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-deeplinks-search title: "@kbn/deeplinks-search" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/deeplinks-search plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/deeplinks-search'] --- import kbnDeeplinksSearchObj from './kbn_deeplinks_search.devdocs.json'; diff --git a/api_docs/kbn_default_nav_analytics.mdx b/api_docs/kbn_default_nav_analytics.mdx index de62031246506..3868161665a57 100644 --- a/api_docs/kbn_default_nav_analytics.mdx +++ b/api_docs/kbn_default_nav_analytics.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-default-nav-analytics title: "@kbn/default-nav-analytics" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/default-nav-analytics plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/default-nav-analytics'] --- import kbnDefaultNavAnalyticsObj from './kbn_default_nav_analytics.devdocs.json'; diff --git a/api_docs/kbn_default_nav_devtools.mdx b/api_docs/kbn_default_nav_devtools.mdx index 9476857ca034b..514cb74776065 100644 --- a/api_docs/kbn_default_nav_devtools.mdx +++ b/api_docs/kbn_default_nav_devtools.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-default-nav-devtools title: "@kbn/default-nav-devtools" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/default-nav-devtools plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/default-nav-devtools'] --- import kbnDefaultNavDevtoolsObj from './kbn_default_nav_devtools.devdocs.json'; diff --git a/api_docs/kbn_default_nav_management.mdx b/api_docs/kbn_default_nav_management.mdx index 0d232ae9edcf7..8311c43a9a441 100644 --- a/api_docs/kbn_default_nav_management.mdx +++ b/api_docs/kbn_default_nav_management.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-default-nav-management title: "@kbn/default-nav-management" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/default-nav-management plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/default-nav-management'] --- import kbnDefaultNavManagementObj from './kbn_default_nav_management.devdocs.json'; diff --git a/api_docs/kbn_default_nav_ml.mdx b/api_docs/kbn_default_nav_ml.mdx index e5dac041682a6..0dfe13c0db377 100644 --- a/api_docs/kbn_default_nav_ml.mdx +++ b/api_docs/kbn_default_nav_ml.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-default-nav-ml title: "@kbn/default-nav-ml" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/default-nav-ml plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/default-nav-ml'] --- import kbnDefaultNavMlObj from './kbn_default_nav_ml.devdocs.json'; diff --git a/api_docs/kbn_dev_cli_errors.mdx b/api_docs/kbn_dev_cli_errors.mdx index e2753afd1793b..469aca48cc934 100644 --- a/api_docs/kbn_dev_cli_errors.mdx +++ b/api_docs/kbn_dev_cli_errors.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-dev-cli-errors title: "@kbn/dev-cli-errors" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/dev-cli-errors plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/dev-cli-errors'] --- import kbnDevCliErrorsObj from './kbn_dev_cli_errors.devdocs.json'; diff --git a/api_docs/kbn_dev_cli_runner.mdx b/api_docs/kbn_dev_cli_runner.mdx index 3bd2e1eac24c3..5150b3208baf6 100644 --- a/api_docs/kbn_dev_cli_runner.mdx +++ b/api_docs/kbn_dev_cli_runner.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-dev-cli-runner title: "@kbn/dev-cli-runner" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/dev-cli-runner plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/dev-cli-runner'] --- import kbnDevCliRunnerObj from './kbn_dev_cli_runner.devdocs.json'; diff --git a/api_docs/kbn_dev_proc_runner.mdx b/api_docs/kbn_dev_proc_runner.mdx index 872e50eff32cd..4ad3bd94cd9a3 100644 --- a/api_docs/kbn_dev_proc_runner.mdx +++ b/api_docs/kbn_dev_proc_runner.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-dev-proc-runner title: "@kbn/dev-proc-runner" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/dev-proc-runner plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/dev-proc-runner'] --- import kbnDevProcRunnerObj from './kbn_dev_proc_runner.devdocs.json'; diff --git a/api_docs/kbn_dev_utils.mdx b/api_docs/kbn_dev_utils.mdx index b568f00d72d88..92a932251f26e 100644 --- a/api_docs/kbn_dev_utils.mdx +++ b/api_docs/kbn_dev_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-dev-utils title: "@kbn/dev-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/dev-utils plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/dev-utils'] --- import kbnDevUtilsObj from './kbn_dev_utils.devdocs.json'; diff --git a/api_docs/kbn_discover_utils.mdx b/api_docs/kbn_discover_utils.mdx index 46cbc852852f8..ea04d05543c93 100644 --- a/api_docs/kbn_discover_utils.mdx +++ b/api_docs/kbn_discover_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-discover-utils title: "@kbn/discover-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/discover-utils plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/discover-utils'] --- import kbnDiscoverUtilsObj from './kbn_discover_utils.devdocs.json'; diff --git a/api_docs/kbn_doc_links.mdx b/api_docs/kbn_doc_links.mdx index f40b4e63aaeba..d3327a14ef58b 100644 --- a/api_docs/kbn_doc_links.mdx +++ b/api_docs/kbn_doc_links.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-doc-links title: "@kbn/doc-links" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/doc-links plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/doc-links'] --- import kbnDocLinksObj from './kbn_doc_links.devdocs.json'; diff --git a/api_docs/kbn_docs_utils.mdx b/api_docs/kbn_docs_utils.mdx index 225bbeeffbde4..2a68ad78330a5 100644 --- a/api_docs/kbn_docs_utils.mdx +++ b/api_docs/kbn_docs_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-docs-utils title: "@kbn/docs-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/docs-utils plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/docs-utils'] --- import kbnDocsUtilsObj from './kbn_docs_utils.devdocs.json'; diff --git a/api_docs/kbn_dom_drag_drop.mdx b/api_docs/kbn_dom_drag_drop.mdx index 202bff9228195..39be718d0ee60 100644 --- a/api_docs/kbn_dom_drag_drop.mdx +++ b/api_docs/kbn_dom_drag_drop.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-dom-drag-drop title: "@kbn/dom-drag-drop" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/dom-drag-drop plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/dom-drag-drop'] --- import kbnDomDragDropObj from './kbn_dom_drag_drop.devdocs.json'; diff --git a/api_docs/kbn_ebt_tools.mdx b/api_docs/kbn_ebt_tools.mdx index e56f5819e46db..85eb2ad9ed9cd 100644 --- a/api_docs/kbn_ebt_tools.mdx +++ b/api_docs/kbn_ebt_tools.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ebt-tools title: "@kbn/ebt-tools" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ebt-tools plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ebt-tools'] --- import kbnEbtToolsObj from './kbn_ebt_tools.devdocs.json'; diff --git a/api_docs/kbn_ecs.mdx b/api_docs/kbn_ecs.mdx index 3f8f8665ef01a..6c70664de6ac0 100644 --- a/api_docs/kbn_ecs.mdx +++ b/api_docs/kbn_ecs.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ecs title: "@kbn/ecs" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ecs plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ecs'] --- import kbnEcsObj from './kbn_ecs.devdocs.json'; diff --git a/api_docs/kbn_ecs_data_quality_dashboard.mdx b/api_docs/kbn_ecs_data_quality_dashboard.mdx index f2eb3ede11ef0..546dacc74bfa7 100644 --- a/api_docs/kbn_ecs_data_quality_dashboard.mdx +++ b/api_docs/kbn_ecs_data_quality_dashboard.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ecs-data-quality-dashboard title: "@kbn/ecs-data-quality-dashboard" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ecs-data-quality-dashboard plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ecs-data-quality-dashboard'] --- import kbnEcsDataQualityDashboardObj from './kbn_ecs_data_quality_dashboard.devdocs.json'; diff --git a/api_docs/kbn_elastic_agent_utils.mdx b/api_docs/kbn_elastic_agent_utils.mdx index fc7cc18aaa48a..43e1281f60835 100644 --- a/api_docs/kbn_elastic_agent_utils.mdx +++ b/api_docs/kbn_elastic_agent_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-elastic-agent-utils title: "@kbn/elastic-agent-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/elastic-agent-utils plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/elastic-agent-utils'] --- import kbnElasticAgentUtilsObj from './kbn_elastic_agent_utils.devdocs.json'; diff --git a/api_docs/kbn_elastic_assistant.mdx b/api_docs/kbn_elastic_assistant.mdx index 41072a391e1a3..07f608f1ff4f7 100644 --- a/api_docs/kbn_elastic_assistant.mdx +++ b/api_docs/kbn_elastic_assistant.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-elastic-assistant title: "@kbn/elastic-assistant" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/elastic-assistant plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/elastic-assistant'] --- import kbnElasticAssistantObj from './kbn_elastic_assistant.devdocs.json'; diff --git a/api_docs/kbn_elastic_assistant_common.mdx b/api_docs/kbn_elastic_assistant_common.mdx index 05040f2f97f8a..1e5f5b2f9a008 100644 --- a/api_docs/kbn_elastic_assistant_common.mdx +++ b/api_docs/kbn_elastic_assistant_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-elastic-assistant-common title: "@kbn/elastic-assistant-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/elastic-assistant-common plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/elastic-assistant-common'] --- import kbnElasticAssistantCommonObj from './kbn_elastic_assistant_common.devdocs.json'; diff --git a/api_docs/kbn_es.mdx b/api_docs/kbn_es.mdx index 5972c0aa65868..680d12f6e88fb 100644 --- a/api_docs/kbn_es.mdx +++ b/api_docs/kbn_es.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-es title: "@kbn/es" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/es plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/es'] --- import kbnEsObj from './kbn_es.devdocs.json'; diff --git a/api_docs/kbn_es_archiver.mdx b/api_docs/kbn_es_archiver.mdx index 54c5cf333d78a..faeff557e4933 100644 --- a/api_docs/kbn_es_archiver.mdx +++ b/api_docs/kbn_es_archiver.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-es-archiver title: "@kbn/es-archiver" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/es-archiver plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/es-archiver'] --- import kbnEsArchiverObj from './kbn_es_archiver.devdocs.json'; diff --git a/api_docs/kbn_es_errors.mdx b/api_docs/kbn_es_errors.mdx index f22593bd865d8..00ea5ddd1e822 100644 --- a/api_docs/kbn_es_errors.mdx +++ b/api_docs/kbn_es_errors.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-es-errors title: "@kbn/es-errors" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/es-errors plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/es-errors'] --- import kbnEsErrorsObj from './kbn_es_errors.devdocs.json'; diff --git a/api_docs/kbn_es_query.mdx b/api_docs/kbn_es_query.mdx index 8e3d535e674c1..e81ab41adf93b 100644 --- a/api_docs/kbn_es_query.mdx +++ b/api_docs/kbn_es_query.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-es-query title: "@kbn/es-query" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/es-query plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/es-query'] --- import kbnEsQueryObj from './kbn_es_query.devdocs.json'; diff --git a/api_docs/kbn_es_types.mdx b/api_docs/kbn_es_types.mdx index b36caa441cf86..65f844c39bc65 100644 --- a/api_docs/kbn_es_types.mdx +++ b/api_docs/kbn_es_types.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-es-types title: "@kbn/es-types" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/es-types plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/es-types'] --- import kbnEsTypesObj from './kbn_es_types.devdocs.json'; diff --git a/api_docs/kbn_eslint_plugin_imports.mdx b/api_docs/kbn_eslint_plugin_imports.mdx index 17015040454a6..c5079d6820d23 100644 --- a/api_docs/kbn_eslint_plugin_imports.mdx +++ b/api_docs/kbn_eslint_plugin_imports.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-eslint-plugin-imports title: "@kbn/eslint-plugin-imports" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/eslint-plugin-imports plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/eslint-plugin-imports'] --- import kbnEslintPluginImportsObj from './kbn_eslint_plugin_imports.devdocs.json'; diff --git a/api_docs/kbn_esql_utils.mdx b/api_docs/kbn_esql_utils.mdx index 0b7968f0785f7..f8c534466903c 100644 --- a/api_docs/kbn_esql_utils.mdx +++ b/api_docs/kbn_esql_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-esql-utils title: "@kbn/esql-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/esql-utils plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/esql-utils'] --- import kbnEsqlUtilsObj from './kbn_esql_utils.devdocs.json'; diff --git a/api_docs/kbn_event_annotation_common.mdx b/api_docs/kbn_event_annotation_common.mdx index 9ef35aa08e8e1..e6adab520e0fb 100644 --- a/api_docs/kbn_event_annotation_common.mdx +++ b/api_docs/kbn_event_annotation_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-event-annotation-common title: "@kbn/event-annotation-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/event-annotation-common plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/event-annotation-common'] --- import kbnEventAnnotationCommonObj from './kbn_event_annotation_common.devdocs.json'; diff --git a/api_docs/kbn_event_annotation_components.mdx b/api_docs/kbn_event_annotation_components.mdx index a0c07420b8d4e..b4ba97b61b1ea 100644 --- a/api_docs/kbn_event_annotation_components.mdx +++ b/api_docs/kbn_event_annotation_components.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-event-annotation-components title: "@kbn/event-annotation-components" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/event-annotation-components plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/event-annotation-components'] --- import kbnEventAnnotationComponentsObj from './kbn_event_annotation_components.devdocs.json'; diff --git a/api_docs/kbn_expandable_flyout.mdx b/api_docs/kbn_expandable_flyout.mdx index a4dfde3636829..95c898c460551 100644 --- a/api_docs/kbn_expandable_flyout.mdx +++ b/api_docs/kbn_expandable_flyout.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-expandable-flyout title: "@kbn/expandable-flyout" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/expandable-flyout plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/expandable-flyout'] --- import kbnExpandableFlyoutObj from './kbn_expandable_flyout.devdocs.json'; diff --git a/api_docs/kbn_field_types.mdx b/api_docs/kbn_field_types.mdx index 79259e481d930..ffdd1c2eee244 100644 --- a/api_docs/kbn_field_types.mdx +++ b/api_docs/kbn_field_types.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-field-types title: "@kbn/field-types" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/field-types plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/field-types'] --- import kbnFieldTypesObj from './kbn_field_types.devdocs.json'; diff --git a/api_docs/kbn_field_utils.mdx b/api_docs/kbn_field_utils.mdx index bfe87a8a3e5ae..4c61043c79e1b 100644 --- a/api_docs/kbn_field_utils.mdx +++ b/api_docs/kbn_field_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-field-utils title: "@kbn/field-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/field-utils plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/field-utils'] --- import kbnFieldUtilsObj from './kbn_field_utils.devdocs.json'; diff --git a/api_docs/kbn_find_used_node_modules.mdx b/api_docs/kbn_find_used_node_modules.mdx index fa2d9ac63eb67..5bb3edaa74760 100644 --- a/api_docs/kbn_find_used_node_modules.mdx +++ b/api_docs/kbn_find_used_node_modules.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-find-used-node-modules title: "@kbn/find-used-node-modules" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/find-used-node-modules plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/find-used-node-modules'] --- import kbnFindUsedNodeModulesObj from './kbn_find_used_node_modules.devdocs.json'; diff --git a/api_docs/kbn_ftr_common_functional_services.mdx b/api_docs/kbn_ftr_common_functional_services.mdx index abca79fc8a910..68b3238b949f9 100644 --- a/api_docs/kbn_ftr_common_functional_services.mdx +++ b/api_docs/kbn_ftr_common_functional_services.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ftr-common-functional-services title: "@kbn/ftr-common-functional-services" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ftr-common-functional-services plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ftr-common-functional-services'] --- import kbnFtrCommonFunctionalServicesObj from './kbn_ftr_common_functional_services.devdocs.json'; diff --git a/api_docs/kbn_ftr_common_functional_ui_services.mdx b/api_docs/kbn_ftr_common_functional_ui_services.mdx index 15277953ff5a2..a971062c6f28f 100644 --- a/api_docs/kbn_ftr_common_functional_ui_services.mdx +++ b/api_docs/kbn_ftr_common_functional_ui_services.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ftr-common-functional-ui-services title: "@kbn/ftr-common-functional-ui-services" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ftr-common-functional-ui-services plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ftr-common-functional-ui-services'] --- import kbnFtrCommonFunctionalUiServicesObj from './kbn_ftr_common_functional_ui_services.devdocs.json'; diff --git a/api_docs/kbn_generate.mdx b/api_docs/kbn_generate.mdx index 736dcd4a2ffd4..25840d540a1ee 100644 --- a/api_docs/kbn_generate.mdx +++ b/api_docs/kbn_generate.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-generate title: "@kbn/generate" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/generate plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/generate'] --- import kbnGenerateObj from './kbn_generate.devdocs.json'; diff --git a/api_docs/kbn_generate_console_definitions.mdx b/api_docs/kbn_generate_console_definitions.mdx index c349123e33160..067789cd20130 100644 --- a/api_docs/kbn_generate_console_definitions.mdx +++ b/api_docs/kbn_generate_console_definitions.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-generate-console-definitions title: "@kbn/generate-console-definitions" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/generate-console-definitions plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/generate-console-definitions'] --- import kbnGenerateConsoleDefinitionsObj from './kbn_generate_console_definitions.devdocs.json'; diff --git a/api_docs/kbn_generate_csv.mdx b/api_docs/kbn_generate_csv.mdx index 918742d6cae28..3ed108bb21150 100644 --- a/api_docs/kbn_generate_csv.mdx +++ b/api_docs/kbn_generate_csv.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-generate-csv title: "@kbn/generate-csv" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/generate-csv plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/generate-csv'] --- import kbnGenerateCsvObj from './kbn_generate_csv.devdocs.json'; diff --git a/api_docs/kbn_guided_onboarding.mdx b/api_docs/kbn_guided_onboarding.mdx index c2386a192dd37..eb968a6dda74d 100644 --- a/api_docs/kbn_guided_onboarding.mdx +++ b/api_docs/kbn_guided_onboarding.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-guided-onboarding title: "@kbn/guided-onboarding" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/guided-onboarding plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/guided-onboarding'] --- import kbnGuidedOnboardingObj from './kbn_guided_onboarding.devdocs.json'; diff --git a/api_docs/kbn_handlebars.mdx b/api_docs/kbn_handlebars.mdx index b791471aeeac0..769d3de7ec481 100644 --- a/api_docs/kbn_handlebars.mdx +++ b/api_docs/kbn_handlebars.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-handlebars title: "@kbn/handlebars" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/handlebars plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/handlebars'] --- import kbnHandlebarsObj from './kbn_handlebars.devdocs.json'; diff --git a/api_docs/kbn_hapi_mocks.mdx b/api_docs/kbn_hapi_mocks.mdx index 02b88c5fc048f..cdbee7152e97e 100644 --- a/api_docs/kbn_hapi_mocks.mdx +++ b/api_docs/kbn_hapi_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-hapi-mocks title: "@kbn/hapi-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/hapi-mocks plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/hapi-mocks'] --- import kbnHapiMocksObj from './kbn_hapi_mocks.devdocs.json'; diff --git a/api_docs/kbn_health_gateway_server.mdx b/api_docs/kbn_health_gateway_server.mdx index cecbfd6ab8f4b..e3d3c87eafd6f 100644 --- a/api_docs/kbn_health_gateway_server.mdx +++ b/api_docs/kbn_health_gateway_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-health-gateway-server title: "@kbn/health-gateway-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/health-gateway-server plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/health-gateway-server'] --- import kbnHealthGatewayServerObj from './kbn_health_gateway_server.devdocs.json'; diff --git a/api_docs/kbn_home_sample_data_card.mdx b/api_docs/kbn_home_sample_data_card.mdx index 9a508927f1072..91da927340ec3 100644 --- a/api_docs/kbn_home_sample_data_card.mdx +++ b/api_docs/kbn_home_sample_data_card.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-home-sample-data-card title: "@kbn/home-sample-data-card" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/home-sample-data-card plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/home-sample-data-card'] --- import kbnHomeSampleDataCardObj from './kbn_home_sample_data_card.devdocs.json'; diff --git a/api_docs/kbn_home_sample_data_tab.mdx b/api_docs/kbn_home_sample_data_tab.mdx index 125f818f07284..55fd56a43428d 100644 --- a/api_docs/kbn_home_sample_data_tab.mdx +++ b/api_docs/kbn_home_sample_data_tab.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-home-sample-data-tab title: "@kbn/home-sample-data-tab" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/home-sample-data-tab plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/home-sample-data-tab'] --- import kbnHomeSampleDataTabObj from './kbn_home_sample_data_tab.devdocs.json'; diff --git a/api_docs/kbn_i18n.mdx b/api_docs/kbn_i18n.mdx index c8fbfe0ceb1e4..11a7f5e0cd41f 100644 --- a/api_docs/kbn_i18n.mdx +++ b/api_docs/kbn_i18n.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-i18n title: "@kbn/i18n" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/i18n plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/i18n'] --- import kbnI18nObj from './kbn_i18n.devdocs.json'; diff --git a/api_docs/kbn_i18n_react.mdx b/api_docs/kbn_i18n_react.mdx index 79ee8597bffbd..4ee91bba7ae6a 100644 --- a/api_docs/kbn_i18n_react.mdx +++ b/api_docs/kbn_i18n_react.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-i18n-react title: "@kbn/i18n-react" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/i18n-react plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/i18n-react'] --- import kbnI18nReactObj from './kbn_i18n_react.devdocs.json'; diff --git a/api_docs/kbn_import_resolver.mdx b/api_docs/kbn_import_resolver.mdx index c1e8a9c336e6c..cf0cf16d8bc16 100644 --- a/api_docs/kbn_import_resolver.mdx +++ b/api_docs/kbn_import_resolver.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-import-resolver title: "@kbn/import-resolver" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/import-resolver plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/import-resolver'] --- import kbnImportResolverObj from './kbn_import_resolver.devdocs.json'; diff --git a/api_docs/kbn_infra_forge.mdx b/api_docs/kbn_infra_forge.mdx index 8eb8dc56edc8a..285ca56b4e56a 100644 --- a/api_docs/kbn_infra_forge.mdx +++ b/api_docs/kbn_infra_forge.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-infra-forge title: "@kbn/infra-forge" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/infra-forge plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/infra-forge'] --- import kbnInfraForgeObj from './kbn_infra_forge.devdocs.json'; diff --git a/api_docs/kbn_interpreter.mdx b/api_docs/kbn_interpreter.mdx index 3206c2c15f096..cde224ab8d130 100644 --- a/api_docs/kbn_interpreter.mdx +++ b/api_docs/kbn_interpreter.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-interpreter title: "@kbn/interpreter" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/interpreter plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/interpreter'] --- import kbnInterpreterObj from './kbn_interpreter.devdocs.json'; diff --git a/api_docs/kbn_io_ts_utils.mdx b/api_docs/kbn_io_ts_utils.mdx index fabe840fabc9b..b8a2ad925422a 100644 --- a/api_docs/kbn_io_ts_utils.mdx +++ b/api_docs/kbn_io_ts_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-io-ts-utils title: "@kbn/io-ts-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/io-ts-utils plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/io-ts-utils'] --- import kbnIoTsUtilsObj from './kbn_io_ts_utils.devdocs.json'; diff --git a/api_docs/kbn_jest_serializers.mdx b/api_docs/kbn_jest_serializers.mdx index feecc920ff2ad..a3961759fbc74 100644 --- a/api_docs/kbn_jest_serializers.mdx +++ b/api_docs/kbn_jest_serializers.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-jest-serializers title: "@kbn/jest-serializers" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/jest-serializers plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/jest-serializers'] --- import kbnJestSerializersObj from './kbn_jest_serializers.devdocs.json'; diff --git a/api_docs/kbn_journeys.mdx b/api_docs/kbn_journeys.mdx index c7b1afe879fbd..8b42e16af4881 100644 --- a/api_docs/kbn_journeys.mdx +++ b/api_docs/kbn_journeys.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-journeys title: "@kbn/journeys" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/journeys plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/journeys'] --- import kbnJourneysObj from './kbn_journeys.devdocs.json'; diff --git a/api_docs/kbn_json_ast.mdx b/api_docs/kbn_json_ast.mdx index 83051d13e0eb6..226fa3127070c 100644 --- a/api_docs/kbn_json_ast.mdx +++ b/api_docs/kbn_json_ast.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-json-ast title: "@kbn/json-ast" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/json-ast plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/json-ast'] --- import kbnJsonAstObj from './kbn_json_ast.devdocs.json'; diff --git a/api_docs/kbn_kibana_manifest_schema.mdx b/api_docs/kbn_kibana_manifest_schema.mdx index 5a6c3d0256d16..94b14c1125191 100644 --- a/api_docs/kbn_kibana_manifest_schema.mdx +++ b/api_docs/kbn_kibana_manifest_schema.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-kibana-manifest-schema title: "@kbn/kibana-manifest-schema" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/kibana-manifest-schema plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/kibana-manifest-schema'] --- import kbnKibanaManifestSchemaObj from './kbn_kibana_manifest_schema.devdocs.json'; diff --git a/api_docs/kbn_language_documentation_popover.mdx b/api_docs/kbn_language_documentation_popover.mdx index 0636f3f54f70d..ce373a971ea2c 100644 --- a/api_docs/kbn_language_documentation_popover.mdx +++ b/api_docs/kbn_language_documentation_popover.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-language-documentation-popover title: "@kbn/language-documentation-popover" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/language-documentation-popover plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/language-documentation-popover'] --- import kbnLanguageDocumentationPopoverObj from './kbn_language_documentation_popover.devdocs.json'; diff --git a/api_docs/kbn_lens_embeddable_utils.mdx b/api_docs/kbn_lens_embeddable_utils.mdx index 00d689be3fd9d..2a047d1e1085e 100644 --- a/api_docs/kbn_lens_embeddable_utils.mdx +++ b/api_docs/kbn_lens_embeddable_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-lens-embeddable-utils title: "@kbn/lens-embeddable-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/lens-embeddable-utils plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/lens-embeddable-utils'] --- import kbnLensEmbeddableUtilsObj from './kbn_lens_embeddable_utils.devdocs.json'; diff --git a/api_docs/kbn_lens_formula_docs.mdx b/api_docs/kbn_lens_formula_docs.mdx index 1289720093e92..37eb313db3619 100644 --- a/api_docs/kbn_lens_formula_docs.mdx +++ b/api_docs/kbn_lens_formula_docs.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-lens-formula-docs title: "@kbn/lens-formula-docs" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/lens-formula-docs plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/lens-formula-docs'] --- import kbnLensFormulaDocsObj from './kbn_lens_formula_docs.devdocs.json'; diff --git a/api_docs/kbn_logging.mdx b/api_docs/kbn_logging.mdx index 6c6e7d803836f..4d8b5924203c9 100644 --- a/api_docs/kbn_logging.mdx +++ b/api_docs/kbn_logging.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-logging title: "@kbn/logging" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/logging plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/logging'] --- import kbnLoggingObj from './kbn_logging.devdocs.json'; diff --git a/api_docs/kbn_logging_mocks.mdx b/api_docs/kbn_logging_mocks.mdx index 121bc89a25ed3..fded721e2c479 100644 --- a/api_docs/kbn_logging_mocks.mdx +++ b/api_docs/kbn_logging_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-logging-mocks title: "@kbn/logging-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/logging-mocks plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/logging-mocks'] --- import kbnLoggingMocksObj from './kbn_logging_mocks.devdocs.json'; diff --git a/api_docs/kbn_managed_content_badge.mdx b/api_docs/kbn_managed_content_badge.mdx index eba0cd2bf2e3b..20812333a0b2f 100644 --- a/api_docs/kbn_managed_content_badge.mdx +++ b/api_docs/kbn_managed_content_badge.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-managed-content-badge title: "@kbn/managed-content-badge" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/managed-content-badge plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/managed-content-badge'] --- import kbnManagedContentBadgeObj from './kbn_managed_content_badge.devdocs.json'; diff --git a/api_docs/kbn_managed_vscode_config.mdx b/api_docs/kbn_managed_vscode_config.mdx index 1b6e3531d7966..ed35ad7c7aa00 100644 --- a/api_docs/kbn_managed_vscode_config.mdx +++ b/api_docs/kbn_managed_vscode_config.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-managed-vscode-config title: "@kbn/managed-vscode-config" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/managed-vscode-config plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/managed-vscode-config'] --- import kbnManagedVscodeConfigObj from './kbn_managed_vscode_config.devdocs.json'; diff --git a/api_docs/kbn_management_cards_navigation.mdx b/api_docs/kbn_management_cards_navigation.mdx index 5439383390979..23ef71e5d7c8a 100644 --- a/api_docs/kbn_management_cards_navigation.mdx +++ b/api_docs/kbn_management_cards_navigation.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-management-cards-navigation title: "@kbn/management-cards-navigation" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/management-cards-navigation plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/management-cards-navigation'] --- import kbnManagementCardsNavigationObj from './kbn_management_cards_navigation.devdocs.json'; diff --git a/api_docs/kbn_management_settings_application.mdx b/api_docs/kbn_management_settings_application.mdx index 6a753d92cdc0f..13c8cb096e121 100644 --- a/api_docs/kbn_management_settings_application.mdx +++ b/api_docs/kbn_management_settings_application.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-management-settings-application title: "@kbn/management-settings-application" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/management-settings-application plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/management-settings-application'] --- import kbnManagementSettingsApplicationObj from './kbn_management_settings_application.devdocs.json'; diff --git a/api_docs/kbn_management_settings_components_field_category.mdx b/api_docs/kbn_management_settings_components_field_category.mdx index af1924608e905..5abc7b1e9ba4e 100644 --- a/api_docs/kbn_management_settings_components_field_category.mdx +++ b/api_docs/kbn_management_settings_components_field_category.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-management-settings-components-field-category title: "@kbn/management-settings-components-field-category" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/management-settings-components-field-category plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/management-settings-components-field-category'] --- import kbnManagementSettingsComponentsFieldCategoryObj from './kbn_management_settings_components_field_category.devdocs.json'; diff --git a/api_docs/kbn_management_settings_components_field_input.mdx b/api_docs/kbn_management_settings_components_field_input.mdx index c1a30b95a2b65..e7cdcb6ad5af8 100644 --- a/api_docs/kbn_management_settings_components_field_input.mdx +++ b/api_docs/kbn_management_settings_components_field_input.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-management-settings-components-field-input title: "@kbn/management-settings-components-field-input" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/management-settings-components-field-input plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/management-settings-components-field-input'] --- import kbnManagementSettingsComponentsFieldInputObj from './kbn_management_settings_components_field_input.devdocs.json'; diff --git a/api_docs/kbn_management_settings_components_field_row.mdx b/api_docs/kbn_management_settings_components_field_row.mdx index 5fe012055d107..32826ec001c16 100644 --- a/api_docs/kbn_management_settings_components_field_row.mdx +++ b/api_docs/kbn_management_settings_components_field_row.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-management-settings-components-field-row title: "@kbn/management-settings-components-field-row" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/management-settings-components-field-row plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/management-settings-components-field-row'] --- import kbnManagementSettingsComponentsFieldRowObj from './kbn_management_settings_components_field_row.devdocs.json'; diff --git a/api_docs/kbn_management_settings_components_form.mdx b/api_docs/kbn_management_settings_components_form.mdx index ededa0b8d4f81..a0f1353e375fb 100644 --- a/api_docs/kbn_management_settings_components_form.mdx +++ b/api_docs/kbn_management_settings_components_form.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-management-settings-components-form title: "@kbn/management-settings-components-form" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/management-settings-components-form plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/management-settings-components-form'] --- import kbnManagementSettingsComponentsFormObj from './kbn_management_settings_components_form.devdocs.json'; diff --git a/api_docs/kbn_management_settings_field_definition.mdx b/api_docs/kbn_management_settings_field_definition.mdx index f531b50a2e8fa..27ccbe4f6ee55 100644 --- a/api_docs/kbn_management_settings_field_definition.mdx +++ b/api_docs/kbn_management_settings_field_definition.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-management-settings-field-definition title: "@kbn/management-settings-field-definition" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/management-settings-field-definition plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/management-settings-field-definition'] --- import kbnManagementSettingsFieldDefinitionObj from './kbn_management_settings_field_definition.devdocs.json'; diff --git a/api_docs/kbn_management_settings_ids.mdx b/api_docs/kbn_management_settings_ids.mdx index a1bd790ad3991..f65fc3c2d73a8 100644 --- a/api_docs/kbn_management_settings_ids.mdx +++ b/api_docs/kbn_management_settings_ids.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-management-settings-ids title: "@kbn/management-settings-ids" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/management-settings-ids plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/management-settings-ids'] --- import kbnManagementSettingsIdsObj from './kbn_management_settings_ids.devdocs.json'; diff --git a/api_docs/kbn_management_settings_section_registry.mdx b/api_docs/kbn_management_settings_section_registry.mdx index dc687e7fecb1a..a5532edb0664e 100644 --- a/api_docs/kbn_management_settings_section_registry.mdx +++ b/api_docs/kbn_management_settings_section_registry.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-management-settings-section-registry title: "@kbn/management-settings-section-registry" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/management-settings-section-registry plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/management-settings-section-registry'] --- import kbnManagementSettingsSectionRegistryObj from './kbn_management_settings_section_registry.devdocs.json'; diff --git a/api_docs/kbn_management_settings_types.mdx b/api_docs/kbn_management_settings_types.mdx index a8b370d45644f..787bb30361a46 100644 --- a/api_docs/kbn_management_settings_types.mdx +++ b/api_docs/kbn_management_settings_types.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-management-settings-types title: "@kbn/management-settings-types" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/management-settings-types plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/management-settings-types'] --- import kbnManagementSettingsTypesObj from './kbn_management_settings_types.devdocs.json'; diff --git a/api_docs/kbn_management_settings_utilities.mdx b/api_docs/kbn_management_settings_utilities.mdx index e634dc9424275..3846938d2c6a5 100644 --- a/api_docs/kbn_management_settings_utilities.mdx +++ b/api_docs/kbn_management_settings_utilities.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-management-settings-utilities title: "@kbn/management-settings-utilities" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/management-settings-utilities plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/management-settings-utilities'] --- import kbnManagementSettingsUtilitiesObj from './kbn_management_settings_utilities.devdocs.json'; diff --git a/api_docs/kbn_management_storybook_config.mdx b/api_docs/kbn_management_storybook_config.mdx index 32135386ab307..99cb21a3847e0 100644 --- a/api_docs/kbn_management_storybook_config.mdx +++ b/api_docs/kbn_management_storybook_config.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-management-storybook-config title: "@kbn/management-storybook-config" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/management-storybook-config plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/management-storybook-config'] --- import kbnManagementStorybookConfigObj from './kbn_management_storybook_config.devdocs.json'; diff --git a/api_docs/kbn_mapbox_gl.mdx b/api_docs/kbn_mapbox_gl.mdx index 0cfd7a39fae79..b28977e3d5b13 100644 --- a/api_docs/kbn_mapbox_gl.mdx +++ b/api_docs/kbn_mapbox_gl.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-mapbox-gl title: "@kbn/mapbox-gl" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/mapbox-gl plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/mapbox-gl'] --- import kbnMapboxGlObj from './kbn_mapbox_gl.devdocs.json'; diff --git a/api_docs/kbn_maps_vector_tile_utils.mdx b/api_docs/kbn_maps_vector_tile_utils.mdx index 09b23cedfb2ef..03aea1fdde156 100644 --- a/api_docs/kbn_maps_vector_tile_utils.mdx +++ b/api_docs/kbn_maps_vector_tile_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-maps-vector-tile-utils title: "@kbn/maps-vector-tile-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/maps-vector-tile-utils plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/maps-vector-tile-utils'] --- import kbnMapsVectorTileUtilsObj from './kbn_maps_vector_tile_utils.devdocs.json'; diff --git a/api_docs/kbn_ml_agg_utils.mdx b/api_docs/kbn_ml_agg_utils.mdx index 024bcc3e08a5d..6f2de58a000cd 100644 --- a/api_docs/kbn_ml_agg_utils.mdx +++ b/api_docs/kbn_ml_agg_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-agg-utils title: "@kbn/ml-agg-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-agg-utils plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-agg-utils'] --- import kbnMlAggUtilsObj from './kbn_ml_agg_utils.devdocs.json'; diff --git a/api_docs/kbn_ml_anomaly_utils.mdx b/api_docs/kbn_ml_anomaly_utils.mdx index 09d339cb8f5bd..1dee9e5408ffc 100644 --- a/api_docs/kbn_ml_anomaly_utils.mdx +++ b/api_docs/kbn_ml_anomaly_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-anomaly-utils title: "@kbn/ml-anomaly-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-anomaly-utils plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-anomaly-utils'] --- import kbnMlAnomalyUtilsObj from './kbn_ml_anomaly_utils.devdocs.json'; diff --git a/api_docs/kbn_ml_cancellable_search.mdx b/api_docs/kbn_ml_cancellable_search.mdx index 63c244e725516..33aa150a9526b 100644 --- a/api_docs/kbn_ml_cancellable_search.mdx +++ b/api_docs/kbn_ml_cancellable_search.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-cancellable-search title: "@kbn/ml-cancellable-search" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-cancellable-search plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-cancellable-search'] --- import kbnMlCancellableSearchObj from './kbn_ml_cancellable_search.devdocs.json'; diff --git a/api_docs/kbn_ml_category_validator.mdx b/api_docs/kbn_ml_category_validator.mdx index 81df9ff61b202..9922ce38cdbc0 100644 --- a/api_docs/kbn_ml_category_validator.mdx +++ b/api_docs/kbn_ml_category_validator.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-category-validator title: "@kbn/ml-category-validator" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-category-validator plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-category-validator'] --- import kbnMlCategoryValidatorObj from './kbn_ml_category_validator.devdocs.json'; diff --git a/api_docs/kbn_ml_chi2test.mdx b/api_docs/kbn_ml_chi2test.mdx index c070a9ddfafdb..487780a1783c3 100644 --- a/api_docs/kbn_ml_chi2test.mdx +++ b/api_docs/kbn_ml_chi2test.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-chi2test title: "@kbn/ml-chi2test" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-chi2test plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-chi2test'] --- import kbnMlChi2testObj from './kbn_ml_chi2test.devdocs.json'; diff --git a/api_docs/kbn_ml_data_frame_analytics_utils.mdx b/api_docs/kbn_ml_data_frame_analytics_utils.mdx index 25708cd70241f..73cca67f04c9e 100644 --- a/api_docs/kbn_ml_data_frame_analytics_utils.mdx +++ b/api_docs/kbn_ml_data_frame_analytics_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-data-frame-analytics-utils title: "@kbn/ml-data-frame-analytics-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-data-frame-analytics-utils plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-data-frame-analytics-utils'] --- import kbnMlDataFrameAnalyticsUtilsObj from './kbn_ml_data_frame_analytics_utils.devdocs.json'; diff --git a/api_docs/kbn_ml_data_grid.mdx b/api_docs/kbn_ml_data_grid.mdx index b82d803bc886b..b8b58b0ae08d1 100644 --- a/api_docs/kbn_ml_data_grid.mdx +++ b/api_docs/kbn_ml_data_grid.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-data-grid title: "@kbn/ml-data-grid" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-data-grid plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-data-grid'] --- import kbnMlDataGridObj from './kbn_ml_data_grid.devdocs.json'; diff --git a/api_docs/kbn_ml_date_picker.mdx b/api_docs/kbn_ml_date_picker.mdx index 390bc75ca65fd..a19cdf0e4d1c0 100644 --- a/api_docs/kbn_ml_date_picker.mdx +++ b/api_docs/kbn_ml_date_picker.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-date-picker title: "@kbn/ml-date-picker" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-date-picker plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-date-picker'] --- import kbnMlDatePickerObj from './kbn_ml_date_picker.devdocs.json'; diff --git a/api_docs/kbn_ml_date_utils.mdx b/api_docs/kbn_ml_date_utils.mdx index 5381d686b3ee3..e7d8bc013f3f1 100644 --- a/api_docs/kbn_ml_date_utils.mdx +++ b/api_docs/kbn_ml_date_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-date-utils title: "@kbn/ml-date-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-date-utils plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-date-utils'] --- import kbnMlDateUtilsObj from './kbn_ml_date_utils.devdocs.json'; diff --git a/api_docs/kbn_ml_error_utils.mdx b/api_docs/kbn_ml_error_utils.mdx index 42b98a2473edf..fd9c01be18796 100644 --- a/api_docs/kbn_ml_error_utils.mdx +++ b/api_docs/kbn_ml_error_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-error-utils title: "@kbn/ml-error-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-error-utils plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-error-utils'] --- import kbnMlErrorUtilsObj from './kbn_ml_error_utils.devdocs.json'; diff --git a/api_docs/kbn_ml_in_memory_table.mdx b/api_docs/kbn_ml_in_memory_table.mdx index 182db925e7a28..eba797de93842 100644 --- a/api_docs/kbn_ml_in_memory_table.mdx +++ b/api_docs/kbn_ml_in_memory_table.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-in-memory-table title: "@kbn/ml-in-memory-table" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-in-memory-table plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-in-memory-table'] --- import kbnMlInMemoryTableObj from './kbn_ml_in_memory_table.devdocs.json'; diff --git a/api_docs/kbn_ml_is_defined.mdx b/api_docs/kbn_ml_is_defined.mdx index c0154ed3d52fa..b31adf066845c 100644 --- a/api_docs/kbn_ml_is_defined.mdx +++ b/api_docs/kbn_ml_is_defined.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-is-defined title: "@kbn/ml-is-defined" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-is-defined plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-is-defined'] --- import kbnMlIsDefinedObj from './kbn_ml_is_defined.devdocs.json'; diff --git a/api_docs/kbn_ml_is_populated_object.mdx b/api_docs/kbn_ml_is_populated_object.mdx index 115b9bdb6c013..51b683bb83841 100644 --- a/api_docs/kbn_ml_is_populated_object.mdx +++ b/api_docs/kbn_ml_is_populated_object.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-is-populated-object title: "@kbn/ml-is-populated-object" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-is-populated-object plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-is-populated-object'] --- import kbnMlIsPopulatedObjectObj from './kbn_ml_is_populated_object.devdocs.json'; diff --git a/api_docs/kbn_ml_kibana_theme.mdx b/api_docs/kbn_ml_kibana_theme.mdx index cda8adfbc5e98..c36a5e2cacadc 100644 --- a/api_docs/kbn_ml_kibana_theme.mdx +++ b/api_docs/kbn_ml_kibana_theme.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-kibana-theme title: "@kbn/ml-kibana-theme" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-kibana-theme plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-kibana-theme'] --- import kbnMlKibanaThemeObj from './kbn_ml_kibana_theme.devdocs.json'; diff --git a/api_docs/kbn_ml_local_storage.mdx b/api_docs/kbn_ml_local_storage.mdx index 9f1b28c3f150f..19b6d3f421998 100644 --- a/api_docs/kbn_ml_local_storage.mdx +++ b/api_docs/kbn_ml_local_storage.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-local-storage title: "@kbn/ml-local-storage" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-local-storage plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-local-storage'] --- import kbnMlLocalStorageObj from './kbn_ml_local_storage.devdocs.json'; diff --git a/api_docs/kbn_ml_nested_property.mdx b/api_docs/kbn_ml_nested_property.mdx index 1a2268e5c17b2..518cb5e9bc484 100644 --- a/api_docs/kbn_ml_nested_property.mdx +++ b/api_docs/kbn_ml_nested_property.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-nested-property title: "@kbn/ml-nested-property" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-nested-property plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-nested-property'] --- import kbnMlNestedPropertyObj from './kbn_ml_nested_property.devdocs.json'; diff --git a/api_docs/kbn_ml_number_utils.mdx b/api_docs/kbn_ml_number_utils.mdx index 7acabe686f291..54858a255a6b5 100644 --- a/api_docs/kbn_ml_number_utils.mdx +++ b/api_docs/kbn_ml_number_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-number-utils title: "@kbn/ml-number-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-number-utils plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-number-utils'] --- import kbnMlNumberUtilsObj from './kbn_ml_number_utils.devdocs.json'; diff --git a/api_docs/kbn_ml_query_utils.mdx b/api_docs/kbn_ml_query_utils.mdx index 74dab575d13ef..f62073f7695b7 100644 --- a/api_docs/kbn_ml_query_utils.mdx +++ b/api_docs/kbn_ml_query_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-query-utils title: "@kbn/ml-query-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-query-utils plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-query-utils'] --- import kbnMlQueryUtilsObj from './kbn_ml_query_utils.devdocs.json'; diff --git a/api_docs/kbn_ml_random_sampler_utils.mdx b/api_docs/kbn_ml_random_sampler_utils.mdx index 7180f60c68bd1..551d75898c6c1 100644 --- a/api_docs/kbn_ml_random_sampler_utils.mdx +++ b/api_docs/kbn_ml_random_sampler_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-random-sampler-utils title: "@kbn/ml-random-sampler-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-random-sampler-utils plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-random-sampler-utils'] --- import kbnMlRandomSamplerUtilsObj from './kbn_ml_random_sampler_utils.devdocs.json'; diff --git a/api_docs/kbn_ml_route_utils.mdx b/api_docs/kbn_ml_route_utils.mdx index 2f57b90a3ee40..74cb1b09d736b 100644 --- a/api_docs/kbn_ml_route_utils.mdx +++ b/api_docs/kbn_ml_route_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-route-utils title: "@kbn/ml-route-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-route-utils plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-route-utils'] --- import kbnMlRouteUtilsObj from './kbn_ml_route_utils.devdocs.json'; diff --git a/api_docs/kbn_ml_runtime_field_utils.mdx b/api_docs/kbn_ml_runtime_field_utils.mdx index 5b3d1a3fcea95..547a031195561 100644 --- a/api_docs/kbn_ml_runtime_field_utils.mdx +++ b/api_docs/kbn_ml_runtime_field_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-runtime-field-utils title: "@kbn/ml-runtime-field-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-runtime-field-utils plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-runtime-field-utils'] --- import kbnMlRuntimeFieldUtilsObj from './kbn_ml_runtime_field_utils.devdocs.json'; diff --git a/api_docs/kbn_ml_string_hash.mdx b/api_docs/kbn_ml_string_hash.mdx index 10510f74aad3c..1a4e97b03cf24 100644 --- a/api_docs/kbn_ml_string_hash.mdx +++ b/api_docs/kbn_ml_string_hash.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-string-hash title: "@kbn/ml-string-hash" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-string-hash plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-string-hash'] --- import kbnMlStringHashObj from './kbn_ml_string_hash.devdocs.json'; diff --git a/api_docs/kbn_ml_trained_models_utils.mdx b/api_docs/kbn_ml_trained_models_utils.mdx index c71bbcff5c19a..957bc75e39036 100644 --- a/api_docs/kbn_ml_trained_models_utils.mdx +++ b/api_docs/kbn_ml_trained_models_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-trained-models-utils title: "@kbn/ml-trained-models-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-trained-models-utils plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-trained-models-utils'] --- import kbnMlTrainedModelsUtilsObj from './kbn_ml_trained_models_utils.devdocs.json'; diff --git a/api_docs/kbn_ml_ui_actions.mdx b/api_docs/kbn_ml_ui_actions.mdx index e5da48faa1b85..b161b9954fcb9 100644 --- a/api_docs/kbn_ml_ui_actions.mdx +++ b/api_docs/kbn_ml_ui_actions.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-ui-actions title: "@kbn/ml-ui-actions" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-ui-actions plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-ui-actions'] --- import kbnMlUiActionsObj from './kbn_ml_ui_actions.devdocs.json'; diff --git a/api_docs/kbn_ml_url_state.mdx b/api_docs/kbn_ml_url_state.mdx index 1f6a78b0876a9..a30cae118df6e 100644 --- a/api_docs/kbn_ml_url_state.mdx +++ b/api_docs/kbn_ml_url_state.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-url-state title: "@kbn/ml-url-state" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-url-state plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-url-state'] --- import kbnMlUrlStateObj from './kbn_ml_url_state.devdocs.json'; diff --git a/api_docs/kbn_mock_idp_utils.mdx b/api_docs/kbn_mock_idp_utils.mdx index 851a9e7e5955f..4103f63f2315a 100644 --- a/api_docs/kbn_mock_idp_utils.mdx +++ b/api_docs/kbn_mock_idp_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-mock-idp-utils title: "@kbn/mock-idp-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/mock-idp-utils plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/mock-idp-utils'] --- import kbnMockIdpUtilsObj from './kbn_mock_idp_utils.devdocs.json'; diff --git a/api_docs/kbn_monaco.mdx b/api_docs/kbn_monaco.mdx index 50c85ae9a5882..be0a57ae39546 100644 --- a/api_docs/kbn_monaco.mdx +++ b/api_docs/kbn_monaco.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-monaco title: "@kbn/monaco" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/monaco plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/monaco'] --- import kbnMonacoObj from './kbn_monaco.devdocs.json'; diff --git a/api_docs/kbn_object_versioning.mdx b/api_docs/kbn_object_versioning.mdx index 8cc368c96c625..a5f897254af3e 100644 --- a/api_docs/kbn_object_versioning.mdx +++ b/api_docs/kbn_object_versioning.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-object-versioning title: "@kbn/object-versioning" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/object-versioning plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/object-versioning'] --- import kbnObjectVersioningObj from './kbn_object_versioning.devdocs.json'; diff --git a/api_docs/kbn_observability_alert_details.mdx b/api_docs/kbn_observability_alert_details.mdx index 1cadea873962a..b871a69d61e59 100644 --- a/api_docs/kbn_observability_alert_details.mdx +++ b/api_docs/kbn_observability_alert_details.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-observability-alert-details title: "@kbn/observability-alert-details" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/observability-alert-details plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/observability-alert-details'] --- import kbnObservabilityAlertDetailsObj from './kbn_observability_alert_details.devdocs.json'; diff --git a/api_docs/kbn_observability_alerting_test_data.mdx b/api_docs/kbn_observability_alerting_test_data.mdx index 929920b0e1001..d0c4b6067473b 100644 --- a/api_docs/kbn_observability_alerting_test_data.mdx +++ b/api_docs/kbn_observability_alerting_test_data.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-observability-alerting-test-data title: "@kbn/observability-alerting-test-data" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/observability-alerting-test-data plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/observability-alerting-test-data'] --- import kbnObservabilityAlertingTestDataObj from './kbn_observability_alerting_test_data.devdocs.json'; diff --git a/api_docs/kbn_observability_get_padded_alert_time_range_util.mdx b/api_docs/kbn_observability_get_padded_alert_time_range_util.mdx index 60d470582d155..c0c5804c9fd35 100644 --- a/api_docs/kbn_observability_get_padded_alert_time_range_util.mdx +++ b/api_docs/kbn_observability_get_padded_alert_time_range_util.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-observability-get-padded-alert-time-range-util title: "@kbn/observability-get-padded-alert-time-range-util" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/observability-get-padded-alert-time-range-util plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/observability-get-padded-alert-time-range-util'] --- import kbnObservabilityGetPaddedAlertTimeRangeUtilObj from './kbn_observability_get_padded_alert_time_range_util.devdocs.json'; diff --git a/api_docs/kbn_openapi_bundler.mdx b/api_docs/kbn_openapi_bundler.mdx index 1218ef5e047db..59ab6bfaf0919 100644 --- a/api_docs/kbn_openapi_bundler.mdx +++ b/api_docs/kbn_openapi_bundler.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-openapi-bundler title: "@kbn/openapi-bundler" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/openapi-bundler plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/openapi-bundler'] --- import kbnOpenapiBundlerObj from './kbn_openapi_bundler.devdocs.json'; diff --git a/api_docs/kbn_openapi_generator.mdx b/api_docs/kbn_openapi_generator.mdx index 5527690d5538c..69a89e449a603 100644 --- a/api_docs/kbn_openapi_generator.mdx +++ b/api_docs/kbn_openapi_generator.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-openapi-generator title: "@kbn/openapi-generator" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/openapi-generator plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/openapi-generator'] --- import kbnOpenapiGeneratorObj from './kbn_openapi_generator.devdocs.json'; diff --git a/api_docs/kbn_optimizer.mdx b/api_docs/kbn_optimizer.mdx index 64b2e33845000..895a7b69f9002 100644 --- a/api_docs/kbn_optimizer.mdx +++ b/api_docs/kbn_optimizer.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-optimizer title: "@kbn/optimizer" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/optimizer plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/optimizer'] --- import kbnOptimizerObj from './kbn_optimizer.devdocs.json'; diff --git a/api_docs/kbn_optimizer_webpack_helpers.mdx b/api_docs/kbn_optimizer_webpack_helpers.mdx index 9fc4b7a8383c4..0677a598cac6a 100644 --- a/api_docs/kbn_optimizer_webpack_helpers.mdx +++ b/api_docs/kbn_optimizer_webpack_helpers.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-optimizer-webpack-helpers title: "@kbn/optimizer-webpack-helpers" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/optimizer-webpack-helpers plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/optimizer-webpack-helpers'] --- import kbnOptimizerWebpackHelpersObj from './kbn_optimizer_webpack_helpers.devdocs.json'; diff --git a/api_docs/kbn_osquery_io_ts_types.mdx b/api_docs/kbn_osquery_io_ts_types.mdx index 9cd09a3ac3098..a6b13ce685dd8 100644 --- a/api_docs/kbn_osquery_io_ts_types.mdx +++ b/api_docs/kbn_osquery_io_ts_types.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-osquery-io-ts-types title: "@kbn/osquery-io-ts-types" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/osquery-io-ts-types plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/osquery-io-ts-types'] --- import kbnOsqueryIoTsTypesObj from './kbn_osquery_io_ts_types.devdocs.json'; diff --git a/api_docs/kbn_panel_loader.mdx b/api_docs/kbn_panel_loader.mdx index 460876ffc685e..70c488db031f5 100644 --- a/api_docs/kbn_panel_loader.mdx +++ b/api_docs/kbn_panel_loader.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-panel-loader title: "@kbn/panel-loader" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/panel-loader plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/panel-loader'] --- import kbnPanelLoaderObj from './kbn_panel_loader.devdocs.json'; diff --git a/api_docs/kbn_performance_testing_dataset_extractor.mdx b/api_docs/kbn_performance_testing_dataset_extractor.mdx index 68d347af5b34e..572eec2b26e9a 100644 --- a/api_docs/kbn_performance_testing_dataset_extractor.mdx +++ b/api_docs/kbn_performance_testing_dataset_extractor.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-performance-testing-dataset-extractor title: "@kbn/performance-testing-dataset-extractor" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/performance-testing-dataset-extractor plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/performance-testing-dataset-extractor'] --- import kbnPerformanceTestingDatasetExtractorObj from './kbn_performance_testing_dataset_extractor.devdocs.json'; diff --git a/api_docs/kbn_plugin_check.mdx b/api_docs/kbn_plugin_check.mdx index af7a0926de5a0..09670705d66cf 100644 --- a/api_docs/kbn_plugin_check.mdx +++ b/api_docs/kbn_plugin_check.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-plugin-check title: "@kbn/plugin-check" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/plugin-check plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/plugin-check'] --- import kbnPluginCheckObj from './kbn_plugin_check.devdocs.json'; diff --git a/api_docs/kbn_plugin_generator.mdx b/api_docs/kbn_plugin_generator.mdx index 09cca745b3136..9c43e034fe4f5 100644 --- a/api_docs/kbn_plugin_generator.mdx +++ b/api_docs/kbn_plugin_generator.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-plugin-generator title: "@kbn/plugin-generator" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/plugin-generator plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/plugin-generator'] --- import kbnPluginGeneratorObj from './kbn_plugin_generator.devdocs.json'; diff --git a/api_docs/kbn_plugin_helpers.mdx b/api_docs/kbn_plugin_helpers.mdx index b2882d31b5415..a428babc7643d 100644 --- a/api_docs/kbn_plugin_helpers.mdx +++ b/api_docs/kbn_plugin_helpers.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-plugin-helpers title: "@kbn/plugin-helpers" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/plugin-helpers plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/plugin-helpers'] --- import kbnPluginHelpersObj from './kbn_plugin_helpers.devdocs.json'; diff --git a/api_docs/kbn_presentation_containers.devdocs.json b/api_docs/kbn_presentation_containers.devdocs.json index ddae3f2b0761a..aaa60fb53da1e 100644 --- a/api_docs/kbn_presentation_containers.devdocs.json +++ b/api_docs/kbn_presentation_containers.devdocs.json @@ -107,7 +107,7 @@ "label": "apiIsPresentationContainer", "description": [], "signature": [ - "(unknownApi: unknown) => unknownApi is ", + "(api: unknown) => api is ", { "pluginId": "@kbn/presentation-containers", "scope": "common", @@ -125,7 +125,7 @@ "id": "def-common.apiIsPresentationContainer.$1", "type": "Unknown", "tags": [], - "label": "unknownApi", + "label": "api", "description": [], "signature": [ "unknown" @@ -796,11 +796,11 @@ "description": [], "signature": [ { - "pluginId": "@kbn/core-saved-objects-common", + "pluginId": "@kbn/content-management-utils", "scope": "common", - "docId": "kibKbnCoreSavedObjectsCommonPluginApi", - "section": "def-common.SavedObjectReference", - "text": "SavedObjectReference" + "docId": "kibKbnContentManagementUtilsPluginApi", + "section": "def-common.Reference", + "text": "Reference" }, "[] | undefined" ], @@ -973,7 +973,7 @@ "section": "def-common.PanelPackage", "text": "PanelPackage" }, - ") => Promise; }" + ") => Promise; getChildIds: () => string[]; getChild: (childId: string) => unknown; }" ], "path": "packages/presentation/presentation_containers/interfaces/presentation_container.ts", "deprecated": false, diff --git a/api_docs/kbn_presentation_containers.mdx b/api_docs/kbn_presentation_containers.mdx index aed03d13a97a4..6be5e0ee01855 100644 --- a/api_docs/kbn_presentation_containers.mdx +++ b/api_docs/kbn_presentation_containers.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-presentation-containers title: "@kbn/presentation-containers" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/presentation-containers plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/presentation-containers'] --- import kbnPresentationContainersObj from './kbn_presentation_containers.devdocs.json'; diff --git a/api_docs/kbn_presentation_library.mdx b/api_docs/kbn_presentation_library.mdx index 13842133d076d..9a659bc0ce935 100644 --- a/api_docs/kbn_presentation_library.mdx +++ b/api_docs/kbn_presentation_library.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-presentation-library title: "@kbn/presentation-library" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/presentation-library plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/presentation-library'] --- import kbnPresentationLibraryObj from './kbn_presentation_library.devdocs.json'; diff --git a/api_docs/kbn_presentation_publishing.devdocs.json b/api_docs/kbn_presentation_publishing.devdocs.json index 26234c10076d9..d4251a160d21c 100644 --- a/api_docs/kbn_presentation_publishing.devdocs.json +++ b/api_docs/kbn_presentation_publishing.devdocs.json @@ -144,6 +144,46 @@ "returnComment": [], "initialIsOpen": false }, + { + "parentPluginId": "@kbn/presentation-publishing", + "id": "def-common.apiHasSupportedTriggers", + "type": "Function", + "tags": [], + "label": "apiHasSupportedTriggers", + "description": [], + "signature": [ + "(api: unknown) => api is ", + { + "pluginId": "@kbn/presentation-publishing", + "scope": "common", + "docId": "kibKbnPresentationPublishingPluginApi", + "section": "def-common.HasSupportedTriggers", + "text": "HasSupportedTriggers" + } + ], + "path": "packages/presentation/presentation_publishing/interfaces/has_supported_triggers.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/presentation-publishing", + "id": "def-common.apiHasSupportedTriggers.$1", + "type": "Unknown", + "tags": [], + "label": "api", + "description": [], + "signature": [ + "unknown" + ], + "path": "packages/presentation/presentation_publishing/interfaces/has_supported_triggers.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [], + "initialIsOpen": false + }, { "parentPluginId": "@kbn/presentation-publishing", "id": "def-common.apiHasType", @@ -2277,6 +2317,36 @@ ], "initialIsOpen": false }, + { + "parentPluginId": "@kbn/presentation-publishing", + "id": "def-common.HasSupportedTriggers", + "type": "Interface", + "tags": [], + "label": "HasSupportedTriggers", + "description": [], + "path": "packages/presentation/presentation_publishing/interfaces/has_supported_triggers.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/presentation-publishing", + "id": "def-common.HasSupportedTriggers.supportedTriggers", + "type": "Function", + "tags": [], + "label": "supportedTriggers", + "description": [], + "signature": [ + "() => string[]" + ], + "path": "packages/presentation/presentation_publishing/interfaces/has_supported_triggers.ts", + "deprecated": false, + "trackAdoption": false, + "children": [], + "returnComment": [] + } + ], + "initialIsOpen": false + }, { "parentPluginId": "@kbn/presentation-publishing", "id": "def-common.HasType", diff --git a/api_docs/kbn_presentation_publishing.mdx b/api_docs/kbn_presentation_publishing.mdx index 322920cd58b5c..f3626df0446b8 100644 --- a/api_docs/kbn_presentation_publishing.mdx +++ b/api_docs/kbn_presentation_publishing.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-presentation-publishing title: "@kbn/presentation-publishing" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/presentation-publishing plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/presentation-publishing'] --- import kbnPresentationPublishingObj from './kbn_presentation_publishing.devdocs.json'; @@ -21,7 +21,7 @@ Contact [@elastic/kibana-presentation](https://github.com/orgs/elastic/teams/kib | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 150 | 0 | 114 | 3 | +| 154 | 0 | 118 | 3 | ## Common diff --git a/api_docs/kbn_profiling_utils.mdx b/api_docs/kbn_profiling_utils.mdx index 9fd5ffdfd3f07..f89341745c524 100644 --- a/api_docs/kbn_profiling_utils.mdx +++ b/api_docs/kbn_profiling_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-profiling-utils title: "@kbn/profiling-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/profiling-utils plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/profiling-utils'] --- import kbnProfilingUtilsObj from './kbn_profiling_utils.devdocs.json'; diff --git a/api_docs/kbn_random_sampling.mdx b/api_docs/kbn_random_sampling.mdx index 92dc594c19cfb..19afaa661c989 100644 --- a/api_docs/kbn_random_sampling.mdx +++ b/api_docs/kbn_random_sampling.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-random-sampling title: "@kbn/random-sampling" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/random-sampling plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/random-sampling'] --- import kbnRandomSamplingObj from './kbn_random_sampling.devdocs.json'; diff --git a/api_docs/kbn_react_field.mdx b/api_docs/kbn_react_field.mdx index a7903c54fd95f..47946e014202e 100644 --- a/api_docs/kbn_react_field.mdx +++ b/api_docs/kbn_react_field.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-react-field title: "@kbn/react-field" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/react-field plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/react-field'] --- import kbnReactFieldObj from './kbn_react_field.devdocs.json'; diff --git a/api_docs/kbn_react_kibana_context_common.mdx b/api_docs/kbn_react_kibana_context_common.mdx index 9503b3b47006d..75434ee094783 100644 --- a/api_docs/kbn_react_kibana_context_common.mdx +++ b/api_docs/kbn_react_kibana_context_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-react-kibana-context-common title: "@kbn/react-kibana-context-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/react-kibana-context-common plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/react-kibana-context-common'] --- import kbnReactKibanaContextCommonObj from './kbn_react_kibana_context_common.devdocs.json'; diff --git a/api_docs/kbn_react_kibana_context_render.mdx b/api_docs/kbn_react_kibana_context_render.mdx index 279274abebdc0..946efc3e89a55 100644 --- a/api_docs/kbn_react_kibana_context_render.mdx +++ b/api_docs/kbn_react_kibana_context_render.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-react-kibana-context-render title: "@kbn/react-kibana-context-render" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/react-kibana-context-render plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/react-kibana-context-render'] --- import kbnReactKibanaContextRenderObj from './kbn_react_kibana_context_render.devdocs.json'; diff --git a/api_docs/kbn_react_kibana_context_root.mdx b/api_docs/kbn_react_kibana_context_root.mdx index 1bf4846069cfb..6fcb78556c7de 100644 --- a/api_docs/kbn_react_kibana_context_root.mdx +++ b/api_docs/kbn_react_kibana_context_root.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-react-kibana-context-root title: "@kbn/react-kibana-context-root" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/react-kibana-context-root plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/react-kibana-context-root'] --- import kbnReactKibanaContextRootObj from './kbn_react_kibana_context_root.devdocs.json'; diff --git a/api_docs/kbn_react_kibana_context_styled.mdx b/api_docs/kbn_react_kibana_context_styled.mdx index 1de011dfe9497..6f3dfd3ee0954 100644 --- a/api_docs/kbn_react_kibana_context_styled.mdx +++ b/api_docs/kbn_react_kibana_context_styled.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-react-kibana-context-styled title: "@kbn/react-kibana-context-styled" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/react-kibana-context-styled plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/react-kibana-context-styled'] --- import kbnReactKibanaContextStyledObj from './kbn_react_kibana_context_styled.devdocs.json'; diff --git a/api_docs/kbn_react_kibana_context_theme.mdx b/api_docs/kbn_react_kibana_context_theme.mdx index 99922497dc316..13db285b8bb74 100644 --- a/api_docs/kbn_react_kibana_context_theme.mdx +++ b/api_docs/kbn_react_kibana_context_theme.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-react-kibana-context-theme title: "@kbn/react-kibana-context-theme" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/react-kibana-context-theme plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/react-kibana-context-theme'] --- import kbnReactKibanaContextThemeObj from './kbn_react_kibana_context_theme.devdocs.json'; diff --git a/api_docs/kbn_react_kibana_mount.mdx b/api_docs/kbn_react_kibana_mount.mdx index d88c2a3659255..ee0976b88dcde 100644 --- a/api_docs/kbn_react_kibana_mount.mdx +++ b/api_docs/kbn_react_kibana_mount.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-react-kibana-mount title: "@kbn/react-kibana-mount" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/react-kibana-mount plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/react-kibana-mount'] --- import kbnReactKibanaMountObj from './kbn_react_kibana_mount.devdocs.json'; diff --git a/api_docs/kbn_repo_file_maps.mdx b/api_docs/kbn_repo_file_maps.mdx index 00bc6167b8117..6552f13ca9b25 100644 --- a/api_docs/kbn_repo_file_maps.mdx +++ b/api_docs/kbn_repo_file_maps.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-repo-file-maps title: "@kbn/repo-file-maps" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/repo-file-maps plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/repo-file-maps'] --- import kbnRepoFileMapsObj from './kbn_repo_file_maps.devdocs.json'; diff --git a/api_docs/kbn_repo_linter.mdx b/api_docs/kbn_repo_linter.mdx index ae9167497793f..98368b717312d 100644 --- a/api_docs/kbn_repo_linter.mdx +++ b/api_docs/kbn_repo_linter.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-repo-linter title: "@kbn/repo-linter" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/repo-linter plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/repo-linter'] --- import kbnRepoLinterObj from './kbn_repo_linter.devdocs.json'; diff --git a/api_docs/kbn_repo_path.mdx b/api_docs/kbn_repo_path.mdx index 626fa9e958060..b7a039f21b20c 100644 --- a/api_docs/kbn_repo_path.mdx +++ b/api_docs/kbn_repo_path.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-repo-path title: "@kbn/repo-path" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/repo-path plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/repo-path'] --- import kbnRepoPathObj from './kbn_repo_path.devdocs.json'; diff --git a/api_docs/kbn_repo_source_classifier.mdx b/api_docs/kbn_repo_source_classifier.mdx index 151aca6848489..b08a8dac2f82b 100644 --- a/api_docs/kbn_repo_source_classifier.mdx +++ b/api_docs/kbn_repo_source_classifier.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-repo-source-classifier title: "@kbn/repo-source-classifier" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/repo-source-classifier plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/repo-source-classifier'] --- import kbnRepoSourceClassifierObj from './kbn_repo_source_classifier.devdocs.json'; diff --git a/api_docs/kbn_reporting_common.mdx b/api_docs/kbn_reporting_common.mdx index 8dbdfbade9aab..be76302a53f5a 100644 --- a/api_docs/kbn_reporting_common.mdx +++ b/api_docs/kbn_reporting_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-reporting-common title: "@kbn/reporting-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/reporting-common plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/reporting-common'] --- import kbnReportingCommonObj from './kbn_reporting_common.devdocs.json'; diff --git a/api_docs/kbn_reporting_export_types_csv.mdx b/api_docs/kbn_reporting_export_types_csv.mdx index e2449b559d5a5..b92d6e79ff9d5 100644 --- a/api_docs/kbn_reporting_export_types_csv.mdx +++ b/api_docs/kbn_reporting_export_types_csv.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-reporting-export-types-csv title: "@kbn/reporting-export-types-csv" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/reporting-export-types-csv plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/reporting-export-types-csv'] --- import kbnReportingExportTypesCsvObj from './kbn_reporting_export_types_csv.devdocs.json'; diff --git a/api_docs/kbn_reporting_export_types_csv_common.mdx b/api_docs/kbn_reporting_export_types_csv_common.mdx index c501f3350a705..93294e6e4277b 100644 --- a/api_docs/kbn_reporting_export_types_csv_common.mdx +++ b/api_docs/kbn_reporting_export_types_csv_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-reporting-export-types-csv-common title: "@kbn/reporting-export-types-csv-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/reporting-export-types-csv-common plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/reporting-export-types-csv-common'] --- import kbnReportingExportTypesCsvCommonObj from './kbn_reporting_export_types_csv_common.devdocs.json'; diff --git a/api_docs/kbn_reporting_export_types_pdf.mdx b/api_docs/kbn_reporting_export_types_pdf.mdx index b375459d310ef..8c0dce6bb52b0 100644 --- a/api_docs/kbn_reporting_export_types_pdf.mdx +++ b/api_docs/kbn_reporting_export_types_pdf.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-reporting-export-types-pdf title: "@kbn/reporting-export-types-pdf" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/reporting-export-types-pdf plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/reporting-export-types-pdf'] --- import kbnReportingExportTypesPdfObj from './kbn_reporting_export_types_pdf.devdocs.json'; diff --git a/api_docs/kbn_reporting_export_types_pdf_common.mdx b/api_docs/kbn_reporting_export_types_pdf_common.mdx index 0a6538b8fac1d..a604061bd0866 100644 --- a/api_docs/kbn_reporting_export_types_pdf_common.mdx +++ b/api_docs/kbn_reporting_export_types_pdf_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-reporting-export-types-pdf-common title: "@kbn/reporting-export-types-pdf-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/reporting-export-types-pdf-common plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/reporting-export-types-pdf-common'] --- import kbnReportingExportTypesPdfCommonObj from './kbn_reporting_export_types_pdf_common.devdocs.json'; diff --git a/api_docs/kbn_reporting_export_types_png.mdx b/api_docs/kbn_reporting_export_types_png.mdx index 5c68d20fa73f7..d7af5db0499a9 100644 --- a/api_docs/kbn_reporting_export_types_png.mdx +++ b/api_docs/kbn_reporting_export_types_png.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-reporting-export-types-png title: "@kbn/reporting-export-types-png" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/reporting-export-types-png plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/reporting-export-types-png'] --- import kbnReportingExportTypesPngObj from './kbn_reporting_export_types_png.devdocs.json'; diff --git a/api_docs/kbn_reporting_export_types_png_common.mdx b/api_docs/kbn_reporting_export_types_png_common.mdx index 36587f1404b35..5ef95d75bf189 100644 --- a/api_docs/kbn_reporting_export_types_png_common.mdx +++ b/api_docs/kbn_reporting_export_types_png_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-reporting-export-types-png-common title: "@kbn/reporting-export-types-png-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/reporting-export-types-png-common plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/reporting-export-types-png-common'] --- import kbnReportingExportTypesPngCommonObj from './kbn_reporting_export_types_png_common.devdocs.json'; diff --git a/api_docs/kbn_reporting_mocks_server.mdx b/api_docs/kbn_reporting_mocks_server.mdx index 127057bcfdbe2..547be93ef9892 100644 --- a/api_docs/kbn_reporting_mocks_server.mdx +++ b/api_docs/kbn_reporting_mocks_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-reporting-mocks-server title: "@kbn/reporting-mocks-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/reporting-mocks-server plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/reporting-mocks-server'] --- import kbnReportingMocksServerObj from './kbn_reporting_mocks_server.devdocs.json'; diff --git a/api_docs/kbn_reporting_public.mdx b/api_docs/kbn_reporting_public.mdx index 164e27661af71..b389941156148 100644 --- a/api_docs/kbn_reporting_public.mdx +++ b/api_docs/kbn_reporting_public.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-reporting-public title: "@kbn/reporting-public" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/reporting-public plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/reporting-public'] --- import kbnReportingPublicObj from './kbn_reporting_public.devdocs.json'; diff --git a/api_docs/kbn_reporting_server.mdx b/api_docs/kbn_reporting_server.mdx index 25af686d31145..c6f8734818591 100644 --- a/api_docs/kbn_reporting_server.mdx +++ b/api_docs/kbn_reporting_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-reporting-server title: "@kbn/reporting-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/reporting-server plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/reporting-server'] --- import kbnReportingServerObj from './kbn_reporting_server.devdocs.json'; diff --git a/api_docs/kbn_resizable_layout.mdx b/api_docs/kbn_resizable_layout.mdx index 2b57bec994203..c797665c16f7b 100644 --- a/api_docs/kbn_resizable_layout.mdx +++ b/api_docs/kbn_resizable_layout.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-resizable-layout title: "@kbn/resizable-layout" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/resizable-layout plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/resizable-layout'] --- import kbnResizableLayoutObj from './kbn_resizable_layout.devdocs.json'; diff --git a/api_docs/kbn_rison.mdx b/api_docs/kbn_rison.mdx index 1b8e47f814c8a..6e0c73695eae1 100644 --- a/api_docs/kbn_rison.mdx +++ b/api_docs/kbn_rison.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-rison title: "@kbn/rison" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/rison plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/rison'] --- import kbnRisonObj from './kbn_rison.devdocs.json'; diff --git a/api_docs/kbn_router_utils.mdx b/api_docs/kbn_router_utils.mdx index dde000e9a571e..5d489de450d8c 100644 --- a/api_docs/kbn_router_utils.mdx +++ b/api_docs/kbn_router_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-router-utils title: "@kbn/router-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/router-utils plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/router-utils'] --- import kbnRouterUtilsObj from './kbn_router_utils.devdocs.json'; diff --git a/api_docs/kbn_rrule.mdx b/api_docs/kbn_rrule.mdx index 31edca16532dc..9533ba91f5db3 100644 --- a/api_docs/kbn_rrule.mdx +++ b/api_docs/kbn_rrule.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-rrule title: "@kbn/rrule" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/rrule plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/rrule'] --- import kbnRruleObj from './kbn_rrule.devdocs.json'; diff --git a/api_docs/kbn_rule_data_utils.mdx b/api_docs/kbn_rule_data_utils.mdx index 659e6f95c701f..f3ea4ccfb413a 100644 --- a/api_docs/kbn_rule_data_utils.mdx +++ b/api_docs/kbn_rule_data_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-rule-data-utils title: "@kbn/rule-data-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/rule-data-utils plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/rule-data-utils'] --- import kbnRuleDataUtilsObj from './kbn_rule_data_utils.devdocs.json'; diff --git a/api_docs/kbn_saved_objects_settings.mdx b/api_docs/kbn_saved_objects_settings.mdx index dec5b56e97069..391cc3a063d72 100644 --- a/api_docs/kbn_saved_objects_settings.mdx +++ b/api_docs/kbn_saved_objects_settings.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-saved-objects-settings title: "@kbn/saved-objects-settings" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/saved-objects-settings plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/saved-objects-settings'] --- import kbnSavedObjectsSettingsObj from './kbn_saved_objects_settings.devdocs.json'; diff --git a/api_docs/kbn_search_api_panels.mdx b/api_docs/kbn_search_api_panels.mdx index e575495680cc5..753932f628c47 100644 --- a/api_docs/kbn_search_api_panels.mdx +++ b/api_docs/kbn_search_api_panels.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-search-api-panels title: "@kbn/search-api-panels" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/search-api-panels plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/search-api-panels'] --- import kbnSearchApiPanelsObj from './kbn_search_api_panels.devdocs.json'; diff --git a/api_docs/kbn_search_connectors.mdx b/api_docs/kbn_search_connectors.mdx index 4190f2af42fc2..9bfc1b3de1827 100644 --- a/api_docs/kbn_search_connectors.mdx +++ b/api_docs/kbn_search_connectors.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-search-connectors title: "@kbn/search-connectors" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/search-connectors plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/search-connectors'] --- import kbnSearchConnectorsObj from './kbn_search_connectors.devdocs.json'; diff --git a/api_docs/kbn_search_errors.mdx b/api_docs/kbn_search_errors.mdx index 7e9c49a0d9231..0af7fa7db5604 100644 --- a/api_docs/kbn_search_errors.mdx +++ b/api_docs/kbn_search_errors.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-search-errors title: "@kbn/search-errors" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/search-errors plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/search-errors'] --- import kbnSearchErrorsObj from './kbn_search_errors.devdocs.json'; diff --git a/api_docs/kbn_search_index_documents.mdx b/api_docs/kbn_search_index_documents.mdx index cc5b96be4e0b2..b4b03c8492069 100644 --- a/api_docs/kbn_search_index_documents.mdx +++ b/api_docs/kbn_search_index_documents.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-search-index-documents title: "@kbn/search-index-documents" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/search-index-documents plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/search-index-documents'] --- import kbnSearchIndexDocumentsObj from './kbn_search_index_documents.devdocs.json'; diff --git a/api_docs/kbn_search_response_warnings.mdx b/api_docs/kbn_search_response_warnings.mdx index 83aae32e80800..974c37785fdff 100644 --- a/api_docs/kbn_search_response_warnings.mdx +++ b/api_docs/kbn_search_response_warnings.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-search-response-warnings title: "@kbn/search-response-warnings" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/search-response-warnings plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/search-response-warnings'] --- import kbnSearchResponseWarningsObj from './kbn_search_response_warnings.devdocs.json'; diff --git a/api_docs/kbn_security_hardening.mdx b/api_docs/kbn_security_hardening.mdx index c7514cb533756..4e2b792110c2b 100644 --- a/api_docs/kbn_security_hardening.mdx +++ b/api_docs/kbn_security_hardening.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-security-hardening title: "@kbn/security-hardening" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/security-hardening plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/security-hardening'] --- import kbnSecurityHardeningObj from './kbn_security_hardening.devdocs.json'; diff --git a/api_docs/kbn_security_plugin_types_common.mdx b/api_docs/kbn_security_plugin_types_common.mdx index 1cc1aac1b30f1..ebc2476f02212 100644 --- a/api_docs/kbn_security_plugin_types_common.mdx +++ b/api_docs/kbn_security_plugin_types_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-security-plugin-types-common title: "@kbn/security-plugin-types-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/security-plugin-types-common plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/security-plugin-types-common'] --- import kbnSecurityPluginTypesCommonObj from './kbn_security_plugin_types_common.devdocs.json'; diff --git a/api_docs/kbn_security_plugin_types_public.mdx b/api_docs/kbn_security_plugin_types_public.mdx index 9555ceb64fa9c..26b41b9d2b4a4 100644 --- a/api_docs/kbn_security_plugin_types_public.mdx +++ b/api_docs/kbn_security_plugin_types_public.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-security-plugin-types-public title: "@kbn/security-plugin-types-public" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/security-plugin-types-public plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/security-plugin-types-public'] --- import kbnSecurityPluginTypesPublicObj from './kbn_security_plugin_types_public.devdocs.json'; diff --git a/api_docs/kbn_security_plugin_types_server.mdx b/api_docs/kbn_security_plugin_types_server.mdx index d8af88076245e..47934fcb73bbf 100644 --- a/api_docs/kbn_security_plugin_types_server.mdx +++ b/api_docs/kbn_security_plugin_types_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-security-plugin-types-server title: "@kbn/security-plugin-types-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/security-plugin-types-server plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/security-plugin-types-server'] --- import kbnSecurityPluginTypesServerObj from './kbn_security_plugin_types_server.devdocs.json'; diff --git a/api_docs/kbn_security_solution_features.mdx b/api_docs/kbn_security_solution_features.mdx index f6a4f1072b22a..f0ce5d194c454 100644 --- a/api_docs/kbn_security_solution_features.mdx +++ b/api_docs/kbn_security_solution_features.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-security-solution-features title: "@kbn/security-solution-features" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/security-solution-features plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/security-solution-features'] --- import kbnSecuritySolutionFeaturesObj from './kbn_security_solution_features.devdocs.json'; diff --git a/api_docs/kbn_security_solution_navigation.mdx b/api_docs/kbn_security_solution_navigation.mdx index 5300898a8ba98..f8815931aa8a6 100644 --- a/api_docs/kbn_security_solution_navigation.mdx +++ b/api_docs/kbn_security_solution_navigation.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-security-solution-navigation title: "@kbn/security-solution-navigation" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/security-solution-navigation plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/security-solution-navigation'] --- import kbnSecuritySolutionNavigationObj from './kbn_security_solution_navigation.devdocs.json'; diff --git a/api_docs/kbn_security_solution_side_nav.mdx b/api_docs/kbn_security_solution_side_nav.mdx index aaf7bd9d48c73..1d6708afa5c37 100644 --- a/api_docs/kbn_security_solution_side_nav.mdx +++ b/api_docs/kbn_security_solution_side_nav.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-security-solution-side-nav title: "@kbn/security-solution-side-nav" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/security-solution-side-nav plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/security-solution-side-nav'] --- import kbnSecuritySolutionSideNavObj from './kbn_security_solution_side_nav.devdocs.json'; diff --git a/api_docs/kbn_security_solution_storybook_config.mdx b/api_docs/kbn_security_solution_storybook_config.mdx index 3e006aa85afe5..4082393eeee64 100644 --- a/api_docs/kbn_security_solution_storybook_config.mdx +++ b/api_docs/kbn_security_solution_storybook_config.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-security-solution-storybook-config title: "@kbn/security-solution-storybook-config" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/security-solution-storybook-config plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/security-solution-storybook-config'] --- import kbnSecuritySolutionStorybookConfigObj from './kbn_security_solution_storybook_config.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_autocomplete.mdx b/api_docs/kbn_securitysolution_autocomplete.mdx index 2699162867b8e..d3488634b2252 100644 --- a/api_docs/kbn_securitysolution_autocomplete.mdx +++ b/api_docs/kbn_securitysolution_autocomplete.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-autocomplete title: "@kbn/securitysolution-autocomplete" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-autocomplete plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-autocomplete'] --- import kbnSecuritysolutionAutocompleteObj from './kbn_securitysolution_autocomplete.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_data_table.mdx b/api_docs/kbn_securitysolution_data_table.mdx index 8e87e00aa6610..3ab1d7e7a1557 100644 --- a/api_docs/kbn_securitysolution_data_table.mdx +++ b/api_docs/kbn_securitysolution_data_table.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-data-table title: "@kbn/securitysolution-data-table" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-data-table plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-data-table'] --- import kbnSecuritysolutionDataTableObj from './kbn_securitysolution_data_table.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_ecs.mdx b/api_docs/kbn_securitysolution_ecs.mdx index 34ec5fad8f7d5..e8213df0a77e6 100644 --- a/api_docs/kbn_securitysolution_ecs.mdx +++ b/api_docs/kbn_securitysolution_ecs.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-ecs title: "@kbn/securitysolution-ecs" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-ecs plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-ecs'] --- import kbnSecuritysolutionEcsObj from './kbn_securitysolution_ecs.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_es_utils.mdx b/api_docs/kbn_securitysolution_es_utils.mdx index 989097ce16971..dc7f2d38be057 100644 --- a/api_docs/kbn_securitysolution_es_utils.mdx +++ b/api_docs/kbn_securitysolution_es_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-es-utils title: "@kbn/securitysolution-es-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-es-utils plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-es-utils'] --- import kbnSecuritysolutionEsUtilsObj from './kbn_securitysolution_es_utils.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_exception_list_components.mdx b/api_docs/kbn_securitysolution_exception_list_components.mdx index 78dd4ea1cabd3..82692812c3dce 100644 --- a/api_docs/kbn_securitysolution_exception_list_components.mdx +++ b/api_docs/kbn_securitysolution_exception_list_components.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-exception-list-components title: "@kbn/securitysolution-exception-list-components" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-exception-list-components plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-exception-list-components'] --- import kbnSecuritysolutionExceptionListComponentsObj from './kbn_securitysolution_exception_list_components.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_grouping.mdx b/api_docs/kbn_securitysolution_grouping.mdx index abddcebb885ef..a8772fb935427 100644 --- a/api_docs/kbn_securitysolution_grouping.mdx +++ b/api_docs/kbn_securitysolution_grouping.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-grouping title: "@kbn/securitysolution-grouping" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-grouping plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-grouping'] --- import kbnSecuritysolutionGroupingObj from './kbn_securitysolution_grouping.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_hook_utils.mdx b/api_docs/kbn_securitysolution_hook_utils.mdx index 91f00d5a04c95..0639aac685a8b 100644 --- a/api_docs/kbn_securitysolution_hook_utils.mdx +++ b/api_docs/kbn_securitysolution_hook_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-hook-utils title: "@kbn/securitysolution-hook-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-hook-utils plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-hook-utils'] --- import kbnSecuritysolutionHookUtilsObj from './kbn_securitysolution_hook_utils.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_io_ts_alerting_types.mdx b/api_docs/kbn_securitysolution_io_ts_alerting_types.mdx index d464937635e47..957a0c854b4e1 100644 --- a/api_docs/kbn_securitysolution_io_ts_alerting_types.mdx +++ b/api_docs/kbn_securitysolution_io_ts_alerting_types.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-io-ts-alerting-types title: "@kbn/securitysolution-io-ts-alerting-types" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-io-ts-alerting-types plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-io-ts-alerting-types'] --- import kbnSecuritysolutionIoTsAlertingTypesObj from './kbn_securitysolution_io_ts_alerting_types.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_io_ts_list_types.mdx b/api_docs/kbn_securitysolution_io_ts_list_types.mdx index db6fa74815ec7..5bc34bd96d990 100644 --- a/api_docs/kbn_securitysolution_io_ts_list_types.mdx +++ b/api_docs/kbn_securitysolution_io_ts_list_types.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-io-ts-list-types title: "@kbn/securitysolution-io-ts-list-types" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-io-ts-list-types plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-io-ts-list-types'] --- import kbnSecuritysolutionIoTsListTypesObj from './kbn_securitysolution_io_ts_list_types.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_io_ts_types.mdx b/api_docs/kbn_securitysolution_io_ts_types.mdx index 892c249619b7f..3d5a76f38835b 100644 --- a/api_docs/kbn_securitysolution_io_ts_types.mdx +++ b/api_docs/kbn_securitysolution_io_ts_types.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-io-ts-types title: "@kbn/securitysolution-io-ts-types" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-io-ts-types plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-io-ts-types'] --- import kbnSecuritysolutionIoTsTypesObj from './kbn_securitysolution_io_ts_types.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_io_ts_utils.mdx b/api_docs/kbn_securitysolution_io_ts_utils.mdx index 5deb63bb5491c..62688b3b1358c 100644 --- a/api_docs/kbn_securitysolution_io_ts_utils.mdx +++ b/api_docs/kbn_securitysolution_io_ts_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-io-ts-utils title: "@kbn/securitysolution-io-ts-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-io-ts-utils plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-io-ts-utils'] --- import kbnSecuritysolutionIoTsUtilsObj from './kbn_securitysolution_io_ts_utils.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_list_api.mdx b/api_docs/kbn_securitysolution_list_api.mdx index 5357b5726316c..a79ffb6dc85bc 100644 --- a/api_docs/kbn_securitysolution_list_api.mdx +++ b/api_docs/kbn_securitysolution_list_api.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-list-api title: "@kbn/securitysolution-list-api" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-list-api plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-list-api'] --- import kbnSecuritysolutionListApiObj from './kbn_securitysolution_list_api.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_list_constants.mdx b/api_docs/kbn_securitysolution_list_constants.mdx index 99db267f6a01f..b91e337ef3d7f 100644 --- a/api_docs/kbn_securitysolution_list_constants.mdx +++ b/api_docs/kbn_securitysolution_list_constants.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-list-constants title: "@kbn/securitysolution-list-constants" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-list-constants plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-list-constants'] --- import kbnSecuritysolutionListConstantsObj from './kbn_securitysolution_list_constants.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_list_hooks.mdx b/api_docs/kbn_securitysolution_list_hooks.mdx index 217f635d691da..a0ea05205c889 100644 --- a/api_docs/kbn_securitysolution_list_hooks.mdx +++ b/api_docs/kbn_securitysolution_list_hooks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-list-hooks title: "@kbn/securitysolution-list-hooks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-list-hooks plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-list-hooks'] --- import kbnSecuritysolutionListHooksObj from './kbn_securitysolution_list_hooks.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_list_utils.mdx b/api_docs/kbn_securitysolution_list_utils.mdx index ee867c89efc89..c57a1264bed62 100644 --- a/api_docs/kbn_securitysolution_list_utils.mdx +++ b/api_docs/kbn_securitysolution_list_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-list-utils title: "@kbn/securitysolution-list-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-list-utils plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-list-utils'] --- import kbnSecuritysolutionListUtilsObj from './kbn_securitysolution_list_utils.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_rules.mdx b/api_docs/kbn_securitysolution_rules.mdx index 9e04828b1b371..97d323bfddeaa 100644 --- a/api_docs/kbn_securitysolution_rules.mdx +++ b/api_docs/kbn_securitysolution_rules.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-rules title: "@kbn/securitysolution-rules" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-rules plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-rules'] --- import kbnSecuritysolutionRulesObj from './kbn_securitysolution_rules.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_t_grid.mdx b/api_docs/kbn_securitysolution_t_grid.mdx index 8638d9dfb70e9..cd8e38f77c7e8 100644 --- a/api_docs/kbn_securitysolution_t_grid.mdx +++ b/api_docs/kbn_securitysolution_t_grid.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-t-grid title: "@kbn/securitysolution-t-grid" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-t-grid plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-t-grid'] --- import kbnSecuritysolutionTGridObj from './kbn_securitysolution_t_grid.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_utils.mdx b/api_docs/kbn_securitysolution_utils.mdx index 1cce8ff3003cd..28842246ae537 100644 --- a/api_docs/kbn_securitysolution_utils.mdx +++ b/api_docs/kbn_securitysolution_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-utils title: "@kbn/securitysolution-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-utils plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-utils'] --- import kbnSecuritysolutionUtilsObj from './kbn_securitysolution_utils.devdocs.json'; diff --git a/api_docs/kbn_server_http_tools.mdx b/api_docs/kbn_server_http_tools.mdx index d998403f06441..896440119700c 100644 --- a/api_docs/kbn_server_http_tools.mdx +++ b/api_docs/kbn_server_http_tools.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-server-http-tools title: "@kbn/server-http-tools" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/server-http-tools plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/server-http-tools'] --- import kbnServerHttpToolsObj from './kbn_server_http_tools.devdocs.json'; diff --git a/api_docs/kbn_server_route_repository.mdx b/api_docs/kbn_server_route_repository.mdx index 9a683b9daaf43..a97d925b48916 100644 --- a/api_docs/kbn_server_route_repository.mdx +++ b/api_docs/kbn_server_route_repository.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-server-route-repository title: "@kbn/server-route-repository" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/server-route-repository plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/server-route-repository'] --- import kbnServerRouteRepositoryObj from './kbn_server_route_repository.devdocs.json'; diff --git a/api_docs/kbn_serverless_common_settings.mdx b/api_docs/kbn_serverless_common_settings.mdx index 4735ab549194a..ae38315f393be 100644 --- a/api_docs/kbn_serverless_common_settings.mdx +++ b/api_docs/kbn_serverless_common_settings.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-serverless-common-settings title: "@kbn/serverless-common-settings" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/serverless-common-settings plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/serverless-common-settings'] --- import kbnServerlessCommonSettingsObj from './kbn_serverless_common_settings.devdocs.json'; diff --git a/api_docs/kbn_serverless_observability_settings.mdx b/api_docs/kbn_serverless_observability_settings.mdx index d2c64d4064adb..afd676670ebfd 100644 --- a/api_docs/kbn_serverless_observability_settings.mdx +++ b/api_docs/kbn_serverless_observability_settings.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-serverless-observability-settings title: "@kbn/serverless-observability-settings" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/serverless-observability-settings plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/serverless-observability-settings'] --- import kbnServerlessObservabilitySettingsObj from './kbn_serverless_observability_settings.devdocs.json'; diff --git a/api_docs/kbn_serverless_project_switcher.mdx b/api_docs/kbn_serverless_project_switcher.mdx index aa0c5f660f172..e894dc5d06a28 100644 --- a/api_docs/kbn_serverless_project_switcher.mdx +++ b/api_docs/kbn_serverless_project_switcher.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-serverless-project-switcher title: "@kbn/serverless-project-switcher" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/serverless-project-switcher plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/serverless-project-switcher'] --- import kbnServerlessProjectSwitcherObj from './kbn_serverless_project_switcher.devdocs.json'; diff --git a/api_docs/kbn_serverless_search_settings.mdx b/api_docs/kbn_serverless_search_settings.mdx index 868e07cd2ebbf..455c200b8dbae 100644 --- a/api_docs/kbn_serverless_search_settings.mdx +++ b/api_docs/kbn_serverless_search_settings.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-serverless-search-settings title: "@kbn/serverless-search-settings" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/serverless-search-settings plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/serverless-search-settings'] --- import kbnServerlessSearchSettingsObj from './kbn_serverless_search_settings.devdocs.json'; diff --git a/api_docs/kbn_serverless_security_settings.mdx b/api_docs/kbn_serverless_security_settings.mdx index fcfc1f363eeee..27bb97e376443 100644 --- a/api_docs/kbn_serverless_security_settings.mdx +++ b/api_docs/kbn_serverless_security_settings.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-serverless-security-settings title: "@kbn/serverless-security-settings" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/serverless-security-settings plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/serverless-security-settings'] --- import kbnServerlessSecuritySettingsObj from './kbn_serverless_security_settings.devdocs.json'; diff --git a/api_docs/kbn_serverless_storybook_config.mdx b/api_docs/kbn_serverless_storybook_config.mdx index 830eb5581db8c..e6aeadbbd2eb7 100644 --- a/api_docs/kbn_serverless_storybook_config.mdx +++ b/api_docs/kbn_serverless_storybook_config.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-serverless-storybook-config title: "@kbn/serverless-storybook-config" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/serverless-storybook-config plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/serverless-storybook-config'] --- import kbnServerlessStorybookConfigObj from './kbn_serverless_storybook_config.devdocs.json'; diff --git a/api_docs/kbn_shared_svg.mdx b/api_docs/kbn_shared_svg.mdx index 86ae90d38a739..0bfb5dd6eb737 100644 --- a/api_docs/kbn_shared_svg.mdx +++ b/api_docs/kbn_shared_svg.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-svg title: "@kbn/shared-svg" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-svg plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-svg'] --- import kbnSharedSvgObj from './kbn_shared_svg.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_avatar_solution.mdx b/api_docs/kbn_shared_ux_avatar_solution.mdx index 90257a1cbfd92..ed077b97c3933 100644 --- a/api_docs/kbn_shared_ux_avatar_solution.mdx +++ b/api_docs/kbn_shared_ux_avatar_solution.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-avatar-solution title: "@kbn/shared-ux-avatar-solution" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-avatar-solution plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-avatar-solution'] --- import kbnSharedUxAvatarSolutionObj from './kbn_shared_ux_avatar_solution.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_button_exit_full_screen.mdx b/api_docs/kbn_shared_ux_button_exit_full_screen.mdx index 3bedf970949a6..113fd4cbecb5b 100644 --- a/api_docs/kbn_shared_ux_button_exit_full_screen.mdx +++ b/api_docs/kbn_shared_ux_button_exit_full_screen.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-button-exit-full-screen title: "@kbn/shared-ux-button-exit-full-screen" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-button-exit-full-screen plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-button-exit-full-screen'] --- import kbnSharedUxButtonExitFullScreenObj from './kbn_shared_ux_button_exit_full_screen.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_button_toolbar.mdx b/api_docs/kbn_shared_ux_button_toolbar.mdx index 9d7775a9f4735..d0b6e5dfc47ab 100644 --- a/api_docs/kbn_shared_ux_button_toolbar.mdx +++ b/api_docs/kbn_shared_ux_button_toolbar.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-button-toolbar title: "@kbn/shared-ux-button-toolbar" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-button-toolbar plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-button-toolbar'] --- import kbnSharedUxButtonToolbarObj from './kbn_shared_ux_button_toolbar.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_card_no_data.mdx b/api_docs/kbn_shared_ux_card_no_data.mdx index 08fa44237da91..b71e68445fec1 100644 --- a/api_docs/kbn_shared_ux_card_no_data.mdx +++ b/api_docs/kbn_shared_ux_card_no_data.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-card-no-data title: "@kbn/shared-ux-card-no-data" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-card-no-data plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-card-no-data'] --- import kbnSharedUxCardNoDataObj from './kbn_shared_ux_card_no_data.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_card_no_data_mocks.mdx b/api_docs/kbn_shared_ux_card_no_data_mocks.mdx index 02a8d4c4da424..61bd3626b9c6e 100644 --- a/api_docs/kbn_shared_ux_card_no_data_mocks.mdx +++ b/api_docs/kbn_shared_ux_card_no_data_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-card-no-data-mocks title: "@kbn/shared-ux-card-no-data-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-card-no-data-mocks plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-card-no-data-mocks'] --- import kbnSharedUxCardNoDataMocksObj from './kbn_shared_ux_card_no_data_mocks.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_chrome_navigation.mdx b/api_docs/kbn_shared_ux_chrome_navigation.mdx index 3930ceacd08b0..1f0e21cfc1115 100644 --- a/api_docs/kbn_shared_ux_chrome_navigation.mdx +++ b/api_docs/kbn_shared_ux_chrome_navigation.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-chrome-navigation title: "@kbn/shared-ux-chrome-navigation" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-chrome-navigation plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-chrome-navigation'] --- import kbnSharedUxChromeNavigationObj from './kbn_shared_ux_chrome_navigation.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_error_boundary.mdx b/api_docs/kbn_shared_ux_error_boundary.mdx index f005d70e7f705..5021610ec0b66 100644 --- a/api_docs/kbn_shared_ux_error_boundary.mdx +++ b/api_docs/kbn_shared_ux_error_boundary.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-error-boundary title: "@kbn/shared-ux-error-boundary" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-error-boundary plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-error-boundary'] --- import kbnSharedUxErrorBoundaryObj from './kbn_shared_ux_error_boundary.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_file_context.mdx b/api_docs/kbn_shared_ux_file_context.mdx index d922dbef23a56..32ad62cac643a 100644 --- a/api_docs/kbn_shared_ux_file_context.mdx +++ b/api_docs/kbn_shared_ux_file_context.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-file-context title: "@kbn/shared-ux-file-context" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-file-context plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-file-context'] --- import kbnSharedUxFileContextObj from './kbn_shared_ux_file_context.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_file_image.mdx b/api_docs/kbn_shared_ux_file_image.mdx index 70afe5cfb9277..b3d9367aa4afa 100644 --- a/api_docs/kbn_shared_ux_file_image.mdx +++ b/api_docs/kbn_shared_ux_file_image.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-file-image title: "@kbn/shared-ux-file-image" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-file-image plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-file-image'] --- import kbnSharedUxFileImageObj from './kbn_shared_ux_file_image.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_file_image_mocks.mdx b/api_docs/kbn_shared_ux_file_image_mocks.mdx index 7842fa9e33a7f..91c1d0b4d6040 100644 --- a/api_docs/kbn_shared_ux_file_image_mocks.mdx +++ b/api_docs/kbn_shared_ux_file_image_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-file-image-mocks title: "@kbn/shared-ux-file-image-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-file-image-mocks plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-file-image-mocks'] --- import kbnSharedUxFileImageMocksObj from './kbn_shared_ux_file_image_mocks.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_file_mocks.mdx b/api_docs/kbn_shared_ux_file_mocks.mdx index d9ad024bd6a18..e7b98502f5ca8 100644 --- a/api_docs/kbn_shared_ux_file_mocks.mdx +++ b/api_docs/kbn_shared_ux_file_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-file-mocks title: "@kbn/shared-ux-file-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-file-mocks plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-file-mocks'] --- import kbnSharedUxFileMocksObj from './kbn_shared_ux_file_mocks.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_file_picker.mdx b/api_docs/kbn_shared_ux_file_picker.mdx index 612f19907da9b..164937ee52cd1 100644 --- a/api_docs/kbn_shared_ux_file_picker.mdx +++ b/api_docs/kbn_shared_ux_file_picker.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-file-picker title: "@kbn/shared-ux-file-picker" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-file-picker plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-file-picker'] --- import kbnSharedUxFilePickerObj from './kbn_shared_ux_file_picker.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_file_types.mdx b/api_docs/kbn_shared_ux_file_types.mdx index 7a13cf9b4aa6d..59845f0ed4051 100644 --- a/api_docs/kbn_shared_ux_file_types.mdx +++ b/api_docs/kbn_shared_ux_file_types.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-file-types title: "@kbn/shared-ux-file-types" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-file-types plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-file-types'] --- import kbnSharedUxFileTypesObj from './kbn_shared_ux_file_types.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_file_upload.mdx b/api_docs/kbn_shared_ux_file_upload.mdx index f3bdd68929a1d..a84d3d2ac8911 100644 --- a/api_docs/kbn_shared_ux_file_upload.mdx +++ b/api_docs/kbn_shared_ux_file_upload.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-file-upload title: "@kbn/shared-ux-file-upload" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-file-upload plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-file-upload'] --- import kbnSharedUxFileUploadObj from './kbn_shared_ux_file_upload.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_file_util.mdx b/api_docs/kbn_shared_ux_file_util.mdx index e23ab99e03169..584fed1f64abd 100644 --- a/api_docs/kbn_shared_ux_file_util.mdx +++ b/api_docs/kbn_shared_ux_file_util.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-file-util title: "@kbn/shared-ux-file-util" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-file-util plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-file-util'] --- import kbnSharedUxFileUtilObj from './kbn_shared_ux_file_util.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_link_redirect_app.mdx b/api_docs/kbn_shared_ux_link_redirect_app.mdx index 773f7d6b04860..47d9920f86eed 100644 --- a/api_docs/kbn_shared_ux_link_redirect_app.mdx +++ b/api_docs/kbn_shared_ux_link_redirect_app.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-link-redirect-app title: "@kbn/shared-ux-link-redirect-app" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-link-redirect-app plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-link-redirect-app'] --- import kbnSharedUxLinkRedirectAppObj from './kbn_shared_ux_link_redirect_app.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_link_redirect_app_mocks.mdx b/api_docs/kbn_shared_ux_link_redirect_app_mocks.mdx index 7188963236338..cc65d22e42815 100644 --- a/api_docs/kbn_shared_ux_link_redirect_app_mocks.mdx +++ b/api_docs/kbn_shared_ux_link_redirect_app_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-link-redirect-app-mocks title: "@kbn/shared-ux-link-redirect-app-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-link-redirect-app-mocks plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-link-redirect-app-mocks'] --- import kbnSharedUxLinkRedirectAppMocksObj from './kbn_shared_ux_link_redirect_app_mocks.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_markdown.mdx b/api_docs/kbn_shared_ux_markdown.mdx index f91ab0adac8aa..33efc92326e94 100644 --- a/api_docs/kbn_shared_ux_markdown.mdx +++ b/api_docs/kbn_shared_ux_markdown.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-markdown title: "@kbn/shared-ux-markdown" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-markdown plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-markdown'] --- import kbnSharedUxMarkdownObj from './kbn_shared_ux_markdown.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_markdown_mocks.mdx b/api_docs/kbn_shared_ux_markdown_mocks.mdx index 9280b02fe69b2..424756bd3f465 100644 --- a/api_docs/kbn_shared_ux_markdown_mocks.mdx +++ b/api_docs/kbn_shared_ux_markdown_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-markdown-mocks title: "@kbn/shared-ux-markdown-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-markdown-mocks plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-markdown-mocks'] --- import kbnSharedUxMarkdownMocksObj from './kbn_shared_ux_markdown_mocks.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_page_analytics_no_data.mdx b/api_docs/kbn_shared_ux_page_analytics_no_data.mdx index db47e2715bf7e..ea5f8c94fefb9 100644 --- a/api_docs/kbn_shared_ux_page_analytics_no_data.mdx +++ b/api_docs/kbn_shared_ux_page_analytics_no_data.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-page-analytics-no-data title: "@kbn/shared-ux-page-analytics-no-data" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-page-analytics-no-data plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-page-analytics-no-data'] --- import kbnSharedUxPageAnalyticsNoDataObj from './kbn_shared_ux_page_analytics_no_data.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_page_analytics_no_data_mocks.mdx b/api_docs/kbn_shared_ux_page_analytics_no_data_mocks.mdx index 08650508c2d50..194e11218efc6 100644 --- a/api_docs/kbn_shared_ux_page_analytics_no_data_mocks.mdx +++ b/api_docs/kbn_shared_ux_page_analytics_no_data_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-page-analytics-no-data-mocks title: "@kbn/shared-ux-page-analytics-no-data-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-page-analytics-no-data-mocks plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-page-analytics-no-data-mocks'] --- import kbnSharedUxPageAnalyticsNoDataMocksObj from './kbn_shared_ux_page_analytics_no_data_mocks.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_page_kibana_no_data.mdx b/api_docs/kbn_shared_ux_page_kibana_no_data.mdx index b9c0e4178e764..ee67ee9e12203 100644 --- a/api_docs/kbn_shared_ux_page_kibana_no_data.mdx +++ b/api_docs/kbn_shared_ux_page_kibana_no_data.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-page-kibana-no-data title: "@kbn/shared-ux-page-kibana-no-data" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-page-kibana-no-data plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-page-kibana-no-data'] --- import kbnSharedUxPageKibanaNoDataObj from './kbn_shared_ux_page_kibana_no_data.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_page_kibana_no_data_mocks.mdx b/api_docs/kbn_shared_ux_page_kibana_no_data_mocks.mdx index b46c3129cca3f..8182a6ffb54dd 100644 --- a/api_docs/kbn_shared_ux_page_kibana_no_data_mocks.mdx +++ b/api_docs/kbn_shared_ux_page_kibana_no_data_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-page-kibana-no-data-mocks title: "@kbn/shared-ux-page-kibana-no-data-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-page-kibana-no-data-mocks plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-page-kibana-no-data-mocks'] --- import kbnSharedUxPageKibanaNoDataMocksObj from './kbn_shared_ux_page_kibana_no_data_mocks.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_page_kibana_template.mdx b/api_docs/kbn_shared_ux_page_kibana_template.mdx index 39b0e3d48b01e..2a37733c05832 100644 --- a/api_docs/kbn_shared_ux_page_kibana_template.mdx +++ b/api_docs/kbn_shared_ux_page_kibana_template.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-page-kibana-template title: "@kbn/shared-ux-page-kibana-template" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-page-kibana-template plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-page-kibana-template'] --- import kbnSharedUxPageKibanaTemplateObj from './kbn_shared_ux_page_kibana_template.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_page_kibana_template_mocks.mdx b/api_docs/kbn_shared_ux_page_kibana_template_mocks.mdx index bcfaf001a3a33..3c8145fed3959 100644 --- a/api_docs/kbn_shared_ux_page_kibana_template_mocks.mdx +++ b/api_docs/kbn_shared_ux_page_kibana_template_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-page-kibana-template-mocks title: "@kbn/shared-ux-page-kibana-template-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-page-kibana-template-mocks plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-page-kibana-template-mocks'] --- import kbnSharedUxPageKibanaTemplateMocksObj from './kbn_shared_ux_page_kibana_template_mocks.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_page_no_data.mdx b/api_docs/kbn_shared_ux_page_no_data.mdx index aa87734f466ff..ea945d7d788fc 100644 --- a/api_docs/kbn_shared_ux_page_no_data.mdx +++ b/api_docs/kbn_shared_ux_page_no_data.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-page-no-data title: "@kbn/shared-ux-page-no-data" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-page-no-data plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-page-no-data'] --- import kbnSharedUxPageNoDataObj from './kbn_shared_ux_page_no_data.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_page_no_data_config.mdx b/api_docs/kbn_shared_ux_page_no_data_config.mdx index c9f15f0bfed10..9b2208355a200 100644 --- a/api_docs/kbn_shared_ux_page_no_data_config.mdx +++ b/api_docs/kbn_shared_ux_page_no_data_config.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-page-no-data-config title: "@kbn/shared-ux-page-no-data-config" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-page-no-data-config plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-page-no-data-config'] --- import kbnSharedUxPageNoDataConfigObj from './kbn_shared_ux_page_no_data_config.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_page_no_data_config_mocks.mdx b/api_docs/kbn_shared_ux_page_no_data_config_mocks.mdx index b301cee51975a..a8504efbaa6d9 100644 --- a/api_docs/kbn_shared_ux_page_no_data_config_mocks.mdx +++ b/api_docs/kbn_shared_ux_page_no_data_config_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-page-no-data-config-mocks title: "@kbn/shared-ux-page-no-data-config-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-page-no-data-config-mocks plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-page-no-data-config-mocks'] --- import kbnSharedUxPageNoDataConfigMocksObj from './kbn_shared_ux_page_no_data_config_mocks.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_page_no_data_mocks.mdx b/api_docs/kbn_shared_ux_page_no_data_mocks.mdx index af89992a8ebbe..cfc1a37ddc217 100644 --- a/api_docs/kbn_shared_ux_page_no_data_mocks.mdx +++ b/api_docs/kbn_shared_ux_page_no_data_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-page-no-data-mocks title: "@kbn/shared-ux-page-no-data-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-page-no-data-mocks plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-page-no-data-mocks'] --- import kbnSharedUxPageNoDataMocksObj from './kbn_shared_ux_page_no_data_mocks.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_page_solution_nav.mdx b/api_docs/kbn_shared_ux_page_solution_nav.mdx index 21e27a52ff999..65e8cfb954d03 100644 --- a/api_docs/kbn_shared_ux_page_solution_nav.mdx +++ b/api_docs/kbn_shared_ux_page_solution_nav.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-page-solution-nav title: "@kbn/shared-ux-page-solution-nav" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-page-solution-nav plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-page-solution-nav'] --- import kbnSharedUxPageSolutionNavObj from './kbn_shared_ux_page_solution_nav.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_prompt_no_data_views.mdx b/api_docs/kbn_shared_ux_prompt_no_data_views.mdx index 02a68c68e2eaf..aed9dbd8cebea 100644 --- a/api_docs/kbn_shared_ux_prompt_no_data_views.mdx +++ b/api_docs/kbn_shared_ux_prompt_no_data_views.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-prompt-no-data-views title: "@kbn/shared-ux-prompt-no-data-views" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-prompt-no-data-views plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-prompt-no-data-views'] --- import kbnSharedUxPromptNoDataViewsObj from './kbn_shared_ux_prompt_no_data_views.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_prompt_no_data_views_mocks.mdx b/api_docs/kbn_shared_ux_prompt_no_data_views_mocks.mdx index 65ab20b9a0b54..c1ca4d9dd00a0 100644 --- a/api_docs/kbn_shared_ux_prompt_no_data_views_mocks.mdx +++ b/api_docs/kbn_shared_ux_prompt_no_data_views_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-prompt-no-data-views-mocks title: "@kbn/shared-ux-prompt-no-data-views-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-prompt-no-data-views-mocks plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-prompt-no-data-views-mocks'] --- import kbnSharedUxPromptNoDataViewsMocksObj from './kbn_shared_ux_prompt_no_data_views_mocks.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_prompt_not_found.mdx b/api_docs/kbn_shared_ux_prompt_not_found.mdx index 338c3bd6dd1d3..abb00b8470062 100644 --- a/api_docs/kbn_shared_ux_prompt_not_found.mdx +++ b/api_docs/kbn_shared_ux_prompt_not_found.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-prompt-not-found title: "@kbn/shared-ux-prompt-not-found" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-prompt-not-found plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-prompt-not-found'] --- import kbnSharedUxPromptNotFoundObj from './kbn_shared_ux_prompt_not_found.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_router.mdx b/api_docs/kbn_shared_ux_router.mdx index bc8f1ead35249..eb03bd86eea2d 100644 --- a/api_docs/kbn_shared_ux_router.mdx +++ b/api_docs/kbn_shared_ux_router.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-router title: "@kbn/shared-ux-router" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-router plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-router'] --- import kbnSharedUxRouterObj from './kbn_shared_ux_router.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_router_mocks.mdx b/api_docs/kbn_shared_ux_router_mocks.mdx index bd12f30da2280..e16d9a516961c 100644 --- a/api_docs/kbn_shared_ux_router_mocks.mdx +++ b/api_docs/kbn_shared_ux_router_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-router-mocks title: "@kbn/shared-ux-router-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-router-mocks plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-router-mocks'] --- import kbnSharedUxRouterMocksObj from './kbn_shared_ux_router_mocks.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_storybook_config.mdx b/api_docs/kbn_shared_ux_storybook_config.mdx index baaf43f1a827d..9f77d00f967e6 100644 --- a/api_docs/kbn_shared_ux_storybook_config.mdx +++ b/api_docs/kbn_shared_ux_storybook_config.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-storybook-config title: "@kbn/shared-ux-storybook-config" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-storybook-config plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-storybook-config'] --- import kbnSharedUxStorybookConfigObj from './kbn_shared_ux_storybook_config.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_storybook_mock.mdx b/api_docs/kbn_shared_ux_storybook_mock.mdx index b44650a2d1a0c..517c95b05b456 100644 --- a/api_docs/kbn_shared_ux_storybook_mock.mdx +++ b/api_docs/kbn_shared_ux_storybook_mock.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-storybook-mock title: "@kbn/shared-ux-storybook-mock" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-storybook-mock plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-storybook-mock'] --- import kbnSharedUxStorybookMockObj from './kbn_shared_ux_storybook_mock.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_utility.mdx b/api_docs/kbn_shared_ux_utility.mdx index aeff100a16daf..016c957f5df81 100644 --- a/api_docs/kbn_shared_ux_utility.mdx +++ b/api_docs/kbn_shared_ux_utility.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-utility title: "@kbn/shared-ux-utility" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-utility plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-utility'] --- import kbnSharedUxUtilityObj from './kbn_shared_ux_utility.devdocs.json'; diff --git a/api_docs/kbn_slo_schema.mdx b/api_docs/kbn_slo_schema.mdx index a0ea0e01d9c8f..c8d18a1839054 100644 --- a/api_docs/kbn_slo_schema.mdx +++ b/api_docs/kbn_slo_schema.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-slo-schema title: "@kbn/slo-schema" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/slo-schema plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/slo-schema'] --- import kbnSloSchemaObj from './kbn_slo_schema.devdocs.json'; diff --git a/api_docs/kbn_some_dev_log.mdx b/api_docs/kbn_some_dev_log.mdx index 6b3fc980ffdd4..fef6baa533e7b 100644 --- a/api_docs/kbn_some_dev_log.mdx +++ b/api_docs/kbn_some_dev_log.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-some-dev-log title: "@kbn/some-dev-log" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/some-dev-log plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/some-dev-log'] --- import kbnSomeDevLogObj from './kbn_some_dev_log.devdocs.json'; diff --git a/api_docs/kbn_sort_predicates.mdx b/api_docs/kbn_sort_predicates.mdx index 77a3842e53104..85a2cd133ec08 100644 --- a/api_docs/kbn_sort_predicates.mdx +++ b/api_docs/kbn_sort_predicates.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-sort-predicates title: "@kbn/sort-predicates" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/sort-predicates plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/sort-predicates'] --- import kbnSortPredicatesObj from './kbn_sort_predicates.devdocs.json'; diff --git a/api_docs/kbn_std.mdx b/api_docs/kbn_std.mdx index a899f814b6142..4b1e2533a0d8c 100644 --- a/api_docs/kbn_std.mdx +++ b/api_docs/kbn_std.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-std title: "@kbn/std" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/std plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/std'] --- import kbnStdObj from './kbn_std.devdocs.json'; diff --git a/api_docs/kbn_stdio_dev_helpers.mdx b/api_docs/kbn_stdio_dev_helpers.mdx index 09788e26a1bd2..5ce03fffb102f 100644 --- a/api_docs/kbn_stdio_dev_helpers.mdx +++ b/api_docs/kbn_stdio_dev_helpers.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-stdio-dev-helpers title: "@kbn/stdio-dev-helpers" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/stdio-dev-helpers plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/stdio-dev-helpers'] --- import kbnStdioDevHelpersObj from './kbn_stdio_dev_helpers.devdocs.json'; diff --git a/api_docs/kbn_storybook.mdx b/api_docs/kbn_storybook.mdx index 47e2bfb9d394c..33f7bc0c6c770 100644 --- a/api_docs/kbn_storybook.mdx +++ b/api_docs/kbn_storybook.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-storybook title: "@kbn/storybook" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/storybook plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/storybook'] --- import kbnStorybookObj from './kbn_storybook.devdocs.json'; diff --git a/api_docs/kbn_telemetry_tools.mdx b/api_docs/kbn_telemetry_tools.mdx index a5a4012524892..b66b8d43c8a56 100644 --- a/api_docs/kbn_telemetry_tools.mdx +++ b/api_docs/kbn_telemetry_tools.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-telemetry-tools title: "@kbn/telemetry-tools" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/telemetry-tools plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/telemetry-tools'] --- import kbnTelemetryToolsObj from './kbn_telemetry_tools.devdocs.json'; diff --git a/api_docs/kbn_test.mdx b/api_docs/kbn_test.mdx index 9374fcd2e7adb..04ed6ad58d62b 100644 --- a/api_docs/kbn_test.mdx +++ b/api_docs/kbn_test.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-test title: "@kbn/test" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/test plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/test'] --- import kbnTestObj from './kbn_test.devdocs.json'; diff --git a/api_docs/kbn_test_eui_helpers.mdx b/api_docs/kbn_test_eui_helpers.mdx index 3efeeea8dd9e2..df6d3591a9b2f 100644 --- a/api_docs/kbn_test_eui_helpers.mdx +++ b/api_docs/kbn_test_eui_helpers.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-test-eui-helpers title: "@kbn/test-eui-helpers" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/test-eui-helpers plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/test-eui-helpers'] --- import kbnTestEuiHelpersObj from './kbn_test_eui_helpers.devdocs.json'; diff --git a/api_docs/kbn_test_jest_helpers.mdx b/api_docs/kbn_test_jest_helpers.mdx index 058a4582fe60d..e1ba5ef041cfa 100644 --- a/api_docs/kbn_test_jest_helpers.mdx +++ b/api_docs/kbn_test_jest_helpers.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-test-jest-helpers title: "@kbn/test-jest-helpers" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/test-jest-helpers plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/test-jest-helpers'] --- import kbnTestJestHelpersObj from './kbn_test_jest_helpers.devdocs.json'; diff --git a/api_docs/kbn_test_subj_selector.mdx b/api_docs/kbn_test_subj_selector.mdx index 5a62b08b6c9df..fbd2be0209a26 100644 --- a/api_docs/kbn_test_subj_selector.mdx +++ b/api_docs/kbn_test_subj_selector.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-test-subj-selector title: "@kbn/test-subj-selector" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/test-subj-selector plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/test-subj-selector'] --- import kbnTestSubjSelectorObj from './kbn_test_subj_selector.devdocs.json'; diff --git a/api_docs/kbn_text_based_editor.mdx b/api_docs/kbn_text_based_editor.mdx index f69b3f72b9e16..bc69d741c5261 100644 --- a/api_docs/kbn_text_based_editor.mdx +++ b/api_docs/kbn_text_based_editor.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-text-based-editor title: "@kbn/text-based-editor" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/text-based-editor plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/text-based-editor'] --- import kbnTextBasedEditorObj from './kbn_text_based_editor.devdocs.json'; diff --git a/api_docs/kbn_tooling_log.mdx b/api_docs/kbn_tooling_log.mdx index 8e117b295d865..701835ae16f72 100644 --- a/api_docs/kbn_tooling_log.mdx +++ b/api_docs/kbn_tooling_log.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-tooling-log title: "@kbn/tooling-log" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/tooling-log plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/tooling-log'] --- import kbnToolingLogObj from './kbn_tooling_log.devdocs.json'; diff --git a/api_docs/kbn_triggers_actions_ui_types.mdx b/api_docs/kbn_triggers_actions_ui_types.mdx index 9fde409c31d99..e566233db3027 100644 --- a/api_docs/kbn_triggers_actions_ui_types.mdx +++ b/api_docs/kbn_triggers_actions_ui_types.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-triggers-actions-ui-types title: "@kbn/triggers-actions-ui-types" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/triggers-actions-ui-types plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/triggers-actions-ui-types'] --- import kbnTriggersActionsUiTypesObj from './kbn_triggers_actions_ui_types.devdocs.json'; diff --git a/api_docs/kbn_ts_projects.mdx b/api_docs/kbn_ts_projects.mdx index ffa3ca3e6e637..c26358dc89e55 100644 --- a/api_docs/kbn_ts_projects.mdx +++ b/api_docs/kbn_ts_projects.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ts-projects title: "@kbn/ts-projects" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ts-projects plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ts-projects'] --- import kbnTsProjectsObj from './kbn_ts_projects.devdocs.json'; diff --git a/api_docs/kbn_typed_react_router_config.mdx b/api_docs/kbn_typed_react_router_config.mdx index 0a1d89e344757..7644589a9362f 100644 --- a/api_docs/kbn_typed_react_router_config.mdx +++ b/api_docs/kbn_typed_react_router_config.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-typed-react-router-config title: "@kbn/typed-react-router-config" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/typed-react-router-config plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/typed-react-router-config'] --- import kbnTypedReactRouterConfigObj from './kbn_typed_react_router_config.devdocs.json'; diff --git a/api_docs/kbn_ui_actions_browser.mdx b/api_docs/kbn_ui_actions_browser.mdx index 678bb06fd29ad..cd7aafc86c4d7 100644 --- a/api_docs/kbn_ui_actions_browser.mdx +++ b/api_docs/kbn_ui_actions_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ui-actions-browser title: "@kbn/ui-actions-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ui-actions-browser plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ui-actions-browser'] --- import kbnUiActionsBrowserObj from './kbn_ui_actions_browser.devdocs.json'; diff --git a/api_docs/kbn_ui_shared_deps_src.mdx b/api_docs/kbn_ui_shared_deps_src.mdx index 7a35f0d570d47..8863e992b6234 100644 --- a/api_docs/kbn_ui_shared_deps_src.mdx +++ b/api_docs/kbn_ui_shared_deps_src.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ui-shared-deps-src title: "@kbn/ui-shared-deps-src" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ui-shared-deps-src plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ui-shared-deps-src'] --- import kbnUiSharedDepsSrcObj from './kbn_ui_shared_deps_src.devdocs.json'; diff --git a/api_docs/kbn_ui_theme.mdx b/api_docs/kbn_ui_theme.mdx index 2cfb33fb89596..e3788cdd5f22b 100644 --- a/api_docs/kbn_ui_theme.mdx +++ b/api_docs/kbn_ui_theme.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ui-theme title: "@kbn/ui-theme" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ui-theme plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ui-theme'] --- import kbnUiThemeObj from './kbn_ui_theme.devdocs.json'; diff --git a/api_docs/kbn_unified_data_table.devdocs.json b/api_docs/kbn_unified_data_table.devdocs.json index 8c9ffa9f3c1e4..c5ceadd632768 100644 --- a/api_docs/kbn_unified_data_table.devdocs.json +++ b/api_docs/kbn_unified_data_table.devdocs.json @@ -363,6 +363,53 @@ "returnComment": [], "initialIsOpen": false }, + { + "parentPluginId": "@kbn/unified-data-table", + "id": "def-common.RowHeightSettings", + "type": "Function", + "tags": [], + "label": "RowHeightSettings", + "description": [], + "signature": [ + "({\n label,\n rowHeight,\n rowHeightLines,\n compressed,\n onChangeRowHeight,\n onChangeRowHeightLines,\n maxRowHeight,\n ['data-test-subj']: dataTestSubj,\n}: ", + { + "pluginId": "@kbn/unified-data-table", + "scope": "common", + "docId": "kibKbnUnifiedDataTablePluginApi", + "section": "def-common.RowHeightSettingsProps", + "text": "RowHeightSettingsProps" + }, + ") => JSX.Element" + ], + "path": "packages/kbn-unified-data-table/src/components/row_height_settings.tsx", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/unified-data-table", + "id": "def-common.RowHeightSettings.$1", + "type": "Object", + "tags": [], + "label": "{\n label,\n rowHeight,\n rowHeightLines,\n compressed,\n onChangeRowHeight,\n onChangeRowHeightLines,\n maxRowHeight,\n ['data-test-subj']: dataTestSubj,\n}", + "description": [], + "signature": [ + { + "pluginId": "@kbn/unified-data-table", + "scope": "common", + "docId": "kibKbnUnifiedDataTablePluginApi", + "section": "def-common.RowHeightSettingsProps", + "text": "RowHeightSettingsProps" + } + ], + "path": "packages/kbn-unified-data-table/src/components/row_height_settings.tsx", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [], + "initialIsOpen": false + }, { "parentPluginId": "@kbn/unified-data-table", "id": "def-common.UnifiedDataTable", @@ -371,7 +418,7 @@ "label": "UnifiedDataTable", "description": [], "signature": [ - "({ ariaLabelledBy, columns, columnTypes, showColumnTokens, headerRowHeight, controlColumnIds, dataView, loadingState, onFilter, onResize, onSetColumns, onSort, rows, searchDescription, searchTitle, settings, showTimeCol, showFullScreenButton, sort, useNewFieldsApi, isSortEnabled, isPaginationEnabled, cellActionsTriggerId, className, rowHeightState, onUpdateRowHeight, maxAllowedSampleSize, sampleSizeState, onUpdateSampleSize, isPlainRecord, rowsPerPageState, onUpdateRowsPerPage, onFieldEdited, services, renderCustomGridBody, renderCustomToolbar, trailingControlColumns, totalHits, onFetchMoreRecords, renderDocumentView, setExpandedDoc, expandedDoc, configRowHeight, showMultiFields, maxDocFieldsDisplayed, externalControlColumns, externalAdditionalControls, rowsPerPageOptions, visibleCellActions, externalCustomRenderers, consumer, componentsTourSteps, gridStyleOverride, rowLineHeightOverride, customGridColumnsConfiguration, customControlColumnsConfiguration, }: ", + "({ ariaLabelledBy, columns, columnTypes, showColumnTokens, configHeaderRowHeight, headerRowHeightState, onUpdateHeaderRowHeight, controlColumnIds, dataView, loadingState, onFilter, onResize, onSetColumns, onSort, rows, searchDescription, searchTitle, settings, showTimeCol, showFullScreenButton, sort, useNewFieldsApi, isSortEnabled, isPaginationEnabled, cellActionsTriggerId, className, rowHeightState, onUpdateRowHeight, maxAllowedSampleSize, sampleSizeState, onUpdateSampleSize, isPlainRecord, rowsPerPageState, onUpdateRowsPerPage, onFieldEdited, services, renderCustomGridBody, renderCustomToolbar, trailingControlColumns, totalHits, onFetchMoreRecords, renderDocumentView, setExpandedDoc, expandedDoc, configRowHeight, showMultiFields, maxDocFieldsDisplayed, externalControlColumns, externalAdditionalControls, rowsPerPageOptions, visibleCellActions, externalCustomRenderers, consumer, componentsTourSteps, gridStyleOverride, rowLineHeightOverride, customGridColumnsConfiguration, customControlColumnsConfiguration, }: ", { "pluginId": "@kbn/unified-data-table", "scope": "common", @@ -390,7 +437,7 @@ "id": "def-common.UnifiedDataTable.$1", "type": "Object", "tags": [], - "label": "{\n ariaLabelledBy,\n columns,\n columnTypes,\n showColumnTokens,\n headerRowHeight,\n controlColumnIds = CONTROL_COLUMN_IDS_DEFAULT,\n dataView,\n loadingState,\n onFilter,\n onResize,\n onSetColumns,\n onSort,\n rows,\n searchDescription,\n searchTitle,\n settings,\n showTimeCol,\n showFullScreenButton = true,\n sort,\n useNewFieldsApi,\n isSortEnabled = true,\n isPaginationEnabled = true,\n cellActionsTriggerId,\n className,\n rowHeightState,\n onUpdateRowHeight,\n maxAllowedSampleSize,\n sampleSizeState,\n onUpdateSampleSize,\n isPlainRecord = false,\n rowsPerPageState,\n onUpdateRowsPerPage,\n onFieldEdited,\n services,\n renderCustomGridBody,\n renderCustomToolbar,\n trailingControlColumns,\n totalHits,\n onFetchMoreRecords,\n renderDocumentView,\n setExpandedDoc,\n expandedDoc,\n configRowHeight,\n showMultiFields = true,\n maxDocFieldsDisplayed = 50,\n externalControlColumns,\n externalAdditionalControls,\n rowsPerPageOptions,\n visibleCellActions,\n externalCustomRenderers,\n consumer = 'discover',\n componentsTourSteps,\n gridStyleOverride,\n rowLineHeightOverride,\n customGridColumnsConfiguration,\n customControlColumnsConfiguration,\n}", + "label": "{\n ariaLabelledBy,\n columns,\n columnTypes,\n showColumnTokens,\n configHeaderRowHeight,\n headerRowHeightState,\n onUpdateHeaderRowHeight,\n controlColumnIds = CONTROL_COLUMN_IDS_DEFAULT,\n dataView,\n loadingState,\n onFilter,\n onResize,\n onSetColumns,\n onSort,\n rows,\n searchDescription,\n searchTitle,\n settings,\n showTimeCol,\n showFullScreenButton = true,\n sort,\n useNewFieldsApi,\n isSortEnabled = true,\n isPaginationEnabled = true,\n cellActionsTriggerId,\n className,\n rowHeightState,\n onUpdateRowHeight,\n maxAllowedSampleSize,\n sampleSizeState,\n onUpdateSampleSize,\n isPlainRecord = false,\n rowsPerPageState,\n onUpdateRowsPerPage,\n onFieldEdited,\n services,\n renderCustomGridBody,\n renderCustomToolbar,\n trailingControlColumns,\n totalHits,\n onFetchMoreRecords,\n renderDocumentView,\n setExpandedDoc,\n expandedDoc,\n configRowHeight,\n showMultiFields = true,\n maxDocFieldsDisplayed = 50,\n externalControlColumns,\n externalAdditionalControls,\n rowsPerPageOptions,\n visibleCellActions,\n externalCustomRenderers,\n consumer = 'discover',\n componentsTourSteps,\n gridStyleOverride,\n rowLineHeightOverride,\n customGridColumnsConfiguration,\n customControlColumnsConfiguration,\n}", "description": [], "signature": [ { @@ -563,6 +610,165 @@ ], "initialIsOpen": false }, + { + "parentPluginId": "@kbn/unified-data-table", + "id": "def-common.RowHeightSettingsProps", + "type": "Interface", + "tags": [], + "label": "RowHeightSettingsProps", + "description": [], + "path": "packages/kbn-unified-data-table/src/components/row_height_settings.tsx", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/unified-data-table", + "id": "def-common.RowHeightSettingsProps.rowHeight", + "type": "CompoundType", + "tags": [], + "label": "rowHeight", + "description": [], + "signature": [ + "\"custom\" | \"auto\" | \"single\" | undefined" + ], + "path": "packages/kbn-unified-data-table/src/components/row_height_settings.tsx", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/unified-data-table", + "id": "def-common.RowHeightSettingsProps.rowHeightLines", + "type": "number", + "tags": [], + "label": "rowHeightLines", + "description": [], + "signature": [ + "number | undefined" + ], + "path": "packages/kbn-unified-data-table/src/components/row_height_settings.tsx", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/unified-data-table", + "id": "def-common.RowHeightSettingsProps.maxRowHeight", + "type": "number", + "tags": [], + "label": "maxRowHeight", + "description": [], + "signature": [ + "number | undefined" + ], + "path": "packages/kbn-unified-data-table/src/components/row_height_settings.tsx", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/unified-data-table", + "id": "def-common.RowHeightSettingsProps.label", + "type": "string", + "tags": [], + "label": "label", + "description": [], + "path": "packages/kbn-unified-data-table/src/components/row_height_settings.tsx", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/unified-data-table", + "id": "def-common.RowHeightSettingsProps.compressed", + "type": "CompoundType", + "tags": [], + "label": "compressed", + "description": [], + "signature": [ + "boolean | undefined" + ], + "path": "packages/kbn-unified-data-table/src/components/row_height_settings.tsx", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/unified-data-table", + "id": "def-common.RowHeightSettingsProps.onChangeRowHeight", + "type": "Function", + "tags": [], + "label": "onChangeRowHeight", + "description": [], + "signature": [ + "(newHeightMode: \"custom\" | \"auto\" | \"single\" | undefined) => void" + ], + "path": "packages/kbn-unified-data-table/src/components/row_height_settings.tsx", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/unified-data-table", + "id": "def-common.RowHeightSettingsProps.onChangeRowHeight.$1", + "type": "CompoundType", + "tags": [], + "label": "newHeightMode", + "description": [], + "signature": [ + "\"custom\" | \"auto\" | \"single\" | undefined" + ], + "path": "packages/kbn-unified-data-table/src/components/row_height_settings.tsx", + "deprecated": false, + "trackAdoption": false, + "isRequired": false + } + ], + "returnComment": [] + }, + { + "parentPluginId": "@kbn/unified-data-table", + "id": "def-common.RowHeightSettingsProps.onChangeRowHeightLines", + "type": "Function", + "tags": [], + "label": "onChangeRowHeightLines", + "description": [], + "signature": [ + "(newRowHeightLines: number) => void" + ], + "path": "packages/kbn-unified-data-table/src/components/row_height_settings.tsx", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/unified-data-table", + "id": "def-common.RowHeightSettingsProps.onChangeRowHeightLines.$1", + "type": "number", + "tags": [], + "label": "newRowHeightLines", + "description": [], + "signature": [ + "number" + ], + "path": "packages/kbn-unified-data-table/src/components/row_height_settings.tsx", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + }, + { + "parentPluginId": "@kbn/unified-data-table", + "id": "def-common.RowHeightSettingsProps.datatestsubj", + "type": "string", + "tags": [], + "label": "'data-test-subj'", + "description": [], + "signature": [ + "string | undefined" + ], + "path": "packages/kbn-unified-data-table/src/components/row_height_settings.tsx", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, { "parentPluginId": "@kbn/unified-data-table", "id": "def-common.UnifiedDataTableProps", @@ -662,10 +868,26 @@ }, { "parentPluginId": "@kbn/unified-data-table", - "id": "def-common.UnifiedDataTableProps.headerRowHeight", + "id": "def-common.UnifiedDataTableProps.configHeaderRowHeight", "type": "number", "tags": [], - "label": "headerRowHeight", + "label": "configHeaderRowHeight", + "description": [ + "\nOptional value for providing configuration setting for UnifiedDataTable header row height" + ], + "signature": [ + "number | undefined" + ], + "path": "packages/kbn-unified-data-table/src/components/data_table.tsx", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/unified-data-table", + "id": "def-common.UnifiedDataTableProps.headerRowHeightState", + "type": "number", + "tags": [], + "label": "headerRowHeightState", "description": [ "\nDetermines number of rows of a column header" ], @@ -676,6 +898,40 @@ "deprecated": false, "trackAdoption": false }, + { + "parentPluginId": "@kbn/unified-data-table", + "id": "def-common.UnifiedDataTableProps.onUpdateHeaderRowHeight", + "type": "Function", + "tags": [], + "label": "onUpdateHeaderRowHeight", + "description": [ + "\nUpdate header row height state" + ], + "signature": [ + "((headerRowHeight: number) => void) | undefined" + ], + "path": "packages/kbn-unified-data-table/src/components/data_table.tsx", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/unified-data-table", + "id": "def-common.UnifiedDataTableProps.onUpdateHeaderRowHeight.$1", + "type": "number", + "tags": [], + "label": "headerRowHeight", + "description": [], + "signature": [ + "number" + ], + "path": "packages/kbn-unified-data-table/src/components/data_table.tsx", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + }, { "parentPluginId": "@kbn/unified-data-table", "id": "def-common.UnifiedDataTableProps.expandedDoc", @@ -1155,6 +1411,22 @@ "deprecated": false, "trackAdoption": false }, + { + "parentPluginId": "@kbn/unified-data-table", + "id": "def-common.UnifiedDataTableProps.configRowHeight", + "type": "number", + "tags": [], + "label": "configRowHeight", + "description": [ + "\nOptional value for providing configuration setting for UnifiedDataTable row height" + ], + "signature": [ + "number | undefined" + ], + "path": "packages/kbn-unified-data-table/src/components/data_table.tsx", + "deprecated": false, + "trackAdoption": false + }, { "parentPluginId": "@kbn/unified-data-table", "id": "def-common.UnifiedDataTableProps.rowHeightState", @@ -1539,22 +1811,6 @@ ], "returnComment": [] }, - { - "parentPluginId": "@kbn/unified-data-table", - "id": "def-common.UnifiedDataTableProps.configRowHeight", - "type": "number", - "tags": [], - "label": "configRowHeight", - "description": [ - "\nOptional value for providing configuration setting for UnifiedDataTable rows height" - ], - "signature": [ - "number | undefined" - ], - "path": "packages/kbn-unified-data-table/src/components/data_table.tsx", - "deprecated": false, - "trackAdoption": false - }, { "parentPluginId": "@kbn/unified-data-table", "id": "def-common.UnifiedDataTableProps.showMultiFields", @@ -2315,44 +2571,12 @@ "description": [ "\nRow height might be a value from -1 to 20\nA value of -1 automatically adjusts the row height to fit the contents.\nA value of 0 displays the content in a single line.\nA value from 1 to 20 represents number of lines of Document explorer row to display." ], + "signature": [ + "{ readonly auto: -1; readonly single: 0; readonly default: 3; }" + ], "path": "packages/kbn-unified-data-table/src/constants.ts", "deprecated": false, "trackAdoption": false, - "children": [ - { - "parentPluginId": "@kbn/unified-data-table", - "id": "def-common.ROWS_HEIGHT_OPTIONS.auto", - "type": "number", - "tags": [], - "label": "auto", - "description": [], - "path": "packages/kbn-unified-data-table/src/constants.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "@kbn/unified-data-table", - "id": "def-common.ROWS_HEIGHT_OPTIONS.single", - "type": "number", - "tags": [], - "label": "single", - "description": [], - "path": "packages/kbn-unified-data-table/src/constants.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "@kbn/unified-data-table", - "id": "def-common.ROWS_HEIGHT_OPTIONS.default", - "type": "number", - "tags": [], - "label": "default", - "description": [], - "path": "packages/kbn-unified-data-table/src/constants.ts", - "deprecated": false, - "trackAdoption": false - } - ], "initialIsOpen": false } ] diff --git a/api_docs/kbn_unified_data_table.mdx b/api_docs/kbn_unified_data_table.mdx index cdec2c085a152..3a26cf82158da 100644 --- a/api_docs/kbn_unified_data_table.mdx +++ b/api_docs/kbn_unified_data_table.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-unified-data-table title: "@kbn/unified-data-table" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/unified-data-table plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/unified-data-table'] --- import kbnUnifiedDataTableObj from './kbn_unified_data_table.devdocs.json'; @@ -21,7 +21,7 @@ Contact [@elastic/kibana-data-discovery](https://github.com/orgs/elastic/teams/k | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 130 | 0 | 67 | 1 | +| 143 | 0 | 78 | 1 | ## Common diff --git a/api_docs/kbn_unified_doc_viewer.mdx b/api_docs/kbn_unified_doc_viewer.mdx index f9bba1cf17922..1b73eb784c7c7 100644 --- a/api_docs/kbn_unified_doc_viewer.mdx +++ b/api_docs/kbn_unified_doc_viewer.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-unified-doc-viewer title: "@kbn/unified-doc-viewer" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/unified-doc-viewer plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/unified-doc-viewer'] --- import kbnUnifiedDocViewerObj from './kbn_unified_doc_viewer.devdocs.json'; diff --git a/api_docs/kbn_unified_field_list.mdx b/api_docs/kbn_unified_field_list.mdx index f34bb6558eeaa..82f2ad8b0733f 100644 --- a/api_docs/kbn_unified_field_list.mdx +++ b/api_docs/kbn_unified_field_list.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-unified-field-list title: "@kbn/unified-field-list" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/unified-field-list plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/unified-field-list'] --- import kbnUnifiedFieldListObj from './kbn_unified_field_list.devdocs.json'; diff --git a/api_docs/kbn_unsaved_changes_badge.mdx b/api_docs/kbn_unsaved_changes_badge.mdx index 302e528889f3b..843f808cce11f 100644 --- a/api_docs/kbn_unsaved_changes_badge.mdx +++ b/api_docs/kbn_unsaved_changes_badge.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-unsaved-changes-badge title: "@kbn/unsaved-changes-badge" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/unsaved-changes-badge plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/unsaved-changes-badge'] --- import kbnUnsavedChangesBadgeObj from './kbn_unsaved_changes_badge.devdocs.json'; diff --git a/api_docs/kbn_use_tracked_promise.mdx b/api_docs/kbn_use_tracked_promise.mdx index 93cfb7432672c..841d9c76f91a3 100644 --- a/api_docs/kbn_use_tracked_promise.mdx +++ b/api_docs/kbn_use_tracked_promise.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-use-tracked-promise title: "@kbn/use-tracked-promise" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/use-tracked-promise plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/use-tracked-promise'] --- import kbnUseTrackedPromiseObj from './kbn_use_tracked_promise.devdocs.json'; diff --git a/api_docs/kbn_user_profile_components.mdx b/api_docs/kbn_user_profile_components.mdx index b409931f5ceaa..703b2335d2713 100644 --- a/api_docs/kbn_user_profile_components.mdx +++ b/api_docs/kbn_user_profile_components.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-user-profile-components title: "@kbn/user-profile-components" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/user-profile-components plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/user-profile-components'] --- import kbnUserProfileComponentsObj from './kbn_user_profile_components.devdocs.json'; diff --git a/api_docs/kbn_utility_types.mdx b/api_docs/kbn_utility_types.mdx index 1dab8db1c2f10..e83cb47128dcf 100644 --- a/api_docs/kbn_utility_types.mdx +++ b/api_docs/kbn_utility_types.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-utility-types title: "@kbn/utility-types" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/utility-types plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/utility-types'] --- import kbnUtilityTypesObj from './kbn_utility_types.devdocs.json'; diff --git a/api_docs/kbn_utility_types_jest.mdx b/api_docs/kbn_utility_types_jest.mdx index f46a5a488f1e8..328f19b1e14d7 100644 --- a/api_docs/kbn_utility_types_jest.mdx +++ b/api_docs/kbn_utility_types_jest.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-utility-types-jest title: "@kbn/utility-types-jest" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/utility-types-jest plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/utility-types-jest'] --- import kbnUtilityTypesJestObj from './kbn_utility_types_jest.devdocs.json'; diff --git a/api_docs/kbn_utils.mdx b/api_docs/kbn_utils.mdx index 20b1964d3ea0e..53513db53452b 100644 --- a/api_docs/kbn_utils.mdx +++ b/api_docs/kbn_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-utils title: "@kbn/utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/utils plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/utils'] --- import kbnUtilsObj from './kbn_utils.devdocs.json'; diff --git a/api_docs/kbn_visualization_ui_components.mdx b/api_docs/kbn_visualization_ui_components.mdx index 65d9b1569722a..79c705e298f59 100644 --- a/api_docs/kbn_visualization_ui_components.mdx +++ b/api_docs/kbn_visualization_ui_components.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-visualization-ui-components title: "@kbn/visualization-ui-components" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/visualization-ui-components plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/visualization-ui-components'] --- import kbnVisualizationUiComponentsObj from './kbn_visualization_ui_components.devdocs.json'; diff --git a/api_docs/kbn_visualization_utils.mdx b/api_docs/kbn_visualization_utils.mdx index 99188d7510a71..4c2d5e4c1375d 100644 --- a/api_docs/kbn_visualization_utils.mdx +++ b/api_docs/kbn_visualization_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-visualization-utils title: "@kbn/visualization-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/visualization-utils plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/visualization-utils'] --- import kbnVisualizationUtilsObj from './kbn_visualization_utils.devdocs.json'; diff --git a/api_docs/kbn_xstate_utils.mdx b/api_docs/kbn_xstate_utils.mdx index b116894c75b2e..d7d4e98ab47d8 100644 --- a/api_docs/kbn_xstate_utils.mdx +++ b/api_docs/kbn_xstate_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-xstate-utils title: "@kbn/xstate-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/xstate-utils plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/xstate-utils'] --- import kbnXstateUtilsObj from './kbn_xstate_utils.devdocs.json'; diff --git a/api_docs/kbn_yarn_lock_validator.mdx b/api_docs/kbn_yarn_lock_validator.mdx index 478551c179265..465c47332b571 100644 --- a/api_docs/kbn_yarn_lock_validator.mdx +++ b/api_docs/kbn_yarn_lock_validator.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-yarn-lock-validator title: "@kbn/yarn-lock-validator" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/yarn-lock-validator plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/yarn-lock-validator'] --- import kbnYarnLockValidatorObj from './kbn_yarn_lock_validator.devdocs.json'; diff --git a/api_docs/kbn_zod_helpers.mdx b/api_docs/kbn_zod_helpers.mdx index 2687067d0969b..e34032fdd5b84 100644 --- a/api_docs/kbn_zod_helpers.mdx +++ b/api_docs/kbn_zod_helpers.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-zod-helpers title: "@kbn/zod-helpers" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/zod-helpers plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/zod-helpers'] --- import kbnZodHelpersObj from './kbn_zod_helpers.devdocs.json'; diff --git a/api_docs/kibana_overview.mdx b/api_docs/kibana_overview.mdx index fbae1afe9d320..3cf2da03bc6cf 100644 --- a/api_docs/kibana_overview.mdx +++ b/api_docs/kibana_overview.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kibanaOverview title: "kibanaOverview" image: https://source.unsplash.com/400x175/?github description: API docs for the kibanaOverview plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'kibanaOverview'] --- import kibanaOverviewObj from './kibana_overview.devdocs.json'; diff --git a/api_docs/kibana_react.devdocs.json b/api_docs/kibana_react.devdocs.json index 9fb8755ec56d4..a9d4b391e9c1e 100644 --- a/api_docs/kibana_react.devdocs.json +++ b/api_docs/kibana_react.devdocs.json @@ -1117,39 +1117,39 @@ }, { "plugin": "dataVisualizer", - "path": "x-pack/plugins/data_visualizer/public/application/index_data_visualizer/embeddables/grid_embeddable/grid_embeddable.tsx" + "path": "x-pack/plugins/data_visualizer/public/application/index_data_visualizer/index_data_visualizer.tsx" }, { "plugin": "dataVisualizer", - "path": "x-pack/plugins/data_visualizer/public/application/index_data_visualizer/embeddables/grid_embeddable/grid_embeddable.tsx" + "path": "x-pack/plugins/data_visualizer/public/application/index_data_visualizer/index_data_visualizer.tsx" }, { "plugin": "dataVisualizer", - "path": "x-pack/plugins/data_visualizer/public/application/index_data_visualizer/embeddables/grid_embeddable/grid_embeddable.tsx" + "path": "x-pack/plugins/data_visualizer/public/application/index_data_visualizer/index_data_visualizer.tsx" }, { "plugin": "dataVisualizer", - "path": "x-pack/plugins/data_visualizer/public/application/index_data_visualizer/index_data_visualizer.tsx" + "path": "x-pack/plugins/data_visualizer/public/application/data_drift/data_drift_app_state.tsx" }, { "plugin": "dataVisualizer", - "path": "x-pack/plugins/data_visualizer/public/application/index_data_visualizer/index_data_visualizer.tsx" + "path": "x-pack/plugins/data_visualizer/public/application/data_drift/data_drift_app_state.tsx" }, { "plugin": "dataVisualizer", - "path": "x-pack/plugins/data_visualizer/public/application/index_data_visualizer/index_data_visualizer.tsx" + "path": "x-pack/plugins/data_visualizer/public/application/data_drift/data_drift_app_state.tsx" }, { "plugin": "dataVisualizer", - "path": "x-pack/plugins/data_visualizer/public/application/data_drift/data_drift_app_state.tsx" + "path": "x-pack/plugins/data_visualizer/public/application/index_data_visualizer/embeddables/grid_embeddable/grid_embeddable.tsx" }, { "plugin": "dataVisualizer", - "path": "x-pack/plugins/data_visualizer/public/application/data_drift/data_drift_app_state.tsx" + "path": "x-pack/plugins/data_visualizer/public/application/index_data_visualizer/embeddables/grid_embeddable/grid_embeddable.tsx" }, { "plugin": "dataVisualizer", - "path": "x-pack/plugins/data_visualizer/public/application/data_drift/data_drift_app_state.tsx" + "path": "x-pack/plugins/data_visualizer/public/application/index_data_visualizer/embeddables/grid_embeddable/grid_embeddable.tsx" }, { "plugin": "ml", diff --git a/api_docs/kibana_react.mdx b/api_docs/kibana_react.mdx index 61b9ca4837b03..852cb5b64c0f9 100644 --- a/api_docs/kibana_react.mdx +++ b/api_docs/kibana_react.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kibanaReact title: "kibanaReact" image: https://source.unsplash.com/400x175/?github description: API docs for the kibanaReact plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'kibanaReact'] --- import kibanaReactObj from './kibana_react.devdocs.json'; diff --git a/api_docs/kibana_utils.mdx b/api_docs/kibana_utils.mdx index 747b228f832fc..665fa44e7a36d 100644 --- a/api_docs/kibana_utils.mdx +++ b/api_docs/kibana_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kibanaUtils title: "kibanaUtils" image: https://source.unsplash.com/400x175/?github description: API docs for the kibanaUtils plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'kibanaUtils'] --- import kibanaUtilsObj from './kibana_utils.devdocs.json'; diff --git a/api_docs/kubernetes_security.mdx b/api_docs/kubernetes_security.mdx index bcd7f7f2c0150..951873cc65ab2 100644 --- a/api_docs/kubernetes_security.mdx +++ b/api_docs/kubernetes_security.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kubernetesSecurity title: "kubernetesSecurity" image: https://source.unsplash.com/400x175/?github description: API docs for the kubernetesSecurity plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'kubernetesSecurity'] --- import kubernetesSecurityObj from './kubernetes_security.devdocs.json'; diff --git a/api_docs/lens.devdocs.json b/api_docs/lens.devdocs.json index bcc08478559d3..c4a7fd1a51056 100644 --- a/api_docs/lens.devdocs.json +++ b/api_docs/lens.devdocs.json @@ -9417,7 +9417,7 @@ }, "<", "YDomainRange", - " | undefined>; hide?: boolean | undefined; showOverlappingTicks?: boolean | undefined; showOverlappingLabels?: boolean | undefined; timeAxisLayerCount?: number | undefined; integersOnly?: boolean | undefined; tickFormat?: \"ignore\" | undefined; labelFormat?: \"ignore\" | undefined; showDuplicatedTicks?: boolean | undefined; }>> & Partial; hide?: boolean | undefined; showOverlappingTicks?: boolean | undefined; showOverlappingLabels?: boolean | undefined; timeAxisLayerCount?: number | undefined; maximumFractionDigits?: number | undefined; tickFormat?: \"ignore\" | undefined; integersOnly?: boolean | undefined; labelFormat?: \"ignore\" | undefined; showDuplicatedTicks?: boolean | undefined; }>> & Partial; hide?: boolean | undefined; showOverlappingTicks?: boolean | undefined; showOverlappingLabels?: boolean | undefined; timeAxisLayerCount?: number | undefined; integersOnly?: boolean | undefined; tickFormat?: \"ignore\" | undefined; labelFormat?: \"ignore\" | undefined; showDuplicatedTicks?: boolean | undefined; }>> | undefined; }" + " | undefined>; hide?: boolean | undefined; showOverlappingTicks?: boolean | undefined; showOverlappingLabels?: boolean | undefined; timeAxisLayerCount?: number | undefined; maximumFractionDigits?: number | undefined; tickFormat?: \"ignore\" | undefined; integersOnly?: boolean | undefined; labelFormat?: \"ignore\" | undefined; showDuplicatedTicks?: boolean | undefined; }>> | undefined; }" ], "path": "x-pack/plugins/lens/public/embeddable/embeddable_component.tsx", "deprecated": false, diff --git a/api_docs/lens.mdx b/api_docs/lens.mdx index a8a3924cf1934..d68d9ba51c392 100644 --- a/api_docs/lens.mdx +++ b/api_docs/lens.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/lens title: "lens" image: https://source.unsplash.com/400x175/?github description: API docs for the lens plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'lens'] --- import lensObj from './lens.devdocs.json'; diff --git a/api_docs/license_api_guard.mdx b/api_docs/license_api_guard.mdx index e5e59d2dcd564..8c7ceb04b53f0 100644 --- a/api_docs/license_api_guard.mdx +++ b/api_docs/license_api_guard.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/licenseApiGuard title: "licenseApiGuard" image: https://source.unsplash.com/400x175/?github description: API docs for the licenseApiGuard plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'licenseApiGuard'] --- import licenseApiGuardObj from './license_api_guard.devdocs.json'; diff --git a/api_docs/license_management.mdx b/api_docs/license_management.mdx index dc340d6424012..dcf42d3145867 100644 --- a/api_docs/license_management.mdx +++ b/api_docs/license_management.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/licenseManagement title: "licenseManagement" image: https://source.unsplash.com/400x175/?github description: API docs for the licenseManagement plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'licenseManagement'] --- import licenseManagementObj from './license_management.devdocs.json'; diff --git a/api_docs/licensing.mdx b/api_docs/licensing.mdx index 7549e9d23daf7..3dfd027920243 100644 --- a/api_docs/licensing.mdx +++ b/api_docs/licensing.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/licensing title: "licensing" image: https://source.unsplash.com/400x175/?github description: API docs for the licensing plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'licensing'] --- import licensingObj from './licensing.devdocs.json'; diff --git a/api_docs/links.mdx b/api_docs/links.mdx index b1e2e9d136cf7..38e9723aba3e9 100644 --- a/api_docs/links.mdx +++ b/api_docs/links.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/links title: "links" image: https://source.unsplash.com/400x175/?github description: API docs for the links plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'links'] --- import linksObj from './links.devdocs.json'; diff --git a/api_docs/lists.mdx b/api_docs/lists.mdx index 34fb4e71ea6fa..3b9b06826d55e 100644 --- a/api_docs/lists.mdx +++ b/api_docs/lists.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/lists title: "lists" image: https://source.unsplash.com/400x175/?github description: API docs for the lists plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'lists'] --- import listsObj from './lists.devdocs.json'; diff --git a/api_docs/logs_explorer.mdx b/api_docs/logs_explorer.mdx index 2901275c255d9..74c38d73c446c 100644 --- a/api_docs/logs_explorer.mdx +++ b/api_docs/logs_explorer.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/logsExplorer title: "logsExplorer" image: https://source.unsplash.com/400x175/?github description: API docs for the logsExplorer plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'logsExplorer'] --- import logsExplorerObj from './logs_explorer.devdocs.json'; diff --git a/api_docs/logs_shared.mdx b/api_docs/logs_shared.mdx index 9e15427c53c96..be45ec18b4d9c 100644 --- a/api_docs/logs_shared.mdx +++ b/api_docs/logs_shared.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/logsShared title: "logsShared" image: https://source.unsplash.com/400x175/?github description: API docs for the logsShared plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'logsShared'] --- import logsSharedObj from './logs_shared.devdocs.json'; diff --git a/api_docs/management.mdx b/api_docs/management.mdx index aada0a773d9fd..1853c61d36c89 100644 --- a/api_docs/management.mdx +++ b/api_docs/management.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/management title: "management" image: https://source.unsplash.com/400x175/?github description: API docs for the management plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'management'] --- import managementObj from './management.devdocs.json'; diff --git a/api_docs/maps.mdx b/api_docs/maps.mdx index 658a0200a2ad9..bf6cca9c0220b 100644 --- a/api_docs/maps.mdx +++ b/api_docs/maps.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/maps title: "maps" image: https://source.unsplash.com/400x175/?github description: API docs for the maps plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'maps'] --- import mapsObj from './maps.devdocs.json'; diff --git a/api_docs/maps_ems.mdx b/api_docs/maps_ems.mdx index fa5af1061f062..c81b7fb20cd3e 100644 --- a/api_docs/maps_ems.mdx +++ b/api_docs/maps_ems.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/mapsEms title: "mapsEms" image: https://source.unsplash.com/400x175/?github description: API docs for the mapsEms plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'mapsEms'] --- import mapsEmsObj from './maps_ems.devdocs.json'; diff --git a/api_docs/metrics_data_access.mdx b/api_docs/metrics_data_access.mdx index dc6ebbef07ae1..dcd42f6612ae5 100644 --- a/api_docs/metrics_data_access.mdx +++ b/api_docs/metrics_data_access.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/metricsDataAccess title: "metricsDataAccess" image: https://source.unsplash.com/400x175/?github description: API docs for the metricsDataAccess plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'metricsDataAccess'] --- import metricsDataAccessObj from './metrics_data_access.devdocs.json'; diff --git a/api_docs/ml.mdx b/api_docs/ml.mdx index c403defdf8460..161e09a409e62 100644 --- a/api_docs/ml.mdx +++ b/api_docs/ml.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/ml title: "ml" image: https://source.unsplash.com/400x175/?github description: API docs for the ml plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'ml'] --- import mlObj from './ml.devdocs.json'; diff --git a/api_docs/mock_idp_plugin.mdx b/api_docs/mock_idp_plugin.mdx index d2e27241a5f3a..272c06bd1f499 100644 --- a/api_docs/mock_idp_plugin.mdx +++ b/api_docs/mock_idp_plugin.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/mockIdpPlugin title: "mockIdpPlugin" image: https://source.unsplash.com/400x175/?github description: API docs for the mockIdpPlugin plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'mockIdpPlugin'] --- import mockIdpPluginObj from './mock_idp_plugin.devdocs.json'; diff --git a/api_docs/monitoring.mdx b/api_docs/monitoring.mdx index da9ebe13de867..71fccdc5a5a71 100644 --- a/api_docs/monitoring.mdx +++ b/api_docs/monitoring.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/monitoring title: "monitoring" image: https://source.unsplash.com/400x175/?github description: API docs for the monitoring plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'monitoring'] --- import monitoringObj from './monitoring.devdocs.json'; diff --git a/api_docs/monitoring_collection.mdx b/api_docs/monitoring_collection.mdx index 3dd9cfd7926ee..930e6fa11ba0b 100644 --- a/api_docs/monitoring_collection.mdx +++ b/api_docs/monitoring_collection.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/monitoringCollection title: "monitoringCollection" image: https://source.unsplash.com/400x175/?github description: API docs for the monitoringCollection plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'monitoringCollection'] --- import monitoringCollectionObj from './monitoring_collection.devdocs.json'; diff --git a/api_docs/navigation.mdx b/api_docs/navigation.mdx index d74e4fd4c2223..0ade920c880a0 100644 --- a/api_docs/navigation.mdx +++ b/api_docs/navigation.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/navigation title: "navigation" image: https://source.unsplash.com/400x175/?github description: API docs for the navigation plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'navigation'] --- import navigationObj from './navigation.devdocs.json'; diff --git a/api_docs/newsfeed.mdx b/api_docs/newsfeed.mdx index ba04a943451aa..37daddf91afe1 100644 --- a/api_docs/newsfeed.mdx +++ b/api_docs/newsfeed.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/newsfeed title: "newsfeed" image: https://source.unsplash.com/400x175/?github description: API docs for the newsfeed plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'newsfeed'] --- import newsfeedObj from './newsfeed.devdocs.json'; diff --git a/api_docs/no_data_page.mdx b/api_docs/no_data_page.mdx index ae8af3267fb1e..9a11656e9f451 100644 --- a/api_docs/no_data_page.mdx +++ b/api_docs/no_data_page.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/noDataPage title: "noDataPage" image: https://source.unsplash.com/400x175/?github description: API docs for the noDataPage plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'noDataPage'] --- import noDataPageObj from './no_data_page.devdocs.json'; diff --git a/api_docs/notifications.mdx b/api_docs/notifications.mdx index 59137e93aff24..ae5f766b061a3 100644 --- a/api_docs/notifications.mdx +++ b/api_docs/notifications.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/notifications title: "notifications" image: https://source.unsplash.com/400x175/?github description: API docs for the notifications plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'notifications'] --- import notificationsObj from './notifications.devdocs.json'; diff --git a/api_docs/observability.mdx b/api_docs/observability.mdx index a74ead68cdb9f..174f02cbd2352 100644 --- a/api_docs/observability.mdx +++ b/api_docs/observability.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/observability title: "observability" image: https://source.unsplash.com/400x175/?github description: API docs for the observability plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'observability'] --- import observabilityObj from './observability.devdocs.json'; diff --git a/api_docs/observability_a_i_assistant.mdx b/api_docs/observability_a_i_assistant.mdx index b6dc0a9d9f3c9..53c268fd94fc0 100644 --- a/api_docs/observability_a_i_assistant.mdx +++ b/api_docs/observability_a_i_assistant.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/observabilityAIAssistant title: "observabilityAIAssistant" image: https://source.unsplash.com/400x175/?github description: API docs for the observabilityAIAssistant plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'observabilityAIAssistant'] --- import observabilityAIAssistantObj from './observability_a_i_assistant.devdocs.json'; diff --git a/api_docs/observability_logs_explorer.mdx b/api_docs/observability_logs_explorer.mdx index f9ef963f922dd..ce05bdbe3a4b0 100644 --- a/api_docs/observability_logs_explorer.mdx +++ b/api_docs/observability_logs_explorer.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/observabilityLogsExplorer title: "observabilityLogsExplorer" image: https://source.unsplash.com/400x175/?github description: API docs for the observabilityLogsExplorer plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'observabilityLogsExplorer'] --- import observabilityLogsExplorerObj from './observability_logs_explorer.devdocs.json'; diff --git a/api_docs/observability_onboarding.mdx b/api_docs/observability_onboarding.mdx index 2a2615e0e2924..1c72a50702b28 100644 --- a/api_docs/observability_onboarding.mdx +++ b/api_docs/observability_onboarding.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/observabilityOnboarding title: "observabilityOnboarding" image: https://source.unsplash.com/400x175/?github description: API docs for the observabilityOnboarding plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'observabilityOnboarding'] --- import observabilityOnboardingObj from './observability_onboarding.devdocs.json'; diff --git a/api_docs/observability_shared.mdx b/api_docs/observability_shared.mdx index 7a2c5654c517b..ed7fad09f17dc 100644 --- a/api_docs/observability_shared.mdx +++ b/api_docs/observability_shared.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/observabilityShared title: "observabilityShared" image: https://source.unsplash.com/400x175/?github description: API docs for the observabilityShared plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'observabilityShared'] --- import observabilitySharedObj from './observability_shared.devdocs.json'; diff --git a/api_docs/osquery.mdx b/api_docs/osquery.mdx index 0baa7ca918baf..e457c3b56228e 100644 --- a/api_docs/osquery.mdx +++ b/api_docs/osquery.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/osquery title: "osquery" image: https://source.unsplash.com/400x175/?github description: API docs for the osquery plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'osquery'] --- import osqueryObj from './osquery.devdocs.json'; diff --git a/api_docs/painless_lab.mdx b/api_docs/painless_lab.mdx index 1251bcf69aba7..e01d04d71fb95 100644 --- a/api_docs/painless_lab.mdx +++ b/api_docs/painless_lab.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/painlessLab title: "painlessLab" image: https://source.unsplash.com/400x175/?github description: API docs for the painlessLab plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'painlessLab'] --- import painlessLabObj from './painless_lab.devdocs.json'; diff --git a/api_docs/plugin_directory.mdx b/api_docs/plugin_directory.mdx index d8021371d7b49..b194e1b5b27d8 100644 --- a/api_docs/plugin_directory.mdx +++ b/api_docs/plugin_directory.mdx @@ -7,7 +7,7 @@ id: kibDevDocsPluginDirectory slug: /kibana-dev-docs/api-meta/plugin-api-directory title: Directory description: Directory of public APIs available through plugins or packages. -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana'] --- @@ -21,7 +21,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | API Count | Any Count | Missing comments | Missing exports | |--------------|----------|-----------------|--------| -| 79848 | 228 | 68394 | 1740 | +| 79878 | 228 | 68417 | 1740 | ## Plugin Directory @@ -57,7 +57,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [@elastic/fleet](https://github.com/orgs/elastic/teams/fleet) | Add custom data integrations so they can be displayed in the Fleet integrations app | 268 | 0 | 249 | 1 | | | [@elastic/kibana-presentation](https://github.com/orgs/elastic/teams/kibana-presentation) | Adds the Dashboard app to Kibana | 108 | 0 | 105 | 12 | | | [@elastic/kibana-presentation](https://github.com/orgs/elastic/teams/kibana-presentation) | - | 54 | 0 | 51 | 0 | -| | [@elastic/kibana-visualizations](https://github.com/orgs/elastic/teams/kibana-visualizations) | Data services are useful for searching and querying data from Elasticsearch. Helpful utilities include: a re-usable react query bar, KQL autocomplete, async search, Data Views (Index Patterns) and field formatters. | 3237 | 31 | 2585 | 23 | +| | [@elastic/kibana-visualizations](https://github.com/orgs/elastic/teams/kibana-visualizations) | Data services are useful for searching and querying data from Elasticsearch. Helpful utilities include: a re-usable react query bar, KQL autocomplete, async search, Data Views (Index Patterns) and field formatters. | 3239 | 31 | 2587 | 23 | | | [@elastic/kibana-data-discovery](https://github.com/orgs/elastic/teams/kibana-data-discovery) | This plugin provides the ability to create data views via a modal flyout inside Kibana apps | 35 | 0 | 25 | 5 | | | [@elastic/kibana-data-discovery](https://github.com/orgs/elastic/teams/kibana-data-discovery) | Reusable data view field editor across Kibana | 72 | 0 | 33 | 0 | | | [@elastic/kibana-data-discovery](https://github.com/orgs/elastic/teams/kibana-data-discovery) | Data view management app | 2 | 0 | 2 | 0 | @@ -65,12 +65,12 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [@elastic/ml-ui](https://github.com/orgs/elastic/teams/ml-ui) | The Data Visualizer tools help you understand your data, by analyzing the metrics and fields in a log file or an existing Elasticsearch index. | 31 | 3 | 25 | 1 | | | [@elastic/obs-ux-logs-team](https://github.com/orgs/elastic/teams/obs-ux-logs-team) | This plugin introduces the concept of dataset quality, where users can easily get an overview on the datasets they have. | 10 | 0 | 10 | 5 | | | [@elastic/platform-deployment-management](https://github.com/orgs/elastic/teams/platform-deployment-management) | - | 16 | 0 | 10 | 3 | -| | [@elastic/kibana-data-discovery](https://github.com/orgs/elastic/teams/kibana-data-discovery) | This plugin contains the Discover application and the saved search embeddable. | 156 | 0 | 109 | 23 | +| | [@elastic/kibana-data-discovery](https://github.com/orgs/elastic/teams/kibana-data-discovery) | This plugin contains the Discover application and the saved search embeddable. | 157 | 0 | 109 | 23 | | | [@elastic/kibana-data-discovery](https://github.com/orgs/elastic/teams/kibana-data-discovery) | - | 37 | 0 | 35 | 2 | | | [@elastic/security-threat-hunting-explore](https://github.com/orgs/elastic/teams/security-threat-hunting-explore) | APIs used to assess the quality of data in Elasticsearch indexes | 2 | 0 | 0 | 0 | | | [@elastic/security-generative-ai](https://github.com/orgs/elastic/teams/security-generative-ai) | Server APIs for the Elastic AI Assistant | 41 | 0 | 27 | 0 | | | [@elastic/kibana-presentation](https://github.com/orgs/elastic/teams/kibana-presentation) | Adds embeddables service to Kibana | 562 | 1 | 457 | 8 | -| | [@elastic/kibana-presentation](https://github.com/orgs/elastic/teams/kibana-presentation) | Extends embeddable plugin with more functionality | 14 | 0 | 14 | 0 | +| | [@elastic/kibana-presentation](https://github.com/orgs/elastic/teams/kibana-presentation) | Extends embeddable plugin with more functionality | 18 | 0 | 18 | 0 | | | [@elastic/kibana-security](https://github.com/orgs/elastic/teams/kibana-security) | This plugin provides encryption and decryption utilities for saved objects containing sensitive information. | 53 | 0 | 46 | 1 | | | [@elastic/enterprise-search-frontend](https://github.com/orgs/elastic/teams/enterprise-search-frontend) | Adds dashboards for discovering and managing Enterprise Search products. | 5 | 0 | 5 | 0 | | | [@elastic/platform-deployment-management](https://github.com/orgs/elastic/teams/platform-deployment-management) | - | 99 | 3 | 97 | 3 | @@ -97,9 +97,9 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [@elastic/kibana-gis](https://github.com/orgs/elastic/teams/kibana-gis) | The file upload plugin contains components and services for uploading a file, analyzing its data, and then importing the data into an Elasticsearch index. Supported file types include CSV, TSV, newline-delimited JSON and GeoJSON. | 84 | 0 | 84 | 8 | | | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | File upload, download, sharing, and serving over HTTP implementation in Kibana. | 240 | 0 | 24 | 9 | | | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | Simple UI for managing files in Kibana | 2 | 0 | 2 | 0 | -| | [@elastic/fleet](https://github.com/orgs/elastic/teams/fleet) | - | 1229 | 3 | 1110 | 54 | +| | [@elastic/fleet](https://github.com/orgs/elastic/teams/fleet) | - | 1226 | 3 | 1107 | 54 | | ftrApis | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 0 | 0 | 0 | 0 | -| | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | - | 68 | 0 | 14 | 5 | +| | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | - | 72 | 0 | 14 | 5 | | globalSearchBar | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | - | 0 | 0 | 0 | 0 | | globalSearchProviders | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | - | 0 | 0 | 0 | 0 | | graph | [@elastic/kibana-visualizations](https://github.com/orgs/elastic/teams/kibana-visualizations) | - | 0 | 0 | 0 | 0 | @@ -161,7 +161,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 164 | 0 | 150 | 2 | | | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | - | 87 | 0 | 81 | 3 | | | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | - | 104 | 0 | 56 | 1 | -| | [@elastic/kibana-data-discovery](https://github.com/orgs/elastic/teams/kibana-data-discovery) | This plugin contains the definition and helper methods around saved searches, used by discover and visualizations. | 78 | 0 | 77 | 3 | +| | [@elastic/kibana-data-discovery](https://github.com/orgs/elastic/teams/kibana-data-discovery) | This plugin contains the definition and helper methods around saved searches, used by discover and visualizations. | 79 | 0 | 78 | 3 | | | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | - | 32 | 0 | 13 | 0 | | | [@elastic/kibana-reporting-services](https://github.com/orgs/elastic/teams/kibana-reporting-services) | Kibana Screenshotting Plugin | 32 | 0 | 8 | 4 | | searchprofiler | [@elastic/platform-deployment-management](https://github.com/orgs/elastic/teams/platform-deployment-management) | - | 0 | 0 | 0 | 0 | @@ -189,12 +189,12 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [@elastic/security-threat-hunting-investigations](https://github.com/orgs/elastic/teams/security-threat-hunting-investigations) | - | 240 | 1 | 196 | 17 | | | [@elastic/ml-ui](https://github.com/orgs/elastic/teams/ml-ui) | This plugin provides access to the transforms features provided by Elastic. Transforms enable you to convert existing Elasticsearch indices into summarized indices, which provide opportunities for new insights and analytics. | 4 | 0 | 4 | 1 | | translations | [@elastic/kibana-localization](https://github.com/orgs/elastic/teams/kibana-localization) | - | 0 | 0 | 0 | 0 | -| | [@elastic/response-ops](https://github.com/orgs/elastic/teams/response-ops) | - | 596 | 1 | 570 | 58 | +| | [@elastic/response-ops](https://github.com/orgs/elastic/teams/response-ops) | - | 597 | 1 | 571 | 58 | | | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | Adds UI Actions service to Kibana | 147 | 0 | 101 | 9 | | | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | Extends UI Actions plugin with more functionality | 212 | 0 | 145 | 11 | | | [@elastic/kibana-data-discovery](https://github.com/orgs/elastic/teams/kibana-data-discovery) | This plugin contains services reliant on the plugin lifecycle for the unified doc viewer component (see @kbn/unified-doc-viewer). | 10 | 0 | 7 | 2 | | | [@elastic/kibana-data-discovery](https://github.com/orgs/elastic/teams/kibana-data-discovery) | The `unifiedHistogram` plugin provides UI components to create a layout including a resizable histogram and a main display. | 55 | 0 | 23 | 2 | -| | [@elastic/kibana-visualizations](https://github.com/orgs/elastic/teams/kibana-visualizations) | Contains all the key functionality of Kibana's unified search experience.Contains all the key functionality of Kibana's unified search experience. | 148 | 2 | 110 | 23 | +| | [@elastic/kibana-visualizations](https://github.com/orgs/elastic/teams/kibana-visualizations) | Contains all the key functionality of Kibana's unified search experience.Contains all the key functionality of Kibana's unified search experience. | 151 | 2 | 113 | 23 | | upgradeAssistant | [@elastic/platform-deployment-management](https://github.com/orgs/elastic/teams/platform-deployment-management) | - | 0 | 0 | 0 | 0 | | | [@elastic/obs-ux-infra_services-team](https://github.com/orgs/elastic/teams/obs-ux-infra_services-team) | This plugin visualizes data from Heartbeat, and integrates with other Observability solutions. | 1 | 0 | 1 | 0 | | urlDrilldown | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | Adds drilldown implementations to Kibana | 0 | 0 | 0 | 0 | @@ -557,7 +557,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [@elastic/kibana-operations](https://github.com/orgs/elastic/teams/kibana-operations) | - | 1 | 0 | 1 | 0 | | | [@elastic/kibana-presentation](https://github.com/orgs/elastic/teams/kibana-presentation) | - | 42 | 0 | 40 | 0 | | | [@elastic/kibana-presentation](https://github.com/orgs/elastic/teams/kibana-presentation) | - | 10 | 0 | 10 | 0 | -| | [@elastic/kibana-presentation](https://github.com/orgs/elastic/teams/kibana-presentation) | - | 150 | 0 | 114 | 3 | +| | [@elastic/kibana-presentation](https://github.com/orgs/elastic/teams/kibana-presentation) | - | 154 | 0 | 118 | 3 | | | [@elastic/obs-ux-infra_services-team](https://github.com/orgs/elastic/teams/obs-ux-infra_services-team) | - | 156 | 0 | 45 | 0 | | | [@elastic/kibana-visualizations](https://github.com/orgs/elastic/teams/kibana-visualizations) | - | 13 | 0 | 7 | 0 | | | [@elastic/kibana-data-discovery](https://github.com/orgs/elastic/teams/kibana-data-discovery) | - | 22 | 0 | 9 | 0 | @@ -684,7 +684,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | - | 42 | 0 | 28 | 0 | | | [@elastic/kibana-operations](https://github.com/orgs/elastic/teams/kibana-operations) | - | 55 | 0 | 46 | 0 | | | [@elastic/kibana-operations](https://github.com/orgs/elastic/teams/kibana-operations) | - | 7 | 0 | 6 | 0 | -| | [@elastic/kibana-data-discovery](https://github.com/orgs/elastic/teams/kibana-data-discovery) | Contains functionality for the unified data table which can be integrated into apps | 130 | 0 | 67 | 1 | +| | [@elastic/kibana-data-discovery](https://github.com/orgs/elastic/teams/kibana-data-discovery) | Contains functionality for the unified data table which can be integrated into apps | 143 | 0 | 78 | 1 | | | [@elastic/kibana-data-discovery](https://github.com/orgs/elastic/teams/kibana-data-discovery) | - | 14 | 0 | 13 | 6 | | | [@elastic/kibana-data-discovery](https://github.com/orgs/elastic/teams/kibana-data-discovery) | Contains functionality for the field list and field stats which can be integrated into apps | 291 | 0 | 267 | 10 | | | [@elastic/kibana-data-discovery](https://github.com/orgs/elastic/teams/kibana-data-discovery) | - | 13 | 0 | 9 | 0 | diff --git a/api_docs/presentation_panel.devdocs.json b/api_docs/presentation_panel.devdocs.json index 8c68c3a16831e..c876b5f958af5 100644 --- a/api_docs/presentation_panel.devdocs.json +++ b/api_docs/presentation_panel.devdocs.json @@ -117,7 +117,7 @@ "section": "def-common.PanelPackage", "text": "PanelPackage" }, - ") => Promise; } & Partial Promise; getChildIds: () => string[]; getChild: (childId: string) => unknown; } & Partial Promise; } & Partial Promise; getChildIds: () => string[]; getChild: (childId: string) => unknown; } & Partial Promise; } & Partial Promise; getChildIds: () => string[]; getChild: (childId: string) => unknown; } & Partial Promise; } & Partial Promise; getChildIds: () => string[]; getChild: (childId: string) => unknown; } & Partial | undefined; frequency?: { throttle: string | null; notifyWhen: \"onActionGroupChange\" | \"onActiveAlert\" | \"onThrottleInterval\"; summary: boolean; } | undefined; }[]; tags: string[]; setup: string; enabled: boolean; revision: number; version: number; references: string[]; interval: string; query: string; description: string; risk_score: number; severity: \"medium\" | \"high\" | \"low\" | \"critical\"; from: string; to: string; language: \"eql\"; created_at: string; created_by: string; updated_at: string; updated_by: string; author: string[]; immutable: boolean; rule_id: string; threat: { framework: string; tactic: { id: string; name: string; reference: string; }; technique?: { id: string; name: string; reference: string; subtechnique?: { id: string; name: string; reference: string; }[] | undefined; }[] | undefined; }[]; risk_score_mapping: { value: string; field: string; operator: \"equals\"; risk_score?: number | undefined; }[]; severity_mapping: { value: string; field: string; severity: \"medium\" | \"high\" | \"low\" | \"critical\"; operator: \"equals\"; }[]; exceptions_list: { type: \"endpoint\" | \"detection\" | \"rule_default\" | \"endpoint_trusted_apps\" | \"endpoint_events\" | \"endpoint_host_isolation_exceptions\" | \"endpoint_blocklists\"; id: string; list_id: string; namespace_type: \"single\" | \"agnostic\"; }[]; false_positives: string[]; max_signals: number; related_integrations: { version: string; package: string; integration?: string | undefined; }[]; required_fields: { type: string; name: string; ecs: boolean; }[]; license?: string | undefined; throttle?: string | undefined; outcome?: \"exactMatch\" | \"aliasMatch\" | \"conflict\" | undefined; alias_target_id?: string | undefined; alias_purpose?: \"savedObjectConversion\" | \"savedObjectImport\" | undefined; meta?: Zod.objectOutputType<{}, Zod.ZodUnknown, \"strip\"> | undefined; namespace?: string | undefined; note?: string | undefined; rule_name_override?: string | undefined; timestamp_override?: string | undefined; timestamp_override_fallback_disabled?: boolean | undefined; timeline_id?: string | undefined; timeline_title?: string | undefined; building_block_type?: string | undefined; output_index?: string | undefined; investigation_fields?: { field_names: string[]; } | undefined; execution_summary?: { last_execution: { message: string; date: string; status: \"running\" | \"succeeded\" | \"failed\" | \"going to run\" | \"partial failure\"; metrics: { total_search_duration_ms?: number | undefined; total_indexing_duration_ms?: number | undefined; total_enrichment_duration_ms?: number | undefined; execution_gap_duration_s?: number | undefined; }; status_order: number; }; } | undefined; index?: string[] | undefined; data_view_id?: string | undefined; filters?: unknown[] | undefined; event_category_override?: string | undefined; tiebreaker_field?: string | undefined; timestamp_field?: string | undefined; } | { type: \"query\"; id: string; name: string; actions: { id: string; params: {} & { [k: string]: unknown; }; group: string; action_type_id: string; uuid?: string | undefined; alerts_filter?: Zod.objectOutputType<{}, Zod.ZodUnknown, \"strip\"> | undefined; frequency?: { throttle: string | null; notifyWhen: \"onActionGroupChange\" | \"onActiveAlert\" | \"onThrottleInterval\"; summary: boolean; } | undefined; }[]; tags: string[]; setup: string; enabled: boolean; revision: number; version: number; references: string[]; interval: string; query: string; description: string; risk_score: number; severity: \"medium\" | \"high\" | \"low\" | \"critical\"; from: string; to: string; language: \"kuery\" | \"lucene\"; created_at: string; created_by: string; updated_at: string; updated_by: string; author: string[]; immutable: boolean; rule_id: string; threat: { framework: string; tactic: { id: string; name: string; reference: string; }; technique?: { id: string; name: string; reference: string; subtechnique?: { id: string; name: string; reference: string; }[] | undefined; }[] | undefined; }[]; risk_score_mapping: { value: string; field: string; operator: \"equals\"; risk_score?: number | undefined; }[]; severity_mapping: { value: string; field: string; severity: \"medium\" | \"high\" | \"low\" | \"critical\"; operator: \"equals\"; }[]; exceptions_list: { type: \"endpoint\" | \"detection\" | \"rule_default\" | \"endpoint_trusted_apps\" | \"endpoint_events\" | \"endpoint_host_isolation_exceptions\" | \"endpoint_blocklists\"; id: string; list_id: string; namespace_type: \"single\" | \"agnostic\"; }[]; false_positives: string[]; max_signals: number; related_integrations: { version: string; package: string; integration?: string | undefined; }[]; required_fields: { type: string; name: string; ecs: boolean; }[]; license?: string | undefined; throttle?: string | undefined; outcome?: \"exactMatch\" | \"aliasMatch\" | \"conflict\" | undefined; alias_target_id?: string | undefined; alias_purpose?: \"savedObjectConversion\" | \"savedObjectImport\" | undefined; meta?: Zod.objectOutputType<{}, Zod.ZodUnknown, \"strip\"> | undefined; namespace?: string | undefined; note?: string | undefined; rule_name_override?: string | undefined; timestamp_override?: string | undefined; timestamp_override_fallback_disabled?: boolean | undefined; timeline_id?: string | undefined; timeline_title?: string | undefined; building_block_type?: string | undefined; output_index?: string | undefined; investigation_fields?: { field_names: string[]; } | undefined; execution_summary?: { last_execution: { message: string; date: string; status: \"running\" | \"succeeded\" | \"failed\" | \"going to run\" | \"partial failure\"; metrics: { total_search_duration_ms?: number | undefined; total_indexing_duration_ms?: number | undefined; total_enrichment_duration_ms?: number | undefined; execution_gap_duration_s?: number | undefined; }; status_order: number; }; } | undefined; index?: string[] | undefined; filters?: unknown[] | undefined; data_view_id?: string | undefined; saved_id?: string | undefined; response_actions?: ({ params: { query?: string | undefined; ecs_mapping?: Zod.objectOutputType<{}, Zod.ZodObject<{ field: Zod.ZodOptional; value: Zod.ZodOptional]>>; }, \"strip\", Zod.ZodTypeAny, { field?: string | undefined; value?: string | string[] | undefined; }, { field?: string | undefined; value?: string | string[] | undefined; }>, \"strip\"> | undefined; queries?: { id: string; query: string; ecs_mapping?: Zod.objectOutputType<{}, Zod.ZodObject<{ field: Zod.ZodOptional; value: Zod.ZodOptional]>>; }, \"strip\", Zod.ZodTypeAny, { field?: string | undefined; value?: string | string[] | undefined; }, { field?: string | undefined; value?: string | string[] | undefined; }>, \"strip\"> | undefined; version?: string | undefined; platform?: string | undefined; removed?: boolean | undefined; snapshot?: boolean | undefined; }[] | undefined; pack_id?: string | undefined; saved_query_id?: string | undefined; timeout?: number | undefined; }; action_type_id: \".osquery\"; } | { params: { command: \"isolate\"; comment?: string | undefined; } | { config: { field: string; overwrite: boolean; }; command: \"kill-process\" | \"suspend-process\"; comment?: string | undefined; }; action_type_id: \".endpoint\"; })[] | undefined; alert_suppression?: { group_by: string[]; duration?: { value: number; unit: \"m\" | \"h\" | \"s\"; } | undefined; missing_fields_strategy?: \"doNotSuppress\" | \"suppress\" | undefined; } | undefined; } | { type: \"saved_query\"; id: string; name: string; actions: { id: string; params: {} & { [k: string]: unknown; }; group: string; action_type_id: string; uuid?: string | undefined; alerts_filter?: Zod.objectOutputType<{}, Zod.ZodUnknown, \"strip\"> | undefined; frequency?: { throttle: string | null; notifyWhen: \"onActionGroupChange\" | \"onActiveAlert\" | \"onThrottleInterval\"; summary: boolean; } | undefined; }[]; tags: string[]; setup: string; enabled: boolean; revision: number; version: number; references: string[]; interval: string; description: string; risk_score: number; severity: \"medium\" | \"high\" | \"low\" | \"critical\"; from: string; to: string; language: \"kuery\" | \"lucene\"; created_at: string; created_by: string; updated_at: string; updated_by: string; author: string[]; immutable: boolean; rule_id: string; threat: { framework: string; tactic: { id: string; name: string; reference: string; }; technique?: { id: string; name: string; reference: string; subtechnique?: { id: string; name: string; reference: string; }[] | undefined; }[] | undefined; }[]; risk_score_mapping: { value: string; field: string; operator: \"equals\"; risk_score?: number | undefined; }[]; severity_mapping: { value: string; field: string; severity: \"medium\" | \"high\" | \"low\" | \"critical\"; operator: \"equals\"; }[]; exceptions_list: { type: \"endpoint\" | \"detection\" | \"rule_default\" | \"endpoint_trusted_apps\" | \"endpoint_events\" | \"endpoint_host_isolation_exceptions\" | \"endpoint_blocklists\"; id: string; list_id: string; namespace_type: \"single\" | \"agnostic\"; }[]; false_positives: string[]; max_signals: number; related_integrations: { version: string; package: string; integration?: string | undefined; }[]; required_fields: { type: string; name: string; ecs: boolean; }[]; saved_id: string; license?: string | undefined; throttle?: string | undefined; outcome?: \"exactMatch\" | \"aliasMatch\" | \"conflict\" | undefined; alias_target_id?: string | undefined; alias_purpose?: \"savedObjectConversion\" | \"savedObjectImport\" | undefined; meta?: Zod.objectOutputType<{}, Zod.ZodUnknown, \"strip\"> | undefined; namespace?: string | undefined; note?: string | undefined; rule_name_override?: string | undefined; timestamp_override?: string | undefined; timestamp_override_fallback_disabled?: boolean | undefined; timeline_id?: string | undefined; timeline_title?: string | undefined; building_block_type?: string | undefined; output_index?: string | undefined; investigation_fields?: { field_names: string[]; } | undefined; execution_summary?: { last_execution: { message: string; date: string; status: \"running\" | \"succeeded\" | \"failed\" | \"going to run\" | \"partial failure\"; metrics: { total_search_duration_ms?: number | undefined; total_indexing_duration_ms?: number | undefined; total_enrichment_duration_ms?: number | undefined; execution_gap_duration_s?: number | undefined; }; status_order: number; }; } | undefined; index?: string[] | undefined; query?: string | undefined; filters?: unknown[] | undefined; data_view_id?: string | undefined; response_actions?: ({ params: { query?: string | undefined; ecs_mapping?: Zod.objectOutputType<{}, Zod.ZodObject<{ field: Zod.ZodOptional; value: Zod.ZodOptional]>>; }, \"strip\", Zod.ZodTypeAny, { field?: string | undefined; value?: string | string[] | undefined; }, { field?: string | undefined; value?: string | string[] | undefined; }>, \"strip\"> | undefined; queries?: { id: string; query: string; ecs_mapping?: Zod.objectOutputType<{}, Zod.ZodObject<{ field: Zod.ZodOptional; value: Zod.ZodOptional]>>; }, \"strip\", Zod.ZodTypeAny, { field?: string | undefined; value?: string | string[] | undefined; }, { field?: string | undefined; value?: string | string[] | undefined; }>, \"strip\"> | undefined; version?: string | undefined; platform?: string | undefined; removed?: boolean | undefined; snapshot?: boolean | undefined; }[] | undefined; pack_id?: string | undefined; saved_query_id?: string | undefined; timeout?: number | undefined; }; action_type_id: \".osquery\"; } | { params: { command: \"isolate\"; comment?: string | undefined; } | { config: { field: string; overwrite: boolean; }; command: \"kill-process\" | \"suspend-process\"; comment?: string | undefined; }; action_type_id: \".endpoint\"; })[] | undefined; alert_suppression?: { group_by: string[]; duration?: { value: number; unit: \"m\" | \"h\" | \"s\"; } | undefined; missing_fields_strategy?: \"doNotSuppress\" | \"suppress\" | undefined; } | undefined; } | { type: \"threshold\"; id: string; name: string; actions: { id: string; params: {} & { [k: string]: unknown; }; group: string; action_type_id: string; uuid?: string | undefined; alerts_filter?: Zod.objectOutputType<{}, Zod.ZodUnknown, \"strip\"> | undefined; frequency?: { throttle: string | null; notifyWhen: \"onActionGroupChange\" | \"onActiveAlert\" | \"onThrottleInterval\"; summary: boolean; } | undefined; }[]; tags: string[]; setup: string; enabled: boolean; revision: number; version: number; references: string[]; interval: string; query: string; description: string; risk_score: number; severity: \"medium\" | \"high\" | \"low\" | \"critical\"; threshold: { value: number; field: (string | string[]) & (string | string[] | undefined); cardinality?: { value: number; field: string; }[] | undefined; }; from: string; to: string; language: \"kuery\" | \"lucene\"; created_at: string; created_by: string; updated_at: string; updated_by: string; author: string[]; immutable: boolean; rule_id: string; threat: { framework: string; tactic: { id: string; name: string; reference: string; }; technique?: { id: string; name: string; reference: string; subtechnique?: { id: string; name: string; reference: string; }[] | undefined; }[] | undefined; }[]; risk_score_mapping: { value: string; field: string; operator: \"equals\"; risk_score?: number | undefined; }[]; severity_mapping: { value: string; field: string; severity: \"medium\" | \"high\" | \"low\" | \"critical\"; operator: \"equals\"; }[]; exceptions_list: { type: \"endpoint\" | \"detection\" | \"rule_default\" | \"endpoint_trusted_apps\" | \"endpoint_events\" | \"endpoint_host_isolation_exceptions\" | \"endpoint_blocklists\"; id: string; list_id: string; namespace_type: \"single\" | \"agnostic\"; }[]; false_positives: string[]; max_signals: number; related_integrations: { version: string; package: string; integration?: string | undefined; }[]; required_fields: { type: string; name: string; ecs: boolean; }[]; license?: string | undefined; throttle?: string | undefined; outcome?: \"exactMatch\" | \"aliasMatch\" | \"conflict\" | undefined; alias_target_id?: string | undefined; alias_purpose?: \"savedObjectConversion\" | \"savedObjectImport\" | undefined; meta?: Zod.objectOutputType<{}, Zod.ZodUnknown, \"strip\"> | undefined; namespace?: string | undefined; note?: string | undefined; rule_name_override?: string | undefined; timestamp_override?: string | undefined; timestamp_override_fallback_disabled?: boolean | undefined; timeline_id?: string | undefined; timeline_title?: string | undefined; building_block_type?: string | undefined; output_index?: string | undefined; investigation_fields?: { field_names: string[]; } | undefined; execution_summary?: { last_execution: { message: string; date: string; status: \"running\" | \"succeeded\" | \"failed\" | \"going to run\" | \"partial failure\"; metrics: { total_search_duration_ms?: number | undefined; total_indexing_duration_ms?: number | undefined; total_enrichment_duration_ms?: number | undefined; execution_gap_duration_s?: number | undefined; }; status_order: number; }; } | undefined; index?: string[] | undefined; filters?: unknown[] | undefined; data_view_id?: string | undefined; saved_id?: string | undefined; alert_suppression?: { duration: { value: number; unit: \"m\" | \"h\" | \"s\"; }; } | undefined; } | { type: \"threat_match\"; id: string; name: string; actions: { id: string; params: {} & { [k: string]: unknown; }; group: string; action_type_id: string; uuid?: string | undefined; alerts_filter?: Zod.objectOutputType<{}, Zod.ZodUnknown, \"strip\"> | undefined; frequency?: { throttle: string | null; notifyWhen: \"onActionGroupChange\" | \"onActiveAlert\" | \"onThrottleInterval\"; summary: boolean; } | undefined; }[]; tags: string[]; setup: string; enabled: boolean; revision: number; version: number; references: string[]; interval: string; query: string; description: string; risk_score: number; severity: \"medium\" | \"high\" | \"low\" | \"critical\"; from: string; to: string; language: \"kuery\" | \"lucene\"; created_at: string; created_by: string; updated_at: string; updated_by: string; author: string[]; immutable: boolean; rule_id: string; threat: { framework: string; tactic: { id: string; name: string; reference: string; }; technique?: { id: string; name: string; reference: string; subtechnique?: { id: string; name: string; reference: string; }[] | undefined; }[] | undefined; }[]; risk_score_mapping: { value: string; field: string; operator: \"equals\"; risk_score?: number | undefined; }[]; severity_mapping: { value: string; field: string; severity: \"medium\" | \"high\" | \"low\" | \"critical\"; operator: \"equals\"; }[]; exceptions_list: { type: \"endpoint\" | \"detection\" | \"rule_default\" | \"endpoint_trusted_apps\" | \"endpoint_events\" | \"endpoint_host_isolation_exceptions\" | \"endpoint_blocklists\"; id: string; list_id: string; namespace_type: \"single\" | \"agnostic\"; }[]; false_positives: string[]; max_signals: number; related_integrations: { version: string; package: string; integration?: string | undefined; }[]; required_fields: { type: string; name: string; ecs: boolean; }[]; threat_query: string; threat_mapping: { entries: { type: \"mapping\"; value: string; field: string; }[]; }[]; threat_index: string[]; license?: string | undefined; throttle?: string | undefined; outcome?: \"exactMatch\" | \"aliasMatch\" | \"conflict\" | undefined; alias_target_id?: string | undefined; alias_purpose?: \"savedObjectConversion\" | \"savedObjectImport\" | undefined; meta?: Zod.objectOutputType<{}, Zod.ZodUnknown, \"strip\"> | undefined; namespace?: string | undefined; note?: string | undefined; rule_name_override?: string | undefined; timestamp_override?: string | undefined; timestamp_override_fallback_disabled?: boolean | undefined; timeline_id?: string | undefined; timeline_title?: string | undefined; building_block_type?: string | undefined; output_index?: string | undefined; investigation_fields?: { field_names: string[]; } | undefined; execution_summary?: { last_execution: { message: string; date: string; status: \"running\" | \"succeeded\" | \"failed\" | \"going to run\" | \"partial failure\"; metrics: { total_search_duration_ms?: number | undefined; total_indexing_duration_ms?: number | undefined; total_enrichment_duration_ms?: number | undefined; execution_gap_duration_s?: number | undefined; }; status_order: number; }; } | undefined; index?: string[] | undefined; filters?: unknown[] | undefined; data_view_id?: string | undefined; saved_id?: string | undefined; alert_suppression?: { group_by: string[]; duration?: { value: number; unit: \"m\" | \"h\" | \"s\"; } | undefined; missing_fields_strategy?: \"doNotSuppress\" | \"suppress\" | undefined; } | undefined; threat_filters?: unknown[] | undefined; threat_indicator_path?: string | undefined; threat_language?: \"lucene\" | \"kuery\" | undefined; concurrent_searches?: number | undefined; items_per_search?: number | undefined; } | { type: \"machine_learning\"; id: string; name: string; actions: { id: string; params: {} & { [k: string]: unknown; }; group: string; action_type_id: string; uuid?: string | undefined; alerts_filter?: Zod.objectOutputType<{}, Zod.ZodUnknown, \"strip\"> | undefined; frequency?: { throttle: string | null; notifyWhen: \"onActionGroupChange\" | \"onActiveAlert\" | \"onThrottleInterval\"; summary: boolean; } | undefined; }[]; tags: string[]; setup: string; enabled: boolean; revision: number; version: number; references: string[]; interval: string; description: string; risk_score: number; severity: \"medium\" | \"high\" | \"low\" | \"critical\"; from: string; to: string; created_at: string; created_by: string; updated_at: string; updated_by: string; author: string[]; immutable: boolean; rule_id: string; threat: { framework: string; tactic: { id: string; name: string; reference: string; }; technique?: { id: string; name: string; reference: string; subtechnique?: { id: string; name: string; reference: string; }[] | undefined; }[] | undefined; }[]; risk_score_mapping: { value: string; field: string; operator: \"equals\"; risk_score?: number | undefined; }[]; severity_mapping: { value: string; field: string; severity: \"medium\" | \"high\" | \"low\" | \"critical\"; operator: \"equals\"; }[]; exceptions_list: { type: \"endpoint\" | \"detection\" | \"rule_default\" | \"endpoint_trusted_apps\" | \"endpoint_events\" | \"endpoint_host_isolation_exceptions\" | \"endpoint_blocklists\"; id: string; list_id: string; namespace_type: \"single\" | \"agnostic\"; }[]; false_positives: string[]; max_signals: number; related_integrations: { version: string; package: string; integration?: string | undefined; }[]; required_fields: { type: string; name: string; ecs: boolean; }[]; anomaly_threshold: number; machine_learning_job_id: (string | string[]) & (string | string[] | undefined); license?: string | undefined; throttle?: string | undefined; outcome?: \"exactMatch\" | \"aliasMatch\" | \"conflict\" | undefined; alias_target_id?: string | undefined; alias_purpose?: \"savedObjectConversion\" | \"savedObjectImport\" | undefined; meta?: Zod.objectOutputType<{}, Zod.ZodUnknown, \"strip\"> | undefined; namespace?: string | undefined; note?: string | undefined; rule_name_override?: string | undefined; timestamp_override?: string | undefined; timestamp_override_fallback_disabled?: boolean | undefined; timeline_id?: string | undefined; timeline_title?: string | undefined; building_block_type?: string | undefined; output_index?: string | undefined; investigation_fields?: { field_names: string[]; } | undefined; execution_summary?: { last_execution: { message: string; date: string; status: \"running\" | \"succeeded\" | \"failed\" | \"going to run\" | \"partial failure\"; metrics: { total_search_duration_ms?: number | undefined; total_indexing_duration_ms?: number | undefined; total_enrichment_duration_ms?: number | undefined; execution_gap_duration_s?: number | undefined; }; status_order: number; }; } | undefined; } | { type: \"new_terms\"; id: string; name: string; actions: { id: string; params: {} & { [k: string]: unknown; }; group: string; action_type_id: string; uuid?: string | undefined; alerts_filter?: Zod.objectOutputType<{}, Zod.ZodUnknown, \"strip\"> | undefined; frequency?: { throttle: string | null; notifyWhen: \"onActionGroupChange\" | \"onActiveAlert\" | \"onThrottleInterval\"; summary: boolean; } | undefined; }[]; tags: string[]; setup: string; enabled: boolean; revision: number; version: number; references: string[]; interval: string; query: string; description: string; risk_score: number; severity: \"medium\" | \"high\" | \"low\" | \"critical\"; from: string; to: string; language: \"kuery\" | \"lucene\"; created_at: string; created_by: string; updated_at: string; updated_by: string; author: string[]; immutable: boolean; rule_id: string; threat: { framework: string; tactic: { id: string; name: string; reference: string; }; technique?: { id: string; name: string; reference: string; subtechnique?: { id: string; name: string; reference: string; }[] | undefined; }[] | undefined; }[]; risk_score_mapping: { value: string; field: string; operator: \"equals\"; risk_score?: number | undefined; }[]; severity_mapping: { value: string; field: string; severity: \"medium\" | \"high\" | \"low\" | \"critical\"; operator: \"equals\"; }[]; exceptions_list: { type: \"endpoint\" | \"detection\" | \"rule_default\" | \"endpoint_trusted_apps\" | \"endpoint_events\" | \"endpoint_host_isolation_exceptions\" | \"endpoint_blocklists\"; id: string; list_id: string; namespace_type: \"single\" | \"agnostic\"; }[]; false_positives: string[]; max_signals: number; related_integrations: { version: string; package: string; integration?: string | undefined; }[]; required_fields: { type: string; name: string; ecs: boolean; }[]; new_terms_fields: string[]; history_window_start: string; license?: string | undefined; throttle?: string | undefined; outcome?: \"exactMatch\" | \"aliasMatch\" | \"conflict\" | undefined; alias_target_id?: string | undefined; alias_purpose?: \"savedObjectConversion\" | \"savedObjectImport\" | undefined; meta?: Zod.objectOutputType<{}, Zod.ZodUnknown, \"strip\"> | undefined; namespace?: string | undefined; note?: string | undefined; rule_name_override?: string | undefined; timestamp_override?: string | undefined; timestamp_override_fallback_disabled?: boolean | undefined; timeline_id?: string | undefined; timeline_title?: string | undefined; building_block_type?: string | undefined; output_index?: string | undefined; investigation_fields?: { field_names: string[]; } | undefined; execution_summary?: { last_execution: { message: string; date: string; status: \"running\" | \"succeeded\" | \"failed\" | \"going to run\" | \"partial failure\"; metrics: { total_search_duration_ms?: number | undefined; total_indexing_duration_ms?: number | undefined; total_enrichment_duration_ms?: number | undefined; execution_gap_duration_s?: number | undefined; }; status_order: number; }; } | undefined; index?: string[] | undefined; filters?: unknown[] | undefined; data_view_id?: string | undefined; } | { type: \"esql\"; id: string; name: string; actions: { id: string; params: {} & { [k: string]: unknown; }; group: string; action_type_id: string; uuid?: string | undefined; alerts_filter?: Zod.objectOutputType<{}, Zod.ZodUnknown, \"strip\"> | undefined; frequency?: { throttle: string | null; notifyWhen: \"onActionGroupChange\" | \"onActiveAlert\" | \"onThrottleInterval\"; summary: boolean; } | undefined; }[]; tags: string[]; setup: string; enabled: boolean; revision: number; version: number; references: string[]; interval: string; query: string; description: string; risk_score: number; severity: \"medium\" | \"high\" | \"low\" | \"critical\"; from: string; to: string; language: \"esql\"; created_at: string; created_by: string; updated_at: string; updated_by: string; author: string[]; immutable: boolean; rule_id: string; threat: { framework: string; tactic: { id: string; name: string; reference: string; }; technique?: { id: string; name: string; reference: string; subtechnique?: { id: string; name: string; reference: string; }[] | undefined; }[] | undefined; }[]; risk_score_mapping: { value: string; field: string; operator: \"equals\"; risk_score?: number | undefined; }[]; severity_mapping: { value: string; field: string; severity: \"medium\" | \"high\" | \"low\" | \"critical\"; operator: \"equals\"; }[]; exceptions_list: { type: \"endpoint\" | \"detection\" | \"rule_default\" | \"endpoint_trusted_apps\" | \"endpoint_events\" | \"endpoint_host_isolation_exceptions\" | \"endpoint_blocklists\"; id: string; list_id: string; namespace_type: \"single\" | \"agnostic\"; }[]; false_positives: string[]; max_signals: number; related_integrations: { version: string; package: string; integration?: string | undefined; }[]; required_fields: { type: string; name: string; ecs: boolean; }[]; license?: string | undefined; throttle?: string | undefined; outcome?: \"exactMatch\" | \"aliasMatch\" | \"conflict\" | undefined; alias_target_id?: string | undefined; alias_purpose?: \"savedObjectConversion\" | \"savedObjectImport\" | undefined; meta?: Zod.objectOutputType<{}, Zod.ZodUnknown, \"strip\"> | undefined; namespace?: string | undefined; note?: string | undefined; rule_name_override?: string | undefined; timestamp_override?: string | undefined; timestamp_override_fallback_disabled?: boolean | undefined; timeline_id?: string | undefined; timeline_title?: string | undefined; building_block_type?: string | undefined; output_index?: string | undefined; investigation_fields?: { field_names: string[]; } | undefined; execution_summary?: { last_execution: { message: string; date: string; status: \"running\" | \"succeeded\" | \"failed\" | \"going to run\" | \"partial failure\"; metrics: { total_search_duration_ms?: number | undefined; total_indexing_duration_ms?: number | undefined; total_enrichment_duration_ms?: number | undefined; execution_gap_duration_s?: number | undefined; }; status_order: number; }; } | undefined; })[]" + "({ type: \"eql\"; id: string; name: string; actions: { id: string; params: {} & { [k: string]: unknown; }; group: string; action_type_id: string; uuid?: string | undefined; alerts_filter?: Zod.objectOutputType<{}, Zod.ZodUnknown, \"strip\"> | undefined; frequency?: { throttle: string | null; notifyWhen: \"onActionGroupChange\" | \"onActiveAlert\" | \"onThrottleInterval\"; summary: boolean; } | undefined; }[]; tags: string[]; setup: string; enabled: boolean; revision: number; query: string; version: number; references: string[]; interval: string; description: string; risk_score: number; severity: \"medium\" | \"high\" | \"low\" | \"critical\"; from: string; to: string; language: \"eql\"; created_at: string; created_by: string; updated_at: string; updated_by: string; author: string[]; immutable: boolean; rule_id: string; threat: { framework: string; tactic: { id: string; name: string; reference: string; }; technique?: { id: string; name: string; reference: string; subtechnique?: { id: string; name: string; reference: string; }[] | undefined; }[] | undefined; }[]; risk_score_mapping: { value: string; field: string; operator: \"equals\"; risk_score?: number | undefined; }[]; severity_mapping: { value: string; field: string; severity: \"medium\" | \"high\" | \"low\" | \"critical\"; operator: \"equals\"; }[]; exceptions_list: { type: \"endpoint\" | \"detection\" | \"rule_default\" | \"endpoint_trusted_apps\" | \"endpoint_events\" | \"endpoint_host_isolation_exceptions\" | \"endpoint_blocklists\"; id: string; list_id: string; namespace_type: \"single\" | \"agnostic\"; }[]; false_positives: string[]; max_signals: number; related_integrations: { version: string; package: string; integration?: string | undefined; }[]; required_fields: { type: string; name: string; ecs: boolean; }[]; license?: string | undefined; throttle?: string | undefined; outcome?: \"exactMatch\" | \"aliasMatch\" | \"conflict\" | undefined; alias_target_id?: string | undefined; alias_purpose?: \"savedObjectConversion\" | \"savedObjectImport\" | undefined; meta?: Zod.objectOutputType<{}, Zod.ZodUnknown, \"strip\"> | undefined; namespace?: string | undefined; note?: string | undefined; rule_name_override?: string | undefined; timestamp_override?: string | undefined; timestamp_override_fallback_disabled?: boolean | undefined; timeline_id?: string | undefined; timeline_title?: string | undefined; building_block_type?: string | undefined; output_index?: string | undefined; investigation_fields?: { field_names: string[]; } | undefined; execution_summary?: { last_execution: { message: string; date: string; status: \"running\" | \"succeeded\" | \"failed\" | \"going to run\" | \"partial failure\"; metrics: { total_search_duration_ms?: number | undefined; total_indexing_duration_ms?: number | undefined; total_enrichment_duration_ms?: number | undefined; execution_gap_duration_s?: number | undefined; }; status_order: number; }; } | undefined; index?: string[] | undefined; data_view_id?: string | undefined; filters?: unknown[] | undefined; event_category_override?: string | undefined; tiebreaker_field?: string | undefined; timestamp_field?: string | undefined; } | { type: \"query\"; id: string; name: string; actions: { id: string; params: {} & { [k: string]: unknown; }; group: string; action_type_id: string; uuid?: string | undefined; alerts_filter?: Zod.objectOutputType<{}, Zod.ZodUnknown, \"strip\"> | undefined; frequency?: { throttle: string | null; notifyWhen: \"onActionGroupChange\" | \"onActiveAlert\" | \"onThrottleInterval\"; summary: boolean; } | undefined; }[]; tags: string[]; setup: string; enabled: boolean; revision: number; query: string; version: number; references: string[]; interval: string; description: string; risk_score: number; severity: \"medium\" | \"high\" | \"low\" | \"critical\"; from: string; to: string; language: \"kuery\" | \"lucene\"; created_at: string; created_by: string; updated_at: string; updated_by: string; author: string[]; immutable: boolean; rule_id: string; threat: { framework: string; tactic: { id: string; name: string; reference: string; }; technique?: { id: string; name: string; reference: string; subtechnique?: { id: string; name: string; reference: string; }[] | undefined; }[] | undefined; }[]; risk_score_mapping: { value: string; field: string; operator: \"equals\"; risk_score?: number | undefined; }[]; severity_mapping: { value: string; field: string; severity: \"medium\" | \"high\" | \"low\" | \"critical\"; operator: \"equals\"; }[]; exceptions_list: { type: \"endpoint\" | \"detection\" | \"rule_default\" | \"endpoint_trusted_apps\" | \"endpoint_events\" | \"endpoint_host_isolation_exceptions\" | \"endpoint_blocklists\"; id: string; list_id: string; namespace_type: \"single\" | \"agnostic\"; }[]; false_positives: string[]; max_signals: number; related_integrations: { version: string; package: string; integration?: string | undefined; }[]; required_fields: { type: string; name: string; ecs: boolean; }[]; license?: string | undefined; throttle?: string | undefined; outcome?: \"exactMatch\" | \"aliasMatch\" | \"conflict\" | undefined; alias_target_id?: string | undefined; alias_purpose?: \"savedObjectConversion\" | \"savedObjectImport\" | undefined; meta?: Zod.objectOutputType<{}, Zod.ZodUnknown, \"strip\"> | undefined; namespace?: string | undefined; note?: string | undefined; rule_name_override?: string | undefined; timestamp_override?: string | undefined; timestamp_override_fallback_disabled?: boolean | undefined; timeline_id?: string | undefined; timeline_title?: string | undefined; building_block_type?: string | undefined; output_index?: string | undefined; investigation_fields?: { field_names: string[]; } | undefined; execution_summary?: { last_execution: { message: string; date: string; status: \"running\" | \"succeeded\" | \"failed\" | \"going to run\" | \"partial failure\"; metrics: { total_search_duration_ms?: number | undefined; total_indexing_duration_ms?: number | undefined; total_enrichment_duration_ms?: number | undefined; execution_gap_duration_s?: number | undefined; }; status_order: number; }; } | undefined; index?: string[] | undefined; filters?: unknown[] | undefined; data_view_id?: string | undefined; saved_id?: string | undefined; response_actions?: ({ params: { query?: string | undefined; ecs_mapping?: Zod.objectOutputType<{}, Zod.ZodObject<{ field: Zod.ZodOptional; value: Zod.ZodOptional]>>; }, \"strip\", Zod.ZodTypeAny, { field?: string | undefined; value?: string | string[] | undefined; }, { field?: string | undefined; value?: string | string[] | undefined; }>, \"strip\"> | undefined; queries?: { id: string; query: string; ecs_mapping?: Zod.objectOutputType<{}, Zod.ZodObject<{ field: Zod.ZodOptional; value: Zod.ZodOptional]>>; }, \"strip\", Zod.ZodTypeAny, { field?: string | undefined; value?: string | string[] | undefined; }, { field?: string | undefined; value?: string | string[] | undefined; }>, \"strip\"> | undefined; version?: string | undefined; platform?: string | undefined; removed?: boolean | undefined; snapshot?: boolean | undefined; }[] | undefined; pack_id?: string | undefined; saved_query_id?: string | undefined; timeout?: number | undefined; }; action_type_id: \".osquery\"; } | { params: { command: \"isolate\"; comment?: string | undefined; } | { config: { field: string; overwrite: boolean; }; command: \"kill-process\" | \"suspend-process\"; comment?: string | undefined; }; action_type_id: \".endpoint\"; })[] | undefined; alert_suppression?: { group_by: string[]; duration?: { value: number; unit: \"m\" | \"h\" | \"s\"; } | undefined; missing_fields_strategy?: \"doNotSuppress\" | \"suppress\" | undefined; } | undefined; } | { type: \"saved_query\"; id: string; name: string; actions: { id: string; params: {} & { [k: string]: unknown; }; group: string; action_type_id: string; uuid?: string | undefined; alerts_filter?: Zod.objectOutputType<{}, Zod.ZodUnknown, \"strip\"> | undefined; frequency?: { throttle: string | null; notifyWhen: \"onActionGroupChange\" | \"onActiveAlert\" | \"onThrottleInterval\"; summary: boolean; } | undefined; }[]; tags: string[]; setup: string; enabled: boolean; revision: number; version: number; references: string[]; interval: string; description: string; risk_score: number; severity: \"medium\" | \"high\" | \"low\" | \"critical\"; from: string; to: string; language: \"kuery\" | \"lucene\"; created_at: string; created_by: string; updated_at: string; updated_by: string; author: string[]; immutable: boolean; rule_id: string; threat: { framework: string; tactic: { id: string; name: string; reference: string; }; technique?: { id: string; name: string; reference: string; subtechnique?: { id: string; name: string; reference: string; }[] | undefined; }[] | undefined; }[]; risk_score_mapping: { value: string; field: string; operator: \"equals\"; risk_score?: number | undefined; }[]; severity_mapping: { value: string; field: string; severity: \"medium\" | \"high\" | \"low\" | \"critical\"; operator: \"equals\"; }[]; exceptions_list: { type: \"endpoint\" | \"detection\" | \"rule_default\" | \"endpoint_trusted_apps\" | \"endpoint_events\" | \"endpoint_host_isolation_exceptions\" | \"endpoint_blocklists\"; id: string; list_id: string; namespace_type: \"single\" | \"agnostic\"; }[]; false_positives: string[]; max_signals: number; related_integrations: { version: string; package: string; integration?: string | undefined; }[]; required_fields: { type: string; name: string; ecs: boolean; }[]; saved_id: string; license?: string | undefined; throttle?: string | undefined; outcome?: \"exactMatch\" | \"aliasMatch\" | \"conflict\" | undefined; alias_target_id?: string | undefined; alias_purpose?: \"savedObjectConversion\" | \"savedObjectImport\" | undefined; meta?: Zod.objectOutputType<{}, Zod.ZodUnknown, \"strip\"> | undefined; namespace?: string | undefined; note?: string | undefined; rule_name_override?: string | undefined; timestamp_override?: string | undefined; timestamp_override_fallback_disabled?: boolean | undefined; timeline_id?: string | undefined; timeline_title?: string | undefined; building_block_type?: string | undefined; output_index?: string | undefined; investigation_fields?: { field_names: string[]; } | undefined; execution_summary?: { last_execution: { message: string; date: string; status: \"running\" | \"succeeded\" | \"failed\" | \"going to run\" | \"partial failure\"; metrics: { total_search_duration_ms?: number | undefined; total_indexing_duration_ms?: number | undefined; total_enrichment_duration_ms?: number | undefined; execution_gap_duration_s?: number | undefined; }; status_order: number; }; } | undefined; index?: string[] | undefined; query?: string | undefined; filters?: unknown[] | undefined; data_view_id?: string | undefined; response_actions?: ({ params: { query?: string | undefined; ecs_mapping?: Zod.objectOutputType<{}, Zod.ZodObject<{ field: Zod.ZodOptional; value: Zod.ZodOptional]>>; }, \"strip\", Zod.ZodTypeAny, { field?: string | undefined; value?: string | string[] | undefined; }, { field?: string | undefined; value?: string | string[] | undefined; }>, \"strip\"> | undefined; queries?: { id: string; query: string; ecs_mapping?: Zod.objectOutputType<{}, Zod.ZodObject<{ field: Zod.ZodOptional; value: Zod.ZodOptional]>>; }, \"strip\", Zod.ZodTypeAny, { field?: string | undefined; value?: string | string[] | undefined; }, { field?: string | undefined; value?: string | string[] | undefined; }>, \"strip\"> | undefined; version?: string | undefined; platform?: string | undefined; removed?: boolean | undefined; snapshot?: boolean | undefined; }[] | undefined; pack_id?: string | undefined; saved_query_id?: string | undefined; timeout?: number | undefined; }; action_type_id: \".osquery\"; } | { params: { command: \"isolate\"; comment?: string | undefined; } | { config: { field: string; overwrite: boolean; }; command: \"kill-process\" | \"suspend-process\"; comment?: string | undefined; }; action_type_id: \".endpoint\"; })[] | undefined; alert_suppression?: { group_by: string[]; duration?: { value: number; unit: \"m\" | \"h\" | \"s\"; } | undefined; missing_fields_strategy?: \"doNotSuppress\" | \"suppress\" | undefined; } | undefined; } | { type: \"threshold\"; id: string; name: string; actions: { id: string; params: {} & { [k: string]: unknown; }; group: string; action_type_id: string; uuid?: string | undefined; alerts_filter?: Zod.objectOutputType<{}, Zod.ZodUnknown, \"strip\"> | undefined; frequency?: { throttle: string | null; notifyWhen: \"onActionGroupChange\" | \"onActiveAlert\" | \"onThrottleInterval\"; summary: boolean; } | undefined; }[]; tags: string[]; setup: string; enabled: boolean; revision: number; query: string; version: number; references: string[]; interval: string; description: string; risk_score: number; severity: \"medium\" | \"high\" | \"low\" | \"critical\"; threshold: { value: number; field: (string | string[]) & (string | string[] | undefined); cardinality?: { value: number; field: string; }[] | undefined; }; from: string; to: string; language: \"kuery\" | \"lucene\"; created_at: string; created_by: string; updated_at: string; updated_by: string; author: string[]; immutable: boolean; rule_id: string; threat: { framework: string; tactic: { id: string; name: string; reference: string; }; technique?: { id: string; name: string; reference: string; subtechnique?: { id: string; name: string; reference: string; }[] | undefined; }[] | undefined; }[]; risk_score_mapping: { value: string; field: string; operator: \"equals\"; risk_score?: number | undefined; }[]; severity_mapping: { value: string; field: string; severity: \"medium\" | \"high\" | \"low\" | \"critical\"; operator: \"equals\"; }[]; exceptions_list: { type: \"endpoint\" | \"detection\" | \"rule_default\" | \"endpoint_trusted_apps\" | \"endpoint_events\" | \"endpoint_host_isolation_exceptions\" | \"endpoint_blocklists\"; id: string; list_id: string; namespace_type: \"single\" | \"agnostic\"; }[]; false_positives: string[]; max_signals: number; related_integrations: { version: string; package: string; integration?: string | undefined; }[]; required_fields: { type: string; name: string; ecs: boolean; }[]; license?: string | undefined; throttle?: string | undefined; outcome?: \"exactMatch\" | \"aliasMatch\" | \"conflict\" | undefined; alias_target_id?: string | undefined; alias_purpose?: \"savedObjectConversion\" | \"savedObjectImport\" | undefined; meta?: Zod.objectOutputType<{}, Zod.ZodUnknown, \"strip\"> | undefined; namespace?: string | undefined; note?: string | undefined; rule_name_override?: string | undefined; timestamp_override?: string | undefined; timestamp_override_fallback_disabled?: boolean | undefined; timeline_id?: string | undefined; timeline_title?: string | undefined; building_block_type?: string | undefined; output_index?: string | undefined; investigation_fields?: { field_names: string[]; } | undefined; execution_summary?: { last_execution: { message: string; date: string; status: \"running\" | \"succeeded\" | \"failed\" | \"going to run\" | \"partial failure\"; metrics: { total_search_duration_ms?: number | undefined; total_indexing_duration_ms?: number | undefined; total_enrichment_duration_ms?: number | undefined; execution_gap_duration_s?: number | undefined; }; status_order: number; }; } | undefined; index?: string[] | undefined; filters?: unknown[] | undefined; data_view_id?: string | undefined; saved_id?: string | undefined; alert_suppression?: { duration: { value: number; unit: \"m\" | \"h\" | \"s\"; }; } | undefined; } | { type: \"threat_match\"; id: string; name: string; actions: { id: string; params: {} & { [k: string]: unknown; }; group: string; action_type_id: string; uuid?: string | undefined; alerts_filter?: Zod.objectOutputType<{}, Zod.ZodUnknown, \"strip\"> | undefined; frequency?: { throttle: string | null; notifyWhen: \"onActionGroupChange\" | \"onActiveAlert\" | \"onThrottleInterval\"; summary: boolean; } | undefined; }[]; tags: string[]; setup: string; enabled: boolean; revision: number; query: string; version: number; references: string[]; interval: string; description: string; risk_score: number; severity: \"medium\" | \"high\" | \"low\" | \"critical\"; from: string; to: string; language: \"kuery\" | \"lucene\"; created_at: string; created_by: string; updated_at: string; updated_by: string; author: string[]; immutable: boolean; rule_id: string; threat: { framework: string; tactic: { id: string; name: string; reference: string; }; technique?: { id: string; name: string; reference: string; subtechnique?: { id: string; name: string; reference: string; }[] | undefined; }[] | undefined; }[]; risk_score_mapping: { value: string; field: string; operator: \"equals\"; risk_score?: number | undefined; }[]; severity_mapping: { value: string; field: string; severity: \"medium\" | \"high\" | \"low\" | \"critical\"; operator: \"equals\"; }[]; exceptions_list: { type: \"endpoint\" | \"detection\" | \"rule_default\" | \"endpoint_trusted_apps\" | \"endpoint_events\" | \"endpoint_host_isolation_exceptions\" | \"endpoint_blocklists\"; id: string; list_id: string; namespace_type: \"single\" | \"agnostic\"; }[]; false_positives: string[]; max_signals: number; related_integrations: { version: string; package: string; integration?: string | undefined; }[]; required_fields: { type: string; name: string; ecs: boolean; }[]; threat_query: string; threat_mapping: { entries: { type: \"mapping\"; value: string; field: string; }[]; }[]; threat_index: string[]; license?: string | undefined; throttle?: string | undefined; outcome?: \"exactMatch\" | \"aliasMatch\" | \"conflict\" | undefined; alias_target_id?: string | undefined; alias_purpose?: \"savedObjectConversion\" | \"savedObjectImport\" | undefined; meta?: Zod.objectOutputType<{}, Zod.ZodUnknown, \"strip\"> | undefined; namespace?: string | undefined; note?: string | undefined; rule_name_override?: string | undefined; timestamp_override?: string | undefined; timestamp_override_fallback_disabled?: boolean | undefined; timeline_id?: string | undefined; timeline_title?: string | undefined; building_block_type?: string | undefined; output_index?: string | undefined; investigation_fields?: { field_names: string[]; } | undefined; execution_summary?: { last_execution: { message: string; date: string; status: \"running\" | \"succeeded\" | \"failed\" | \"going to run\" | \"partial failure\"; metrics: { total_search_duration_ms?: number | undefined; total_indexing_duration_ms?: number | undefined; total_enrichment_duration_ms?: number | undefined; execution_gap_duration_s?: number | undefined; }; status_order: number; }; } | undefined; index?: string[] | undefined; filters?: unknown[] | undefined; data_view_id?: string | undefined; saved_id?: string | undefined; alert_suppression?: { group_by: string[]; duration?: { value: number; unit: \"m\" | \"h\" | \"s\"; } | undefined; missing_fields_strategy?: \"doNotSuppress\" | \"suppress\" | undefined; } | undefined; threat_filters?: unknown[] | undefined; threat_indicator_path?: string | undefined; threat_language?: \"lucene\" | \"kuery\" | undefined; concurrent_searches?: number | undefined; items_per_search?: number | undefined; } | { type: \"machine_learning\"; id: string; name: string; actions: { id: string; params: {} & { [k: string]: unknown; }; group: string; action_type_id: string; uuid?: string | undefined; alerts_filter?: Zod.objectOutputType<{}, Zod.ZodUnknown, \"strip\"> | undefined; frequency?: { throttle: string | null; notifyWhen: \"onActionGroupChange\" | \"onActiveAlert\" | \"onThrottleInterval\"; summary: boolean; } | undefined; }[]; tags: string[]; setup: string; enabled: boolean; revision: number; version: number; references: string[]; interval: string; description: string; risk_score: number; severity: \"medium\" | \"high\" | \"low\" | \"critical\"; from: string; to: string; created_at: string; created_by: string; updated_at: string; updated_by: string; author: string[]; immutable: boolean; rule_id: string; threat: { framework: string; tactic: { id: string; name: string; reference: string; }; technique?: { id: string; name: string; reference: string; subtechnique?: { id: string; name: string; reference: string; }[] | undefined; }[] | undefined; }[]; risk_score_mapping: { value: string; field: string; operator: \"equals\"; risk_score?: number | undefined; }[]; severity_mapping: { value: string; field: string; severity: \"medium\" | \"high\" | \"low\" | \"critical\"; operator: \"equals\"; }[]; exceptions_list: { type: \"endpoint\" | \"detection\" | \"rule_default\" | \"endpoint_trusted_apps\" | \"endpoint_events\" | \"endpoint_host_isolation_exceptions\" | \"endpoint_blocklists\"; id: string; list_id: string; namespace_type: \"single\" | \"agnostic\"; }[]; false_positives: string[]; max_signals: number; related_integrations: { version: string; package: string; integration?: string | undefined; }[]; required_fields: { type: string; name: string; ecs: boolean; }[]; anomaly_threshold: number; machine_learning_job_id: (string | string[]) & (string | string[] | undefined); license?: string | undefined; throttle?: string | undefined; outcome?: \"exactMatch\" | \"aliasMatch\" | \"conflict\" | undefined; alias_target_id?: string | undefined; alias_purpose?: \"savedObjectConversion\" | \"savedObjectImport\" | undefined; meta?: Zod.objectOutputType<{}, Zod.ZodUnknown, \"strip\"> | undefined; namespace?: string | undefined; note?: string | undefined; rule_name_override?: string | undefined; timestamp_override?: string | undefined; timestamp_override_fallback_disabled?: boolean | undefined; timeline_id?: string | undefined; timeline_title?: string | undefined; building_block_type?: string | undefined; output_index?: string | undefined; investigation_fields?: { field_names: string[]; } | undefined; execution_summary?: { last_execution: { message: string; date: string; status: \"running\" | \"succeeded\" | \"failed\" | \"going to run\" | \"partial failure\"; metrics: { total_search_duration_ms?: number | undefined; total_indexing_duration_ms?: number | undefined; total_enrichment_duration_ms?: number | undefined; execution_gap_duration_s?: number | undefined; }; status_order: number; }; } | undefined; } | { type: \"new_terms\"; id: string; name: string; actions: { id: string; params: {} & { [k: string]: unknown; }; group: string; action_type_id: string; uuid?: string | undefined; alerts_filter?: Zod.objectOutputType<{}, Zod.ZodUnknown, \"strip\"> | undefined; frequency?: { throttle: string | null; notifyWhen: \"onActionGroupChange\" | \"onActiveAlert\" | \"onThrottleInterval\"; summary: boolean; } | undefined; }[]; tags: string[]; setup: string; enabled: boolean; revision: number; query: string; version: number; references: string[]; interval: string; description: string; risk_score: number; severity: \"medium\" | \"high\" | \"low\" | \"critical\"; from: string; to: string; language: \"kuery\" | \"lucene\"; created_at: string; created_by: string; updated_at: string; updated_by: string; author: string[]; immutable: boolean; rule_id: string; threat: { framework: string; tactic: { id: string; name: string; reference: string; }; technique?: { id: string; name: string; reference: string; subtechnique?: { id: string; name: string; reference: string; }[] | undefined; }[] | undefined; }[]; risk_score_mapping: { value: string; field: string; operator: \"equals\"; risk_score?: number | undefined; }[]; severity_mapping: { value: string; field: string; severity: \"medium\" | \"high\" | \"low\" | \"critical\"; operator: \"equals\"; }[]; exceptions_list: { type: \"endpoint\" | \"detection\" | \"rule_default\" | \"endpoint_trusted_apps\" | \"endpoint_events\" | \"endpoint_host_isolation_exceptions\" | \"endpoint_blocklists\"; id: string; list_id: string; namespace_type: \"single\" | \"agnostic\"; }[]; false_positives: string[]; max_signals: number; related_integrations: { version: string; package: string; integration?: string | undefined; }[]; required_fields: { type: string; name: string; ecs: boolean; }[]; new_terms_fields: string[]; history_window_start: string; license?: string | undefined; throttle?: string | undefined; outcome?: \"exactMatch\" | \"aliasMatch\" | \"conflict\" | undefined; alias_target_id?: string | undefined; alias_purpose?: \"savedObjectConversion\" | \"savedObjectImport\" | undefined; meta?: Zod.objectOutputType<{}, Zod.ZodUnknown, \"strip\"> | undefined; namespace?: string | undefined; note?: string | undefined; rule_name_override?: string | undefined; timestamp_override?: string | undefined; timestamp_override_fallback_disabled?: boolean | undefined; timeline_id?: string | undefined; timeline_title?: string | undefined; building_block_type?: string | undefined; output_index?: string | undefined; investigation_fields?: { field_names: string[]; } | undefined; execution_summary?: { last_execution: { message: string; date: string; status: \"running\" | \"succeeded\" | \"failed\" | \"going to run\" | \"partial failure\"; metrics: { total_search_duration_ms?: number | undefined; total_indexing_duration_ms?: number | undefined; total_enrichment_duration_ms?: number | undefined; execution_gap_duration_s?: number | undefined; }; status_order: number; }; } | undefined; index?: string[] | undefined; filters?: unknown[] | undefined; data_view_id?: string | undefined; } | { type: \"esql\"; id: string; name: string; actions: { id: string; params: {} & { [k: string]: unknown; }; group: string; action_type_id: string; uuid?: string | undefined; alerts_filter?: Zod.objectOutputType<{}, Zod.ZodUnknown, \"strip\"> | undefined; frequency?: { throttle: string | null; notifyWhen: \"onActionGroupChange\" | \"onActiveAlert\" | \"onThrottleInterval\"; summary: boolean; } | undefined; }[]; tags: string[]; setup: string; enabled: boolean; revision: number; query: string; version: number; references: string[]; interval: string; description: string; risk_score: number; severity: \"medium\" | \"high\" | \"low\" | \"critical\"; from: string; to: string; language: \"esql\"; created_at: string; created_by: string; updated_at: string; updated_by: string; author: string[]; immutable: boolean; rule_id: string; threat: { framework: string; tactic: { id: string; name: string; reference: string; }; technique?: { id: string; name: string; reference: string; subtechnique?: { id: string; name: string; reference: string; }[] | undefined; }[] | undefined; }[]; risk_score_mapping: { value: string; field: string; operator: \"equals\"; risk_score?: number | undefined; }[]; severity_mapping: { value: string; field: string; severity: \"medium\" | \"high\" | \"low\" | \"critical\"; operator: \"equals\"; }[]; exceptions_list: { type: \"endpoint\" | \"detection\" | \"rule_default\" | \"endpoint_trusted_apps\" | \"endpoint_events\" | \"endpoint_host_isolation_exceptions\" | \"endpoint_blocklists\"; id: string; list_id: string; namespace_type: \"single\" | \"agnostic\"; }[]; false_positives: string[]; max_signals: number; related_integrations: { version: string; package: string; integration?: string | undefined; }[]; required_fields: { type: string; name: string; ecs: boolean; }[]; license?: string | undefined; throttle?: string | undefined; outcome?: \"exactMatch\" | \"aliasMatch\" | \"conflict\" | undefined; alias_target_id?: string | undefined; alias_purpose?: \"savedObjectConversion\" | \"savedObjectImport\" | undefined; meta?: Zod.objectOutputType<{}, Zod.ZodUnknown, \"strip\"> | undefined; namespace?: string | undefined; note?: string | undefined; rule_name_override?: string | undefined; timestamp_override?: string | undefined; timestamp_override_fallback_disabled?: boolean | undefined; timeline_id?: string | undefined; timeline_title?: string | undefined; building_block_type?: string | undefined; output_index?: string | undefined; investigation_fields?: { field_names: string[]; } | undefined; execution_summary?: { last_execution: { message: string; date: string; status: \"running\" | \"succeeded\" | \"failed\" | \"going to run\" | \"partial failure\"; metrics: { total_search_duration_ms?: number | undefined; total_indexing_duration_ms?: number | undefined; total_enrichment_duration_ms?: number | undefined; execution_gap_duration_s?: number | undefined; }; status_order: number; }; } | undefined; })[]" ], "path": "x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/types.ts", "deprecated": false, @@ -568,7 +568,7 @@ "\nExperimental flag needed to enable the link" ], "signature": [ - "\"assistantModelEvaluation\" | \"assistantStreamingEnabled\" | \"tGridEnabled\" | \"tGridEventRenderedViewEnabled\" | \"excludePoliciesInFilterEnabled\" | \"kubernetesEnabled\" | \"chartEmbeddablesEnabled\" | \"donutChartEmbeddablesEnabled\" | \"previewTelemetryUrlEnabled\" | \"insightsRelatedAlertsByProcessAncestry\" | \"extendedRuleExecutionLoggingEnabled\" | \"socTrendsEnabled\" | \"responseActionsEnabled\" | \"endpointResponseActionsEnabled\" | \"responseActionUploadEnabled\" | \"automatedProcessActionsEnabled\" | \"responseActionsSentinelOneV1Enabled\" | \"alertsPageChartsEnabled\" | \"alertTypeEnabled\" | \"expandableFlyoutInCreateRuleEnabled\" | \"alertsPageFiltersEnabled\" | \"newUserDetailsFlyout\" | \"newUserDetailsFlyoutManagedUser\" | \"newHostDetailsFlyout\" | \"riskScoringPersistence\" | \"riskScoringRoutesEnabled\" | \"esqlRulesDisabled\" | \"protectionUpdatesEnabled\" | \"disableTimelineSaveTour\" | \"riskEnginePrivilegesRouteEnabled\" | \"alertSuppressionForIndicatorMatchRuleEnabled\" | \"entityAnalyticsAssetCriticalityEnabled\" | \"sentinelOneDataInAnalyzerEnabled\" | \"sentinelOneManualHostActionsEnabled\" | \"jsonPrebuiltRulesDiffingEnabled\" | \"timelineEsqlTabDisabled\" | undefined" + "\"assistantModelEvaluation\" | \"assistantStreamingEnabled\" | \"tGridEnabled\" | \"tGridEventRenderedViewEnabled\" | \"excludePoliciesInFilterEnabled\" | \"kubernetesEnabled\" | \"chartEmbeddablesEnabled\" | \"donutChartEmbeddablesEnabled\" | \"previewTelemetryUrlEnabled\" | \"insightsRelatedAlertsByProcessAncestry\" | \"extendedRuleExecutionLoggingEnabled\" | \"socTrendsEnabled\" | \"responseActionsEnabled\" | \"endpointResponseActionsEnabled\" | \"responseActionUploadEnabled\" | \"automatedProcessActionsEnabled\" | \"responseActionsSentinelOneV1Enabled\" | \"alertsPageChartsEnabled\" | \"alertTypeEnabled\" | \"expandableFlyoutInCreateRuleEnabled\" | \"alertsPageFiltersEnabled\" | \"newUserDetailsFlyout\" | \"newUserDetailsFlyoutManagedUser\" | \"newHostDetailsFlyout\" | \"riskScoringPersistence\" | \"riskScoringRoutesEnabled\" | \"esqlRulesDisabled\" | \"protectionUpdatesEnabled\" | \"disableTimelineSaveTour\" | \"riskEnginePrivilegesRouteEnabled\" | \"alertSuppressionForIndicatorMatchRuleEnabled\" | \"entityAnalyticsAssetCriticalityEnabled\" | \"sentinelOneDataInAnalyzerEnabled\" | \"sentinelOneManualHostActionsEnabled\" | \"crowdstrikeDataInAnalyzerEnabled\" | \"jsonPrebuiltRulesDiffingEnabled\" | \"timelineEsqlTabDisabled\" | \"perFieldPrebuiltRulesDiffingEnabled\" | undefined" ], "path": "x-pack/plugins/security_solution/public/common/links/types.ts", "deprecated": false, @@ -648,7 +648,7 @@ "\nExperimental flag needed to disable the link. Opposite of experimentalKey" ], "signature": [ - "\"assistantModelEvaluation\" | \"assistantStreamingEnabled\" | \"tGridEnabled\" | \"tGridEventRenderedViewEnabled\" | \"excludePoliciesInFilterEnabled\" | \"kubernetesEnabled\" | \"chartEmbeddablesEnabled\" | \"donutChartEmbeddablesEnabled\" | \"previewTelemetryUrlEnabled\" | \"insightsRelatedAlertsByProcessAncestry\" | \"extendedRuleExecutionLoggingEnabled\" | \"socTrendsEnabled\" | \"responseActionsEnabled\" | \"endpointResponseActionsEnabled\" | \"responseActionUploadEnabled\" | \"automatedProcessActionsEnabled\" | \"responseActionsSentinelOneV1Enabled\" | \"alertsPageChartsEnabled\" | \"alertTypeEnabled\" | \"expandableFlyoutInCreateRuleEnabled\" | \"alertsPageFiltersEnabled\" | \"newUserDetailsFlyout\" | \"newUserDetailsFlyoutManagedUser\" | \"newHostDetailsFlyout\" | \"riskScoringPersistence\" | \"riskScoringRoutesEnabled\" | \"esqlRulesDisabled\" | \"protectionUpdatesEnabled\" | \"disableTimelineSaveTour\" | \"riskEnginePrivilegesRouteEnabled\" | \"alertSuppressionForIndicatorMatchRuleEnabled\" | \"entityAnalyticsAssetCriticalityEnabled\" | \"sentinelOneDataInAnalyzerEnabled\" | \"sentinelOneManualHostActionsEnabled\" | \"jsonPrebuiltRulesDiffingEnabled\" | \"timelineEsqlTabDisabled\" | undefined" + "\"assistantModelEvaluation\" | \"assistantStreamingEnabled\" | \"tGridEnabled\" | \"tGridEventRenderedViewEnabled\" | \"excludePoliciesInFilterEnabled\" | \"kubernetesEnabled\" | \"chartEmbeddablesEnabled\" | \"donutChartEmbeddablesEnabled\" | \"previewTelemetryUrlEnabled\" | \"insightsRelatedAlertsByProcessAncestry\" | \"extendedRuleExecutionLoggingEnabled\" | \"socTrendsEnabled\" | \"responseActionsEnabled\" | \"endpointResponseActionsEnabled\" | \"responseActionUploadEnabled\" | \"automatedProcessActionsEnabled\" | \"responseActionsSentinelOneV1Enabled\" | \"alertsPageChartsEnabled\" | \"alertTypeEnabled\" | \"expandableFlyoutInCreateRuleEnabled\" | \"alertsPageFiltersEnabled\" | \"newUserDetailsFlyout\" | \"newUserDetailsFlyoutManagedUser\" | \"newHostDetailsFlyout\" | \"riskScoringPersistence\" | \"riskScoringRoutesEnabled\" | \"esqlRulesDisabled\" | \"protectionUpdatesEnabled\" | \"disableTimelineSaveTour\" | \"riskEnginePrivilegesRouteEnabled\" | \"alertSuppressionForIndicatorMatchRuleEnabled\" | \"entityAnalyticsAssetCriticalityEnabled\" | \"sentinelOneDataInAnalyzerEnabled\" | \"sentinelOneManualHostActionsEnabled\" | \"crowdstrikeDataInAnalyzerEnabled\" | \"jsonPrebuiltRulesDiffingEnabled\" | \"timelineEsqlTabDisabled\" | \"perFieldPrebuiltRulesDiffingEnabled\" | undefined" ], "path": "x-pack/plugins/security_solution/public/common/links/types.ts", "deprecated": false, @@ -1986,7 +1986,7 @@ "label": "experimentalFeatures", "description": [], "signature": [ - "{ readonly tGridEnabled: boolean; readonly tGridEventRenderedViewEnabled: boolean; readonly excludePoliciesInFilterEnabled: boolean; readonly kubernetesEnabled: boolean; readonly chartEmbeddablesEnabled: boolean; readonly donutChartEmbeddablesEnabled: boolean; readonly previewTelemetryUrlEnabled: boolean; readonly insightsRelatedAlertsByProcessAncestry: boolean; readonly extendedRuleExecutionLoggingEnabled: boolean; readonly assistantStreamingEnabled: boolean; readonly socTrendsEnabled: boolean; readonly responseActionsEnabled: boolean; readonly endpointResponseActionsEnabled: boolean; readonly responseActionUploadEnabled: boolean; readonly automatedProcessActionsEnabled: boolean; readonly responseActionsSentinelOneV1Enabled: boolean; readonly alertsPageChartsEnabled: boolean; readonly alertTypeEnabled: boolean; readonly expandableFlyoutInCreateRuleEnabled: boolean; readonly alertsPageFiltersEnabled: boolean; readonly assistantModelEvaluation: boolean; readonly newUserDetailsFlyout: boolean; readonly newUserDetailsFlyoutManagedUser: boolean; readonly newHostDetailsFlyout: boolean; readonly riskScoringPersistence: boolean; readonly riskScoringRoutesEnabled: boolean; readonly esqlRulesDisabled: boolean; readonly protectionUpdatesEnabled: boolean; readonly disableTimelineSaveTour: boolean; readonly riskEnginePrivilegesRouteEnabled: boolean; readonly alertSuppressionForIndicatorMatchRuleEnabled: boolean; readonly entityAnalyticsAssetCriticalityEnabled: boolean; readonly sentinelOneDataInAnalyzerEnabled: boolean; readonly sentinelOneManualHostActionsEnabled: boolean; readonly jsonPrebuiltRulesDiffingEnabled: boolean; readonly timelineEsqlTabDisabled: boolean; }" + "{ readonly tGridEnabled: boolean; readonly tGridEventRenderedViewEnabled: boolean; readonly excludePoliciesInFilterEnabled: boolean; readonly kubernetesEnabled: boolean; readonly chartEmbeddablesEnabled: boolean; readonly donutChartEmbeddablesEnabled: boolean; readonly previewTelemetryUrlEnabled: boolean; readonly insightsRelatedAlertsByProcessAncestry: boolean; readonly extendedRuleExecutionLoggingEnabled: boolean; readonly assistantStreamingEnabled: boolean; readonly socTrendsEnabled: boolean; readonly responseActionsEnabled: boolean; readonly endpointResponseActionsEnabled: boolean; readonly responseActionUploadEnabled: boolean; readonly automatedProcessActionsEnabled: boolean; readonly responseActionsSentinelOneV1Enabled: boolean; readonly alertsPageChartsEnabled: boolean; readonly alertTypeEnabled: boolean; readonly expandableFlyoutInCreateRuleEnabled: boolean; readonly alertsPageFiltersEnabled: boolean; readonly assistantModelEvaluation: boolean; readonly newUserDetailsFlyout: boolean; readonly newUserDetailsFlyoutManagedUser: boolean; readonly newHostDetailsFlyout: boolean; readonly riskScoringPersistence: boolean; readonly riskScoringRoutesEnabled: boolean; readonly esqlRulesDisabled: boolean; readonly protectionUpdatesEnabled: boolean; readonly disableTimelineSaveTour: boolean; readonly riskEnginePrivilegesRouteEnabled: boolean; readonly alertSuppressionForIndicatorMatchRuleEnabled: boolean; readonly entityAnalyticsAssetCriticalityEnabled: boolean; readonly sentinelOneDataInAnalyzerEnabled: boolean; readonly sentinelOneManualHostActionsEnabled: boolean; readonly crowdstrikeDataInAnalyzerEnabled: boolean; readonly jsonPrebuiltRulesDiffingEnabled: boolean; readonly timelineEsqlTabDisabled: boolean; readonly perFieldPrebuiltRulesDiffingEnabled: boolean; }" ], "path": "x-pack/plugins/security_solution/public/types.ts", "deprecated": false, @@ -3105,7 +3105,7 @@ "\nThe security solution generic experimental features" ], "signature": [ - "{ readonly tGridEnabled: boolean; readonly tGridEventRenderedViewEnabled: boolean; readonly excludePoliciesInFilterEnabled: boolean; readonly kubernetesEnabled: boolean; readonly chartEmbeddablesEnabled: boolean; readonly donutChartEmbeddablesEnabled: boolean; readonly previewTelemetryUrlEnabled: boolean; readonly insightsRelatedAlertsByProcessAncestry: boolean; readonly extendedRuleExecutionLoggingEnabled: boolean; readonly assistantStreamingEnabled: boolean; readonly socTrendsEnabled: boolean; readonly responseActionsEnabled: boolean; readonly endpointResponseActionsEnabled: boolean; readonly responseActionUploadEnabled: boolean; readonly automatedProcessActionsEnabled: boolean; readonly responseActionsSentinelOneV1Enabled: boolean; readonly alertsPageChartsEnabled: boolean; readonly alertTypeEnabled: boolean; readonly expandableFlyoutInCreateRuleEnabled: boolean; readonly alertsPageFiltersEnabled: boolean; readonly assistantModelEvaluation: boolean; readonly newUserDetailsFlyout: boolean; readonly newUserDetailsFlyoutManagedUser: boolean; readonly newHostDetailsFlyout: boolean; readonly riskScoringPersistence: boolean; readonly riskScoringRoutesEnabled: boolean; readonly esqlRulesDisabled: boolean; readonly protectionUpdatesEnabled: boolean; readonly disableTimelineSaveTour: boolean; readonly riskEnginePrivilegesRouteEnabled: boolean; readonly alertSuppressionForIndicatorMatchRuleEnabled: boolean; readonly entityAnalyticsAssetCriticalityEnabled: boolean; readonly sentinelOneDataInAnalyzerEnabled: boolean; readonly sentinelOneManualHostActionsEnabled: boolean; readonly jsonPrebuiltRulesDiffingEnabled: boolean; readonly timelineEsqlTabDisabled: boolean; }" + "{ readonly tGridEnabled: boolean; readonly tGridEventRenderedViewEnabled: boolean; readonly excludePoliciesInFilterEnabled: boolean; readonly kubernetesEnabled: boolean; readonly chartEmbeddablesEnabled: boolean; readonly donutChartEmbeddablesEnabled: boolean; readonly previewTelemetryUrlEnabled: boolean; readonly insightsRelatedAlertsByProcessAncestry: boolean; readonly extendedRuleExecutionLoggingEnabled: boolean; readonly assistantStreamingEnabled: boolean; readonly socTrendsEnabled: boolean; readonly responseActionsEnabled: boolean; readonly endpointResponseActionsEnabled: boolean; readonly responseActionUploadEnabled: boolean; readonly automatedProcessActionsEnabled: boolean; readonly responseActionsSentinelOneV1Enabled: boolean; readonly alertsPageChartsEnabled: boolean; readonly alertTypeEnabled: boolean; readonly expandableFlyoutInCreateRuleEnabled: boolean; readonly alertsPageFiltersEnabled: boolean; readonly assistantModelEvaluation: boolean; readonly newUserDetailsFlyout: boolean; readonly newUserDetailsFlyoutManagedUser: boolean; readonly newHostDetailsFlyout: boolean; readonly riskScoringPersistence: boolean; readonly riskScoringRoutesEnabled: boolean; readonly esqlRulesDisabled: boolean; readonly protectionUpdatesEnabled: boolean; readonly disableTimelineSaveTour: boolean; readonly riskEnginePrivilegesRouteEnabled: boolean; readonly alertSuppressionForIndicatorMatchRuleEnabled: boolean; readonly entityAnalyticsAssetCriticalityEnabled: boolean; readonly sentinelOneDataInAnalyzerEnabled: boolean; readonly sentinelOneManualHostActionsEnabled: boolean; readonly crowdstrikeDataInAnalyzerEnabled: boolean; readonly jsonPrebuiltRulesDiffingEnabled: boolean; readonly timelineEsqlTabDisabled: boolean; readonly perFieldPrebuiltRulesDiffingEnabled: boolean; }" ], "path": "x-pack/plugins/security_solution/server/plugin_contract.ts", "deprecated": false, @@ -3281,7 +3281,7 @@ "label": "ExperimentalFeatures", "description": [], "signature": [ - "{ readonly tGridEnabled: boolean; readonly tGridEventRenderedViewEnabled: boolean; readonly excludePoliciesInFilterEnabled: boolean; readonly kubernetesEnabled: boolean; readonly chartEmbeddablesEnabled: boolean; readonly donutChartEmbeddablesEnabled: boolean; readonly previewTelemetryUrlEnabled: boolean; readonly insightsRelatedAlertsByProcessAncestry: boolean; readonly extendedRuleExecutionLoggingEnabled: boolean; readonly assistantStreamingEnabled: boolean; readonly socTrendsEnabled: boolean; readonly responseActionsEnabled: boolean; readonly endpointResponseActionsEnabled: boolean; readonly responseActionUploadEnabled: boolean; readonly automatedProcessActionsEnabled: boolean; readonly responseActionsSentinelOneV1Enabled: boolean; readonly alertsPageChartsEnabled: boolean; readonly alertTypeEnabled: boolean; readonly expandableFlyoutInCreateRuleEnabled: boolean; readonly alertsPageFiltersEnabled: boolean; readonly assistantModelEvaluation: boolean; readonly newUserDetailsFlyout: boolean; readonly newUserDetailsFlyoutManagedUser: boolean; readonly newHostDetailsFlyout: boolean; readonly riskScoringPersistence: boolean; readonly riskScoringRoutesEnabled: boolean; readonly esqlRulesDisabled: boolean; readonly protectionUpdatesEnabled: boolean; readonly disableTimelineSaveTour: boolean; readonly riskEnginePrivilegesRouteEnabled: boolean; readonly alertSuppressionForIndicatorMatchRuleEnabled: boolean; readonly entityAnalyticsAssetCriticalityEnabled: boolean; readonly sentinelOneDataInAnalyzerEnabled: boolean; readonly sentinelOneManualHostActionsEnabled: boolean; readonly jsonPrebuiltRulesDiffingEnabled: boolean; readonly timelineEsqlTabDisabled: boolean; }" + "{ readonly tGridEnabled: boolean; readonly tGridEventRenderedViewEnabled: boolean; readonly excludePoliciesInFilterEnabled: boolean; readonly kubernetesEnabled: boolean; readonly chartEmbeddablesEnabled: boolean; readonly donutChartEmbeddablesEnabled: boolean; readonly previewTelemetryUrlEnabled: boolean; readonly insightsRelatedAlertsByProcessAncestry: boolean; readonly extendedRuleExecutionLoggingEnabled: boolean; readonly assistantStreamingEnabled: boolean; readonly socTrendsEnabled: boolean; readonly responseActionsEnabled: boolean; readonly endpointResponseActionsEnabled: boolean; readonly responseActionUploadEnabled: boolean; readonly automatedProcessActionsEnabled: boolean; readonly responseActionsSentinelOneV1Enabled: boolean; readonly alertsPageChartsEnabled: boolean; readonly alertTypeEnabled: boolean; readonly expandableFlyoutInCreateRuleEnabled: boolean; readonly alertsPageFiltersEnabled: boolean; readonly assistantModelEvaluation: boolean; readonly newUserDetailsFlyout: boolean; readonly newUserDetailsFlyoutManagedUser: boolean; readonly newHostDetailsFlyout: boolean; readonly riskScoringPersistence: boolean; readonly riskScoringRoutesEnabled: boolean; readonly esqlRulesDisabled: boolean; readonly protectionUpdatesEnabled: boolean; readonly disableTimelineSaveTour: boolean; readonly riskEnginePrivilegesRouteEnabled: boolean; readonly alertSuppressionForIndicatorMatchRuleEnabled: boolean; readonly entityAnalyticsAssetCriticalityEnabled: boolean; readonly sentinelOneDataInAnalyzerEnabled: boolean; readonly sentinelOneManualHostActionsEnabled: boolean; readonly crowdstrikeDataInAnalyzerEnabled: boolean; readonly jsonPrebuiltRulesDiffingEnabled: boolean; readonly timelineEsqlTabDisabled: boolean; readonly perFieldPrebuiltRulesDiffingEnabled: boolean; }" ], "path": "x-pack/plugins/security_solution/common/experimental_features.ts", "deprecated": false, @@ -3330,7 +3330,7 @@ "\nA list of allowed values that can be used in `xpack.securitySolution.enableExperimental`.\nThis object is then used to validate and parse the value entered." ], "signature": [ - "{ readonly tGridEnabled: true; readonly tGridEventRenderedViewEnabled: true; readonly excludePoliciesInFilterEnabled: false; readonly kubernetesEnabled: true; readonly chartEmbeddablesEnabled: true; readonly donutChartEmbeddablesEnabled: false; readonly previewTelemetryUrlEnabled: false; readonly insightsRelatedAlertsByProcessAncestry: true; readonly extendedRuleExecutionLoggingEnabled: false; readonly assistantStreamingEnabled: false; readonly socTrendsEnabled: false; readonly responseActionsEnabled: true; readonly endpointResponseActionsEnabled: true; readonly responseActionUploadEnabled: true; readonly automatedProcessActionsEnabled: false; readonly responseActionsSentinelOneV1Enabled: false; readonly alertsPageChartsEnabled: true; readonly alertTypeEnabled: false; readonly expandableFlyoutInCreateRuleEnabled: true; readonly alertsPageFiltersEnabled: true; readonly assistantModelEvaluation: false; readonly newUserDetailsFlyout: false; readonly newUserDetailsFlyoutManagedUser: false; readonly newHostDetailsFlyout: false; readonly riskScoringPersistence: true; readonly riskScoringRoutesEnabled: true; readonly esqlRulesDisabled: false; readonly protectionUpdatesEnabled: true; readonly disableTimelineSaveTour: false; readonly riskEnginePrivilegesRouteEnabled: true; readonly alertSuppressionForIndicatorMatchRuleEnabled: false; readonly entityAnalyticsAssetCriticalityEnabled: false; readonly sentinelOneDataInAnalyzerEnabled: false; readonly sentinelOneManualHostActionsEnabled: true; readonly jsonPrebuiltRulesDiffingEnabled: true; readonly timelineEsqlTabDisabled: false; }" + "{ readonly tGridEnabled: true; readonly tGridEventRenderedViewEnabled: true; readonly excludePoliciesInFilterEnabled: false; readonly kubernetesEnabled: true; readonly chartEmbeddablesEnabled: true; readonly donutChartEmbeddablesEnabled: false; readonly previewTelemetryUrlEnabled: false; readonly insightsRelatedAlertsByProcessAncestry: true; readonly extendedRuleExecutionLoggingEnabled: false; readonly assistantStreamingEnabled: false; readonly socTrendsEnabled: false; readonly responseActionsEnabled: true; readonly endpointResponseActionsEnabled: true; readonly responseActionUploadEnabled: true; readonly automatedProcessActionsEnabled: false; readonly responseActionsSentinelOneV1Enabled: false; readonly alertsPageChartsEnabled: true; readonly alertTypeEnabled: false; readonly expandableFlyoutInCreateRuleEnabled: true; readonly alertsPageFiltersEnabled: true; readonly assistantModelEvaluation: false; readonly newUserDetailsFlyout: false; readonly newUserDetailsFlyoutManagedUser: false; readonly newHostDetailsFlyout: false; readonly riskScoringPersistence: true; readonly riskScoringRoutesEnabled: true; readonly esqlRulesDisabled: false; readonly protectionUpdatesEnabled: true; readonly disableTimelineSaveTour: false; readonly riskEnginePrivilegesRouteEnabled: true; readonly alertSuppressionForIndicatorMatchRuleEnabled: false; readonly entityAnalyticsAssetCriticalityEnabled: false; readonly sentinelOneDataInAnalyzerEnabled: false; readonly sentinelOneManualHostActionsEnabled: true; readonly crowdstrikeDataInAnalyzerEnabled: false; readonly jsonPrebuiltRulesDiffingEnabled: true; readonly timelineEsqlTabDisabled: false; readonly perFieldPrebuiltRulesDiffingEnabled: false; }" ], "path": "x-pack/plugins/security_solution/common/experimental_features.ts", "deprecated": false, diff --git a/api_docs/security_solution.mdx b/api_docs/security_solution.mdx index c520b8435ddb3..309279c6e9942 100644 --- a/api_docs/security_solution.mdx +++ b/api_docs/security_solution.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/securitySolution title: "securitySolution" image: https://source.unsplash.com/400x175/?github description: API docs for the securitySolution plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'securitySolution'] --- import securitySolutionObj from './security_solution.devdocs.json'; diff --git a/api_docs/security_solution_ess.mdx b/api_docs/security_solution_ess.mdx index e384c489ededb..f28e92af62605 100644 --- a/api_docs/security_solution_ess.mdx +++ b/api_docs/security_solution_ess.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/securitySolutionEss title: "securitySolutionEss" image: https://source.unsplash.com/400x175/?github description: API docs for the securitySolutionEss plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'securitySolutionEss'] --- import securitySolutionEssObj from './security_solution_ess.devdocs.json'; diff --git a/api_docs/security_solution_serverless.mdx b/api_docs/security_solution_serverless.mdx index 9adc1c60fd26c..8a9191ebc6db3 100644 --- a/api_docs/security_solution_serverless.mdx +++ b/api_docs/security_solution_serverless.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/securitySolutionServerless title: "securitySolutionServerless" image: https://source.unsplash.com/400x175/?github description: API docs for the securitySolutionServerless plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'securitySolutionServerless'] --- import securitySolutionServerlessObj from './security_solution_serverless.devdocs.json'; diff --git a/api_docs/serverless.mdx b/api_docs/serverless.mdx index b1ead1a134f75..d9dfc05681a40 100644 --- a/api_docs/serverless.mdx +++ b/api_docs/serverless.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/serverless title: "serverless" image: https://source.unsplash.com/400x175/?github description: API docs for the serverless plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'serverless'] --- import serverlessObj from './serverless.devdocs.json'; diff --git a/api_docs/serverless_observability.mdx b/api_docs/serverless_observability.mdx index eb8dc89f51b43..c8b43f2c40dc3 100644 --- a/api_docs/serverless_observability.mdx +++ b/api_docs/serverless_observability.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/serverlessObservability title: "serverlessObservability" image: https://source.unsplash.com/400x175/?github description: API docs for the serverlessObservability plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'serverlessObservability'] --- import serverlessObservabilityObj from './serverless_observability.devdocs.json'; diff --git a/api_docs/serverless_search.mdx b/api_docs/serverless_search.mdx index f8551d622d8ff..627455f0b164b 100644 --- a/api_docs/serverless_search.mdx +++ b/api_docs/serverless_search.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/serverlessSearch title: "serverlessSearch" image: https://source.unsplash.com/400x175/?github description: API docs for the serverlessSearch plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'serverlessSearch'] --- import serverlessSearchObj from './serverless_search.devdocs.json'; diff --git a/api_docs/session_view.mdx b/api_docs/session_view.mdx index b7571517000cf..fc0e3d59fc364 100644 --- a/api_docs/session_view.mdx +++ b/api_docs/session_view.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/sessionView title: "sessionView" image: https://source.unsplash.com/400x175/?github description: API docs for the sessionView plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'sessionView'] --- import sessionViewObj from './session_view.devdocs.json'; diff --git a/api_docs/share.mdx b/api_docs/share.mdx index 38cef1ea131a0..f3833727faff5 100644 --- a/api_docs/share.mdx +++ b/api_docs/share.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/share title: "share" image: https://source.unsplash.com/400x175/?github description: API docs for the share plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'share'] --- import shareObj from './share.devdocs.json'; diff --git a/api_docs/snapshot_restore.mdx b/api_docs/snapshot_restore.mdx index 16bd8997f0205..20693b0fbb78b 100644 --- a/api_docs/snapshot_restore.mdx +++ b/api_docs/snapshot_restore.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/snapshotRestore title: "snapshotRestore" image: https://source.unsplash.com/400x175/?github description: API docs for the snapshotRestore plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'snapshotRestore'] --- import snapshotRestoreObj from './snapshot_restore.devdocs.json'; diff --git a/api_docs/spaces.mdx b/api_docs/spaces.mdx index 3ade3d8371583..4b25cb2d7a8f5 100644 --- a/api_docs/spaces.mdx +++ b/api_docs/spaces.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/spaces title: "spaces" image: https://source.unsplash.com/400x175/?github description: API docs for the spaces plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'spaces'] --- import spacesObj from './spaces.devdocs.json'; diff --git a/api_docs/stack_alerts.mdx b/api_docs/stack_alerts.mdx index aecc5244138ff..5ea851514bf77 100644 --- a/api_docs/stack_alerts.mdx +++ b/api_docs/stack_alerts.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/stackAlerts title: "stackAlerts" image: https://source.unsplash.com/400x175/?github description: API docs for the stackAlerts plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'stackAlerts'] --- import stackAlertsObj from './stack_alerts.devdocs.json'; diff --git a/api_docs/stack_connectors.mdx b/api_docs/stack_connectors.mdx index f343dfd3579d8..39fdeed64d418 100644 --- a/api_docs/stack_connectors.mdx +++ b/api_docs/stack_connectors.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/stackConnectors title: "stackConnectors" image: https://source.unsplash.com/400x175/?github description: API docs for the stackConnectors plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'stackConnectors'] --- import stackConnectorsObj from './stack_connectors.devdocs.json'; diff --git a/api_docs/task_manager.mdx b/api_docs/task_manager.mdx index 191b215c413ca..38405d05e71e7 100644 --- a/api_docs/task_manager.mdx +++ b/api_docs/task_manager.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/taskManager title: "taskManager" image: https://source.unsplash.com/400x175/?github description: API docs for the taskManager plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'taskManager'] --- import taskManagerObj from './task_manager.devdocs.json'; diff --git a/api_docs/telemetry.devdocs.json b/api_docs/telemetry.devdocs.json index 38a7798de26e1..6a4b8332f9d43 100644 --- a/api_docs/telemetry.devdocs.json +++ b/api_docs/telemetry.devdocs.json @@ -811,6 +811,10 @@ "plugin": "securitySolution", "path": "x-pack/plugins/security_solution/server/lib/telemetry/sender.ts" }, + { + "plugin": "securitySolution", + "path": "x-pack/plugins/security_solution/server/lib/telemetry/sender_helpers.ts" + }, { "plugin": "synthetics", "path": "x-pack/plugins/synthetics/server/telemetry/sender.ts" diff --git a/api_docs/telemetry.mdx b/api_docs/telemetry.mdx index 3f9d7c824c5c1..ec1e81cb4094b 100644 --- a/api_docs/telemetry.mdx +++ b/api_docs/telemetry.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/telemetry title: "telemetry" image: https://source.unsplash.com/400x175/?github description: API docs for the telemetry plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'telemetry'] --- import telemetryObj from './telemetry.devdocs.json'; diff --git a/api_docs/telemetry_collection_manager.mdx b/api_docs/telemetry_collection_manager.mdx index 1972d6f1a600f..4c329b3b69ef3 100644 --- a/api_docs/telemetry_collection_manager.mdx +++ b/api_docs/telemetry_collection_manager.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/telemetryCollectionManager title: "telemetryCollectionManager" image: https://source.unsplash.com/400x175/?github description: API docs for the telemetryCollectionManager plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'telemetryCollectionManager'] --- import telemetryCollectionManagerObj from './telemetry_collection_manager.devdocs.json'; diff --git a/api_docs/telemetry_collection_xpack.mdx b/api_docs/telemetry_collection_xpack.mdx index 42ee1667891d4..501222ccd2fda 100644 --- a/api_docs/telemetry_collection_xpack.mdx +++ b/api_docs/telemetry_collection_xpack.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/telemetryCollectionXpack title: "telemetryCollectionXpack" image: https://source.unsplash.com/400x175/?github description: API docs for the telemetryCollectionXpack plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'telemetryCollectionXpack'] --- import telemetryCollectionXpackObj from './telemetry_collection_xpack.devdocs.json'; diff --git a/api_docs/telemetry_management_section.mdx b/api_docs/telemetry_management_section.mdx index 4d6fa23954621..a1ff2e3c796fe 100644 --- a/api_docs/telemetry_management_section.mdx +++ b/api_docs/telemetry_management_section.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/telemetryManagementSection title: "telemetryManagementSection" image: https://source.unsplash.com/400x175/?github description: API docs for the telemetryManagementSection plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'telemetryManagementSection'] --- import telemetryManagementSectionObj from './telemetry_management_section.devdocs.json'; diff --git a/api_docs/text_based_languages.mdx b/api_docs/text_based_languages.mdx index dec3c90e11512..6369971ae7e3d 100644 --- a/api_docs/text_based_languages.mdx +++ b/api_docs/text_based_languages.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/textBasedLanguages title: "textBasedLanguages" image: https://source.unsplash.com/400x175/?github description: API docs for the textBasedLanguages plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'textBasedLanguages'] --- import textBasedLanguagesObj from './text_based_languages.devdocs.json'; diff --git a/api_docs/threat_intelligence.mdx b/api_docs/threat_intelligence.mdx index aac17751a061a..0c4e52d3939d8 100644 --- a/api_docs/threat_intelligence.mdx +++ b/api_docs/threat_intelligence.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/threatIntelligence title: "threatIntelligence" image: https://source.unsplash.com/400x175/?github description: API docs for the threatIntelligence plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'threatIntelligence'] --- import threatIntelligenceObj from './threat_intelligence.devdocs.json'; diff --git a/api_docs/timelines.mdx b/api_docs/timelines.mdx index e05f82a522942..3818eed222c4c 100644 --- a/api_docs/timelines.mdx +++ b/api_docs/timelines.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/timelines title: "timelines" image: https://source.unsplash.com/400x175/?github description: API docs for the timelines plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'timelines'] --- import timelinesObj from './timelines.devdocs.json'; diff --git a/api_docs/transform.mdx b/api_docs/transform.mdx index e6a6b6d43ac7d..78e0f6e9ff83f 100644 --- a/api_docs/transform.mdx +++ b/api_docs/transform.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/transform title: "transform" image: https://source.unsplash.com/400x175/?github description: API docs for the transform plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'transform'] --- import transformObj from './transform.devdocs.json'; diff --git a/api_docs/triggers_actions_ui.devdocs.json b/api_docs/triggers_actions_ui.devdocs.json index e89eb79dd824a..67dc4f9f6e127 100644 --- a/api_docs/triggers_actions_ui.devdocs.json +++ b/api_docs/triggers_actions_ui.devdocs.json @@ -2886,6 +2886,20 @@ "deprecated": false, "trackAdoption": false }, + { + "parentPluginId": "triggersActionsUi", + "id": "def-public.ActionTypeModel.isBeta", + "type": "CompoundType", + "tags": [], + "label": "isBeta", + "description": [], + "signature": [ + "boolean | undefined" + ], + "path": "x-pack/plugins/triggers_actions_ui/public/types.ts", + "deprecated": false, + "trackAdoption": false + }, { "parentPluginId": "triggersActionsUi", "id": "def-public.ActionTypeModel.isExperimental", diff --git a/api_docs/triggers_actions_ui.mdx b/api_docs/triggers_actions_ui.mdx index 8f21b225a8476..e3cf217f2d5e9 100644 --- a/api_docs/triggers_actions_ui.mdx +++ b/api_docs/triggers_actions_ui.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/triggersActionsUi title: "triggersActionsUi" image: https://source.unsplash.com/400x175/?github description: API docs for the triggersActionsUi plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'triggersActionsUi'] --- import triggersActionsUiObj from './triggers_actions_ui.devdocs.json'; @@ -21,7 +21,7 @@ Contact [@elastic/response-ops](https://github.com/orgs/elastic/teams/response-o | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 596 | 1 | 570 | 58 | +| 597 | 1 | 571 | 58 | ## Client diff --git a/api_docs/ui_actions.mdx b/api_docs/ui_actions.mdx index ca171045634c9..c0e17e13f0168 100644 --- a/api_docs/ui_actions.mdx +++ b/api_docs/ui_actions.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/uiActions title: "uiActions" image: https://source.unsplash.com/400x175/?github description: API docs for the uiActions plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'uiActions'] --- import uiActionsObj from './ui_actions.devdocs.json'; diff --git a/api_docs/ui_actions_enhanced.mdx b/api_docs/ui_actions_enhanced.mdx index be262f73df74f..62dcd9671f184 100644 --- a/api_docs/ui_actions_enhanced.mdx +++ b/api_docs/ui_actions_enhanced.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/uiActionsEnhanced title: "uiActionsEnhanced" image: https://source.unsplash.com/400x175/?github description: API docs for the uiActionsEnhanced plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'uiActionsEnhanced'] --- import uiActionsEnhancedObj from './ui_actions_enhanced.devdocs.json'; diff --git a/api_docs/unified_doc_viewer.mdx b/api_docs/unified_doc_viewer.mdx index ec901217041be..dc34ca68a1d5e 100644 --- a/api_docs/unified_doc_viewer.mdx +++ b/api_docs/unified_doc_viewer.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/unifiedDocViewer title: "unifiedDocViewer" image: https://source.unsplash.com/400x175/?github description: API docs for the unifiedDocViewer plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'unifiedDocViewer'] --- import unifiedDocViewerObj from './unified_doc_viewer.devdocs.json'; diff --git a/api_docs/unified_histogram.mdx b/api_docs/unified_histogram.mdx index 873621a255c1d..d89f6e2a9c064 100644 --- a/api_docs/unified_histogram.mdx +++ b/api_docs/unified_histogram.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/unifiedHistogram title: "unifiedHistogram" image: https://source.unsplash.com/400x175/?github description: API docs for the unifiedHistogram plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'unifiedHistogram'] --- import unifiedHistogramObj from './unified_histogram.devdocs.json'; diff --git a/api_docs/unified_search.devdocs.json b/api_docs/unified_search.devdocs.json index 2485b27bdd804..407451c4d14b6 100644 --- a/api_docs/unified_search.devdocs.json +++ b/api_docs/unified_search.devdocs.json @@ -1574,6 +1574,78 @@ "deprecated": false, "trackAdoption": false }, + { + "parentPluginId": "unifiedSearch", + "id": "def-public.IUnifiedSearchPluginServices.analytics", + "type": "Object", + "tags": [], + "label": "analytics", + "description": [], + "signature": [ + "{ optIn: (optInConfig: ", + { + "pluginId": "@kbn/analytics-client", + "scope": "common", + "docId": "kibKbnAnalyticsClientPluginApi", + "section": "def-common.OptInConfig", + "text": "OptInConfig" + }, + ") => void; reportEvent: (eventType: string, eventData: EventTypeData) => void; readonly telemetryCounter$: ", + "Observable", + "<", + { + "pluginId": "@kbn/analytics-client", + "scope": "common", + "docId": "kibKbnAnalyticsClientPluginApi", + "section": "def-common.TelemetryCounter", + "text": "TelemetryCounter" + }, + ">; }" + ], + "path": "src/plugins/unified_search/public/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "unifiedSearch", + "id": "def-public.IUnifiedSearchPluginServices.i18n", + "type": "Object", + "tags": [], + "label": "i18n", + "description": [], + "signature": [ + { + "pluginId": "@kbn/core-i18n-browser", + "scope": "common", + "docId": "kibKbnCoreI18nBrowserPluginApi", + "section": "def-common.I18nStart", + "text": "I18nStart" + } + ], + "path": "src/plugins/unified_search/public/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "unifiedSearch", + "id": "def-public.IUnifiedSearchPluginServices.theme", + "type": "Object", + "tags": [], + "label": "theme", + "description": [], + "signature": [ + { + "pluginId": "@kbn/core-theme-browser", + "scope": "common", + "docId": "kibKbnCoreThemeBrowserPluginApi", + "section": "def-common.ThemeServiceSetup", + "text": "ThemeServiceSetup" + } + ], + "path": "src/plugins/unified_search/public/types.ts", + "deprecated": false, + "trackAdoption": false + }, { "parentPluginId": "unifiedSearch", "id": "def-public.IUnifiedSearchPluginServices.storage", diff --git a/api_docs/unified_search.mdx b/api_docs/unified_search.mdx index 9414ca14f107f..31576ba1dc075 100644 --- a/api_docs/unified_search.mdx +++ b/api_docs/unified_search.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/unifiedSearch title: "unifiedSearch" image: https://source.unsplash.com/400x175/?github description: API docs for the unifiedSearch plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'unifiedSearch'] --- import unifiedSearchObj from './unified_search.devdocs.json'; @@ -21,7 +21,7 @@ Contact [@elastic/kibana-visualizations](https://github.com/orgs/elastic/teams/k | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 148 | 2 | 110 | 23 | +| 151 | 2 | 113 | 23 | ## Client diff --git a/api_docs/unified_search_autocomplete.mdx b/api_docs/unified_search_autocomplete.mdx index f174ec10d4396..a9fe8a3529abd 100644 --- a/api_docs/unified_search_autocomplete.mdx +++ b/api_docs/unified_search_autocomplete.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/unifiedSearch-autocomplete title: "unifiedSearch.autocomplete" image: https://source.unsplash.com/400x175/?github description: API docs for the unifiedSearch.autocomplete plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'unifiedSearch.autocomplete'] --- import unifiedSearchAutocompleteObj from './unified_search_autocomplete.devdocs.json'; @@ -21,7 +21,7 @@ Contact [@elastic/kibana-visualizations](https://github.com/orgs/elastic/teams/k | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 148 | 2 | 110 | 23 | +| 151 | 2 | 113 | 23 | ## Client diff --git a/api_docs/uptime.mdx b/api_docs/uptime.mdx index 20ef27b80ec98..5fcecdf3d3fc4 100644 --- a/api_docs/uptime.mdx +++ b/api_docs/uptime.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/uptime title: "uptime" image: https://source.unsplash.com/400x175/?github description: API docs for the uptime plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'uptime'] --- import uptimeObj from './uptime.devdocs.json'; diff --git a/api_docs/url_forwarding.mdx b/api_docs/url_forwarding.mdx index 470477e5419e4..2011de96ae24b 100644 --- a/api_docs/url_forwarding.mdx +++ b/api_docs/url_forwarding.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/urlForwarding title: "urlForwarding" image: https://source.unsplash.com/400x175/?github description: API docs for the urlForwarding plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'urlForwarding'] --- import urlForwardingObj from './url_forwarding.devdocs.json'; diff --git a/api_docs/usage_collection.mdx b/api_docs/usage_collection.mdx index be823aeea439a..ca27379f90d5f 100644 --- a/api_docs/usage_collection.mdx +++ b/api_docs/usage_collection.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/usageCollection title: "usageCollection" image: https://source.unsplash.com/400x175/?github description: API docs for the usageCollection plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'usageCollection'] --- import usageCollectionObj from './usage_collection.devdocs.json'; diff --git a/api_docs/ux.mdx b/api_docs/ux.mdx index 7a7f72b1ae963..8ed74ab18b9f1 100644 --- a/api_docs/ux.mdx +++ b/api_docs/ux.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/ux title: "ux" image: https://source.unsplash.com/400x175/?github description: API docs for the ux plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'ux'] --- import uxObj from './ux.devdocs.json'; diff --git a/api_docs/vis_default_editor.mdx b/api_docs/vis_default_editor.mdx index ae73fb4742324..b2b60bc661083 100644 --- a/api_docs/vis_default_editor.mdx +++ b/api_docs/vis_default_editor.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/visDefaultEditor title: "visDefaultEditor" image: https://source.unsplash.com/400x175/?github description: API docs for the visDefaultEditor plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'visDefaultEditor'] --- import visDefaultEditorObj from './vis_default_editor.devdocs.json'; diff --git a/api_docs/vis_type_gauge.mdx b/api_docs/vis_type_gauge.mdx index 6f0d11a214381..3df22b791030c 100644 --- a/api_docs/vis_type_gauge.mdx +++ b/api_docs/vis_type_gauge.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/visTypeGauge title: "visTypeGauge" image: https://source.unsplash.com/400x175/?github description: API docs for the visTypeGauge plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'visTypeGauge'] --- import visTypeGaugeObj from './vis_type_gauge.devdocs.json'; diff --git a/api_docs/vis_type_heatmap.mdx b/api_docs/vis_type_heatmap.mdx index abe5de120ac76..5bf3d7f524e42 100644 --- a/api_docs/vis_type_heatmap.mdx +++ b/api_docs/vis_type_heatmap.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/visTypeHeatmap title: "visTypeHeatmap" image: https://source.unsplash.com/400x175/?github description: API docs for the visTypeHeatmap plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'visTypeHeatmap'] --- import visTypeHeatmapObj from './vis_type_heatmap.devdocs.json'; diff --git a/api_docs/vis_type_pie.mdx b/api_docs/vis_type_pie.mdx index b5abe58ea23d4..aacf6c60bf317 100644 --- a/api_docs/vis_type_pie.mdx +++ b/api_docs/vis_type_pie.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/visTypePie title: "visTypePie" image: https://source.unsplash.com/400x175/?github description: API docs for the visTypePie plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'visTypePie'] --- import visTypePieObj from './vis_type_pie.devdocs.json'; diff --git a/api_docs/vis_type_table.mdx b/api_docs/vis_type_table.mdx index 83a09d8ec8543..51476b52b8c7b 100644 --- a/api_docs/vis_type_table.mdx +++ b/api_docs/vis_type_table.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/visTypeTable title: "visTypeTable" image: https://source.unsplash.com/400x175/?github description: API docs for the visTypeTable plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'visTypeTable'] --- import visTypeTableObj from './vis_type_table.devdocs.json'; diff --git a/api_docs/vis_type_timelion.mdx b/api_docs/vis_type_timelion.mdx index 1f673b4d7b32d..4d57847bd677d 100644 --- a/api_docs/vis_type_timelion.mdx +++ b/api_docs/vis_type_timelion.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/visTypeTimelion title: "visTypeTimelion" image: https://source.unsplash.com/400x175/?github description: API docs for the visTypeTimelion plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'visTypeTimelion'] --- import visTypeTimelionObj from './vis_type_timelion.devdocs.json'; diff --git a/api_docs/vis_type_timeseries.mdx b/api_docs/vis_type_timeseries.mdx index b4dd3114e57e9..b262592099d11 100644 --- a/api_docs/vis_type_timeseries.mdx +++ b/api_docs/vis_type_timeseries.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/visTypeTimeseries title: "visTypeTimeseries" image: https://source.unsplash.com/400x175/?github description: API docs for the visTypeTimeseries plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'visTypeTimeseries'] --- import visTypeTimeseriesObj from './vis_type_timeseries.devdocs.json'; diff --git a/api_docs/vis_type_vega.mdx b/api_docs/vis_type_vega.mdx index ea81ae5c08bc8..a713e28c6ca0e 100644 --- a/api_docs/vis_type_vega.mdx +++ b/api_docs/vis_type_vega.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/visTypeVega title: "visTypeVega" image: https://source.unsplash.com/400x175/?github description: API docs for the visTypeVega plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'visTypeVega'] --- import visTypeVegaObj from './vis_type_vega.devdocs.json'; diff --git a/api_docs/vis_type_vislib.mdx b/api_docs/vis_type_vislib.mdx index 3ae49e9632210..77297785ff7ac 100644 --- a/api_docs/vis_type_vislib.mdx +++ b/api_docs/vis_type_vislib.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/visTypeVislib title: "visTypeVislib" image: https://source.unsplash.com/400x175/?github description: API docs for the visTypeVislib plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'visTypeVislib'] --- import visTypeVislibObj from './vis_type_vislib.devdocs.json'; diff --git a/api_docs/vis_type_xy.mdx b/api_docs/vis_type_xy.mdx index 430efbb329603..9bd6b0fd218c4 100644 --- a/api_docs/vis_type_xy.mdx +++ b/api_docs/vis_type_xy.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/visTypeXy title: "visTypeXy" image: https://source.unsplash.com/400x175/?github description: API docs for the visTypeXy plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'visTypeXy'] --- import visTypeXyObj from './vis_type_xy.devdocs.json'; diff --git a/api_docs/visualizations.devdocs.json b/api_docs/visualizations.devdocs.json index a6e2e2f68542e..f04ff41d211bd 100644 --- a/api_docs/visualizations.devdocs.json +++ b/api_docs/visualizations.devdocs.json @@ -6864,7 +6864,7 @@ "section": "def-common.PanelPackage", "text": "PanelPackage" }, - ") => Promise; } & Partial Promise; getChildIds: () => string[]; getChild: (childId: string) => unknown; } & Partial string; destroy: () => void; getQuery: () => ", + "; getDescription: () => string; localFilters: ", { - "pluginId": "@kbn/es-query", - "scope": "common", - "docId": "kibKbnEsQueryPluginApi", - "section": "def-common.Query", - "text": "Query" - }, - " | ", - { - "pluginId": "@kbn/es-query", + "pluginId": "@kbn/presentation-publishing", "scope": "common", - "docId": "kibKbnEsQueryPluginApi", - "section": "def-common.AggregateQuery", - "text": "AggregateQuery" + "docId": "kibKbnPresentationPublishingPluginApi", + "section": "def-common.PublishingSubject", + "text": "PublishingSubject" }, - " | undefined; getFilters: () => ", + "<", { "pluginId": "@kbn/es-query", "scope": "common", @@ -6984,7 +6976,7 @@ "section": "def-common.Filter", "text": "Filter" }, - "[]; onEdit: () => void; localQuery: ", + "[] | undefined>; localQuery: ", { "pluginId": "@kbn/presentation-publishing", "scope": "common", @@ -7008,15 +7000,23 @@ "section": "def-common.AggregateQuery", "text": "AggregateQuery" }, - " | undefined>; localFilters: ", + " | undefined>; destroy: () => void; getQuery: () => ", { - "pluginId": "@kbn/presentation-publishing", + "pluginId": "@kbn/es-query", "scope": "common", - "docId": "kibKbnPresentationPublishingPluginApi", - "section": "def-common.PublishingSubject", - "text": "PublishingSubject" + "docId": "kibKbnEsQueryPluginApi", + "section": "def-common.Query", + "text": "Query" }, - "<", + " | ", + { + "pluginId": "@kbn/es-query", + "scope": "common", + "docId": "kibKbnEsQueryPluginApi", + "section": "def-common.AggregateQuery", + "text": "AggregateQuery" + }, + " | undefined; getFilters: () => ", { "pluginId": "@kbn/es-query", "scope": "common", @@ -7024,7 +7024,7 @@ "section": "def-common.Filter", "text": "Filter" }, - "[] | undefined>; onPhaseChange: ", + "[]; onEdit: () => void; onPhaseChange: ", { "pluginId": "@kbn/presentation-publishing", "scope": "common", diff --git a/api_docs/visualizations.mdx b/api_docs/visualizations.mdx index 95a6ac56889dd..ed3dc88978610 100644 --- a/api_docs/visualizations.mdx +++ b/api_docs/visualizations.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/visualizations title: "visualizations" image: https://source.unsplash.com/400x175/?github description: API docs for the visualizations plugin -date: 2024-02-12 +date: 2024-02-13 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'visualizations'] --- import visualizationsObj from './visualizations.devdocs.json'; From 79f63c2a3dd1b484a3583f5b5a991cd72a20528c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20R=C3=BChsen?= Date: Tue, 13 Feb 2024 07:40:21 +0100 Subject: [PATCH 43/83] [Profiling] Ignore error frames (#176537) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This change allows the Universal Profiling agent to send error frames, which will give us more accurate values for CO2 emission and $ costs. The reason is that unwinding errors resulting in 0-length stacktraces happen quite often. These are not sent to the backend currently, so the related CPU activity doesn't go into the calculations. This can make up showing 10% less CPU / CO2 / costs in the UI. Adding artificial error frames in case of unwinding errors guarantees that stacktraces always have a length of > 0. Once we settled on how error frames can be displayed in a user-friendly way, this code can be removed. --------- Co-authored-by: Joel Höner Co-authored-by: Caue Marcondes --- docs/management/advanced-options.asciidoc | 3 ++ .../common/flamegraph.test.ts | 2 +- .../kbn-profiling-utils/common/flamegraph.ts | 20 +++++++++- .../common/functions.test.ts | 7 +++- .../kbn-profiling-utils/common/functions.ts | 9 ++++- .../common/profiling.test.ts | 15 ++++++++ .../kbn-profiling-utils/common/profiling.ts | 38 ++++++++++++++++++- .../common/stack_traces.test.ts | 6 +-- .../common/stack_traces.ts | 18 +++++++-- packages/kbn-profiling-utils/index.ts | 1 + .../server/collectors/management/schema.ts | 4 ++ .../server/collectors/management/types.ts | 1 + src/plugins/telemetry/schema/oss_plugins.json | 6 +++ x-pack/plugins/observability/common/index.ts | 1 + .../observability/common/ui_settings_keys.ts | 1 + .../observability/server/ui_settings.ts | 10 +++++ .../common/columnar_view_model.test.ts | 2 +- .../profiling/common/frame_type_colors.ts | 27 +++++++------ .../e2e/profiling_views/settings.cy.ts | 1 + .../flamegraph/embeddable_flamegraph.tsx | 28 +++++++++----- x-pack/plugins/profiling/public/services.ts | 6 ++- .../differential_flamegraphs/index.tsx | 7 ++++ .../views/flamegraphs/flamegraph/index.tsx | 7 +++- .../profiling/public/views/settings/index.tsx | 18 ++++++++- .../server/routes/search_stacktraces.ts | 4 +- .../profiling/server/routes/topn.test.ts | 1 + .../plugins/profiling/server/routes/topn.ts | 9 ++++- .../server/services/functions/index.ts | 5 +++ .../services/search_stack_traces/index.ts | 4 +- 29 files changed, 216 insertions(+), 45 deletions(-) diff --git a/docs/management/advanced-options.asciidoc b/docs/management/advanced-options.asciidoc index 41e2d6c6bb45a..28f334d207514 100644 --- a/docs/management/advanced-options.asciidoc +++ b/docs/management/advanced-options.asciidoc @@ -433,6 +433,9 @@ Set the default environment for the APM app. When left empty, data from all envi [[observability-apm-enable-profiling]]`observability:apmEnableProfilingIntegration`:: Enable the Universal Profiling integration in APM. +[[observability-profiling-show-error-frames]]`observability:profilingShowErrorFrames`:: +Show error frames in the Universal Profiling views to indicate stack unwinding failures. + [[observability-apm-enable-table-search-bar]]`observability:apmEnableTableSearchBar`:: beta:[] Enables faster searching in APM tables by adding a handy search bar with live filtering. Available for the following tables: Services, Transactions, and Errors. diff --git a/packages/kbn-profiling-utils/common/flamegraph.test.ts b/packages/kbn-profiling-utils/common/flamegraph.test.ts index 676c253b9e6b2..ab3c0e63c38cf 100644 --- a/packages/kbn-profiling-utils/common/flamegraph.test.ts +++ b/packages/kbn-profiling-utils/common/flamegraph.test.ts @@ -10,7 +10,7 @@ import { createFlameGraph } from './flamegraph'; import { baseFlamegraph } from './__fixtures__/base_flamegraph'; describe('Flamegraph', () => { - const flamegraph = createFlameGraph(baseFlamegraph); + const flamegraph = createFlameGraph(baseFlamegraph, false); it('base flamegraph has non-zero total seconds', () => { expect(baseFlamegraph.TotalSeconds).toEqual(4.980000019073486); diff --git a/packages/kbn-profiling-utils/common/flamegraph.ts b/packages/kbn-profiling-utils/common/flamegraph.ts index 355919a6c08d8..27d544d8712ef 100644 --- a/packages/kbn-profiling-utils/common/flamegraph.ts +++ b/packages/kbn-profiling-utils/common/flamegraph.ts @@ -8,7 +8,7 @@ import { createFrameGroupID } from './frame_group'; import { fnv1a64 } from './hash'; -import { createStackFrameMetadata, getCalleeLabel } from './profiling'; +import { createStackFrameMetadata, getCalleeLabel, isErrorFrame } from './profiling'; import { convertTonsToKgs } from './utils'; /** @@ -81,9 +81,25 @@ export interface ElasticFlameGraph * This allows us to create a flamegraph in two steps (e.g. first on the server * and finally in the browser). * @param base BaseFlameGraph + * @param showErrorFrames * @returns ElasticFlameGraph */ -export function createFlameGraph(base: BaseFlameGraph): ElasticFlameGraph { +export function createFlameGraph( + base: BaseFlameGraph, + showErrorFrames: boolean +): ElasticFlameGraph { + if (!showErrorFrames) { + // This loop jumps over the error frames in the graph. + // Error frames only appear as child nodes of the root frame. + // Error frames only have a single child node. + for (let i = 0; i < base.Edges[0].length; i++) { + const childNodeID = base.Edges[0][i]; + if (isErrorFrame(base.FrameType[childNodeID])) { + base.Edges[0][i] = base.Edges[childNodeID][0]; + } + } + } + const graph: ElasticFlameGraph = { Size: base.Size, SamplingRate: base.SamplingRate, diff --git a/packages/kbn-profiling-utils/common/functions.test.ts b/packages/kbn-profiling-utils/common/functions.test.ts index d7d59d26abf75..fa30c2abff353 100644 --- a/packages/kbn-profiling-utils/common/functions.test.ts +++ b/packages/kbn-profiling-utils/common/functions.test.ts @@ -12,8 +12,10 @@ import { decodeStackTraceResponse } from '..'; import { stacktraces } from './__fixtures__/stacktraces'; describe('TopN function operations', () => { - const { events, stackTraces, stackFrames, executables, samplingRate } = - decodeStackTraceResponse(stacktraces); + const { events, stackTraces, stackFrames, executables, samplingRate } = decodeStackTraceResponse( + stacktraces, + false + ); const maxTopN = 5; const topNFunctions = createTopNFunctions({ events, @@ -23,6 +25,7 @@ describe('TopN function operations', () => { startIndex: 0, endIndex: maxTopN, samplingRate, + showErrorFrames: false, }); const exclusiveCounts = topNFunctions.TopN.map((value) => value.CountExclusive); diff --git a/packages/kbn-profiling-utils/common/functions.ts b/packages/kbn-profiling-utils/common/functions.ts index 72dd41899f861..7fcf8df2025ee 100644 --- a/packages/kbn-profiling-utils/common/functions.ts +++ b/packages/kbn-profiling-utils/common/functions.ts @@ -24,6 +24,7 @@ import { emptyStackFrame, emptyStackTrace, } from '..'; +import { isErrorFrame } from './profiling'; interface TopNFunctionAndFrameGroup { Frame: StackFrameMetadata; @@ -68,6 +69,7 @@ export function createTopNFunctions({ stackFrames, stackTraces, startIndex, + showErrorFrames, }: { endIndex: number; events: Map; @@ -76,6 +78,7 @@ export function createTopNFunctions({ stackFrames: Map; stackTraces: Map; startIndex: number; + showErrorFrames: boolean; }): TopNFunctions { // The `count` associated with a frame provides the total number of // traces in which that node has appeared at least once. However, a @@ -101,7 +104,11 @@ export function createTopNFunctions({ const lenStackTrace = stackTrace.FrameIDs.length; - for (let i = 0; i < lenStackTrace; i++) { + // Error frames only appear as first frame in a stacktrace. + const start = + !showErrorFrames && lenStackTrace > 0 && isErrorFrame(stackTrace.Types[0]) ? 1 : 0; + + for (let i = start; i < lenStackTrace; i++) { const frameID = stackTrace.FrameIDs[i]; const fileID = stackTrace.FileIDs[i]; const addressOrLine = stackTrace.AddressOrLines[i]; diff --git a/packages/kbn-profiling-utils/common/profiling.test.ts b/packages/kbn-profiling-utils/common/profiling.test.ts index e235633890069..af89594a5a683 100644 --- a/packages/kbn-profiling-utils/common/profiling.test.ts +++ b/packages/kbn-profiling-utils/common/profiling.test.ts @@ -14,6 +14,7 @@ import { getCalleeSource, getFrameSymbolStatus, getLanguageType, + normalizeFrameType, } from './profiling'; describe('Stack frame metadata operations', () => { @@ -101,6 +102,20 @@ describe('getFrameSymbolStatus', () => { }); }); +describe('normalizeFrameType', () => { + it('rewrites any frame with error bit to the generic error variant', () => { + expect(normalizeFrameType(0x83 as FrameType)).toEqual(FrameType.Error); + }); + it('rewrites unknown frame types to "unsymbolized" variant', () => { + expect(normalizeFrameType(0x123 as FrameType)).toEqual(FrameType.Unsymbolized); + }); + it('passes regular known frame types through untouched', () => { + expect(normalizeFrameType(FrameType.JVM)).toEqual(FrameType.JVM); + expect(normalizeFrameType(FrameType.Native)).toEqual(FrameType.Native); + expect(normalizeFrameType(FrameType.JavaScript)).toEqual(FrameType.JavaScript); + }); +}); + describe('getLanguageType', () => { [FrameType.Native, FrameType.Kernel].map((type) => it(`returns native for ${type}`, () => { diff --git a/packages/kbn-profiling-utils/common/profiling.ts b/packages/kbn-profiling-utils/common/profiling.ts index daea4166bea6e..9c6f643cb5f87 100644 --- a/packages/kbn-profiling-utils/common/profiling.ts +++ b/packages/kbn-profiling-utils/common/profiling.ts @@ -33,6 +33,8 @@ export enum FrameType { Perl, JavaScript, PHPJIT, + ErrorFlag = 0x80, + Error = 0xff, } const frameTypeDescriptions = { @@ -46,15 +48,41 @@ const frameTypeDescriptions = { [FrameType.Perl]: 'Perl', [FrameType.JavaScript]: 'JavaScript', [FrameType.PHPJIT]: 'PHP JIT', + [FrameType.ErrorFlag]: 'ErrorFlag', + [FrameType.Error]: 'Error', }; +export function isErrorFrame(ft: FrameType): boolean { + // eslint-disable-next-line no-bitwise + return (ft & FrameType.ErrorFlag) !== 0; +} + +/** + * normalize the given frame type + * @param ft FrameType + * @returns FrameType + */ +export function normalizeFrameType(ft: FrameType): FrameType { + // Normalize any frame type with error bit into our uniform error variant. + if (isErrorFrame(ft)) { + return FrameType.Error; + } + + // Guard against new / unknown frame types, rewriting them to "unsymbolized". + if (!(ft in frameTypeDescriptions)) { + return FrameType.Unsymbolized; + } + + return ft; +} + /** * get frame type name * @param ft FrameType * @returns string */ export function describeFrameType(ft: FrameType): string { - return frameTypeDescriptions[ft]; + return frameTypeDescriptions[normalizeFrameType(ft)]; } export interface StackTraceEvent { @@ -214,7 +242,9 @@ function getExeFileName(metadata: StackFrameMetadata) { */ export function getCalleeLabel(metadata: StackFrameMetadata) { const inlineLabel = metadata.Inline ? '-> ' : ''; - if (metadata.FunctionName !== '') { + if (metadata.FrameType === FrameType.Error) { + return `Error: unwinding error code #${metadata.AddressOrLine.toString()}`; + } else if (metadata.FunctionName !== '') { const sourceFilename = metadata.SourceFilename; const sourceURL = sourceFilename ? sourceFilename.split('/').pop() : ''; return `${inlineLabel}${getExeFileName(metadata)}: ${getFunctionName( @@ -298,6 +328,10 @@ export function getLanguageType(param: LanguageTypeParams) { * @returns string */ export function getCalleeSource(frame: StackFrameMetadata): string { + if (frame.FrameType === FrameType.Error) { + return `unwinding error code #${frame.AddressOrLine.toString()}`; + } + const frameSymbolStatus = getFrameSymbolStatus({ sourceFilename: frame.SourceFilename, sourceLine: frame.SourceLine, diff --git a/packages/kbn-profiling-utils/common/stack_traces.test.ts b/packages/kbn-profiling-utils/common/stack_traces.test.ts index 9f7076f133d01..29ed301aa8a3e 100644 --- a/packages/kbn-profiling-utils/common/stack_traces.test.ts +++ b/packages/kbn-profiling-utils/common/stack_traces.test.ts @@ -29,7 +29,7 @@ describe('Stack trace response operations', () => { samplingRate: 1.0, }; - const decoded = decodeStackTraceResponse(original); + const decoded = decodeStackTraceResponse(original, false); expect(decoded.executables.size).toEqual(expected.executables.size); expect(decoded.executables.size).toEqual(0); @@ -141,7 +141,7 @@ describe('Stack trace response operations', () => { samplingRate: 1.0, }; - const decoded = decodeStackTraceResponse(original); + const decoded = decodeStackTraceResponse(original, false); expect(decoded.executables.size).toEqual(expected.executables.size); expect(decoded.executables.size).toEqual(2); @@ -223,7 +223,7 @@ describe('Stack trace response operations', () => { samplingRate: 1.0, }; - const decoded = decodeStackTraceResponse(original); + const decoded = decodeStackTraceResponse(original, false); expect(decoded.executables.size).toEqual(expected.executables.size); expect(decoded.executables.size).toEqual(1); diff --git a/packages/kbn-profiling-utils/common/stack_traces.ts b/packages/kbn-profiling-utils/common/stack_traces.ts index be8ffe8a32f8c..a8c8b9c877c4c 100644 --- a/packages/kbn-profiling-utils/common/stack_traces.ts +++ b/packages/kbn-profiling-utils/common/stack_traces.ts @@ -10,6 +10,7 @@ import { ProfilingESField } from './elasticsearch'; import { Executable, FileID, + isErrorFrame, StackFrame, StackFrameID, StackTrace, @@ -111,7 +112,8 @@ export const makeFrameID = (frameID: string, n: number): string => { // createInlineTrace builds a new StackTrace with inline frames. const createInlineTrace = ( trace: ProfilingStackTrace, - frames: Map + frames: Map, + showErrorFrames: boolean ): StackTrace => { // The arrays need to be extended with the inline frame information. const frameIDs: string[] = []; @@ -119,7 +121,11 @@ const createInlineTrace = ( const addressOrLines: number[] = []; const typeIDs: number[] = []; - for (let i = 0; i < trace.frame_ids.length; i++) { + // Error frames only appear as first frame in a stacktrace. + const start = + !showErrorFrames && trace.frame_ids.length > 0 && isErrorFrame(trace.type_ids[0]) ? 1 : 0; + + for (let i = start; i < trace.frame_ids.length; i++) { const frameID = trace.frame_ids[i]; frameIDs.push(frameID); fileIDs.push(trace.file_ids[i]); @@ -153,9 +159,13 @@ const createInlineTrace = ( /** * Decodes stack trace response * @param response StackTraceResponse + * @param showErrorFrames * @returns DecodedStackTraceResponse */ -export function decodeStackTraceResponse(response: StackTraceResponse): DecodedStackTraceResponse { +export function decodeStackTraceResponse( + response: StackTraceResponse, + showErrorFrames: boolean +): DecodedStackTraceResponse { const stackTraceEvents: Map = new Map(); for (const [key, value] of Object.entries(response.stack_trace_events ?? {})) { stackTraceEvents.set(key, value); @@ -181,7 +191,7 @@ export function decodeStackTraceResponse(response: StackTraceResponse): DecodedS const stackTraces: Map = new Map(); for (const [traceID, trace] of Object.entries(response.stack_traces ?? {})) { - stackTraces.set(traceID, createInlineTrace(trace, stackFrames)); + stackTraces.set(traceID, createInlineTrace(trace, stackFrames, showErrorFrames)); } const executables: Map = new Map(); diff --git a/packages/kbn-profiling-utils/index.ts b/packages/kbn-profiling-utils/index.ts index 502253b1aef10..a2c582009c785 100644 --- a/packages/kbn-profiling-utils/index.ts +++ b/packages/kbn-profiling-utils/index.ts @@ -12,6 +12,7 @@ export { ProfilingESField } from './common/elasticsearch'; export { groupStackFrameMetadataByStackTrace, describeFrameType, + normalizeFrameType, FrameType, getCalleeFunction, getCalleeSource, diff --git a/src/plugins/kibana_usage_collection/server/collectors/management/schema.ts b/src/plugins/kibana_usage_collection/server/collectors/management/schema.ts index 1269a68e41c17..b742808749aac 100644 --- a/src/plugins/kibana_usage_collection/server/collectors/management/schema.ts +++ b/src/plugins/kibana_usage_collection/server/collectors/management/schema.ts @@ -568,6 +568,10 @@ export const stackManagementSchema: MakeSchemaFrom = { type: 'boolean', _meta: { description: 'Non-default value of setting.' }, }, + 'observability:profilingShowErrorFrames': { + type: 'boolean', + _meta: { description: 'Non-default value of setting.' }, + }, 'observability:profilingPerVCPUWattX86': { type: 'integer', _meta: { description: 'Non-default value of setting.' }, diff --git a/src/plugins/kibana_usage_collection/server/collectors/management/types.ts b/src/plugins/kibana_usage_collection/server/collectors/management/types.ts index 177a01b81af19..347d71d2794cc 100644 --- a/src/plugins/kibana_usage_collection/server/collectors/management/types.ts +++ b/src/plugins/kibana_usage_collection/server/collectors/management/types.ts @@ -156,6 +156,7 @@ export interface UsageStats { 'observability:apmTraceExplorerTab': boolean; 'observability:apmEnableCriticalPath': boolean; 'observability:apmEnableProfilingIntegration': boolean; + 'observability:profilingShowErrorFrames': boolean; 'securitySolution:enableGroupedNav': boolean; 'securitySolution:showRelatedIntegrations': boolean; 'visualization:visualize:legacyGaugeChartsLibrary': boolean; diff --git a/src/plugins/telemetry/schema/oss_plugins.json b/src/plugins/telemetry/schema/oss_plugins.json index b5108f3d9269d..9f26915a2b9e9 100644 --- a/src/plugins/telemetry/schema/oss_plugins.json +++ b/src/plugins/telemetry/schema/oss_plugins.json @@ -10168,6 +10168,12 @@ "description": "Non-default value of setting." } }, + "observability:profilingShowErrorFrames": { + "type": "boolean", + "_meta": { + "description": "Non-default value of setting." + } + }, "observability:profilingPerVCPUWattX86": { "type": "integer", "_meta": { diff --git a/x-pack/plugins/observability/common/index.ts b/x-pack/plugins/observability/common/index.ts index f9467b8945060..4d2f8c560261c 100644 --- a/x-pack/plugins/observability/common/index.ts +++ b/x-pack/plugins/observability/common/index.ts @@ -44,6 +44,7 @@ export { enableCriticalPath, syntheticsThrottlingEnabled, apmEnableProfilingIntegration, + profilingShowErrorFrames, profilingCo2PerKWH, profilingDatacenterPUE, profilingPervCPUWattX86, diff --git a/x-pack/plugins/observability/common/ui_settings_keys.ts b/x-pack/plugins/observability/common/ui_settings_keys.ts index 2d2a76c6e36b0..366aa3ce860b6 100644 --- a/x-pack/plugins/observability/common/ui_settings_keys.ts +++ b/x-pack/plugins/observability/common/ui_settings_keys.ts @@ -32,6 +32,7 @@ export const apmEnableContinuousRollups = 'observability:apmEnableContinuousRoll export const syntheticsThrottlingEnabled = 'observability:syntheticsThrottlingEnabled'; export const enableLegacyUptimeApp = 'observability:enableLegacyUptimeApp'; export const apmEnableProfilingIntegration = 'observability:apmEnableProfilingIntegration'; +export const profilingShowErrorFrames = 'observability:profilingShowErrorFrames'; export const profilingPervCPUWattX86 = 'observability:profilingPerVCPUWattX86'; export const profilingPervCPUWattArm64 = 'observability:profilingPervCPUWattArm64'; export const profilingCo2PerKWH = 'observability:profilingCo2PerKWH'; diff --git a/x-pack/plugins/observability/server/ui_settings.ts b/x-pack/plugins/observability/server/ui_settings.ts index 0fdf57c97f956..02717b59f2f4e 100644 --- a/x-pack/plugins/observability/server/ui_settings.ts +++ b/x-pack/plugins/observability/server/ui_settings.ts @@ -31,6 +31,7 @@ import { syntheticsThrottlingEnabled, enableLegacyUptimeApp, apmEnableProfilingIntegration, + profilingShowErrorFrames, profilingCo2PerKWH, profilingDatacenterPUE, profilingPervCPUWattX86, @@ -432,6 +433,15 @@ export const uiSettings: Record = { schema: schema.boolean(), requiresPageReload: false, }, + [profilingShowErrorFrames]: { + category: [observabilityFeatureId], + name: i18n.translate('xpack.observability.profilingShowErrorFramesSettingName', { + defaultMessage: 'Show error frames in the Universal Profiling views', + }), + value: false, + schema: schema.boolean(), + requiresPageReload: true, + }, [profilingPervCPUWattX86]: { category: [observabilityFeatureId], name: i18n.translate('xpack.observability.profilingPervCPUWattX86UiSettingName', { diff --git a/x-pack/plugins/profiling/common/columnar_view_model.test.ts b/x-pack/plugins/profiling/common/columnar_view_model.test.ts index 484e035de7164..c00f8cb20dcd0 100644 --- a/x-pack/plugins/profiling/common/columnar_view_model.test.ts +++ b/x-pack/plugins/profiling/common/columnar_view_model.test.ts @@ -11,7 +11,7 @@ import { createColumnarViewModel } from './columnar_view_model'; import { baseFlamegraph } from './__fixtures__/base_flamegraph'; describe('Columnar view model operations', () => { - const graph = createFlameGraph(baseFlamegraph); + const graph = createFlameGraph(baseFlamegraph, false); describe('color values are generated by default', () => { const viewModel = createColumnarViewModel(graph); diff --git a/x-pack/plugins/profiling/common/frame_type_colors.ts b/x-pack/plugins/profiling/common/frame_type_colors.ts index 81571c459bed4..a2827f307feb1 100644 --- a/x-pack/plugins/profiling/common/frame_type_colors.ts +++ b/x-pack/plugins/profiling/common/frame_type_colors.ts @@ -5,22 +5,23 @@ * 2.0. */ -import { FrameType } from '@kbn/profiling-utils'; +import { FrameType, normalizeFrameType } from '@kbn/profiling-utils'; /* * Helper to calculate the color of a given block to be drawn. The desirable outcomes of this are: * Each of the following frame types should get a different set of color hues: * - * 0 = Unsymbolized frame - * 1 = Python - * 2 = PHP - * 3 = Native - * 4 = Kernel - * 5 = JVM/Hotspot - * 6 = Ruby - * 7 = Perl - * 8 = JavaScript - * 9 = PHP JIT + * 0x00 = Unsymbolized frame + * 0x01 = Python + * 0x02 = PHP + * 0x03 = Native + * 0x04 = Kernel + * 0x05 = JVM/Hotspot + * 0x06 = Ruby + * 0x07 = Perl + * 0x08 = JavaScript + * 0x09 = PHP JIT + * 0xFF = Error frame * * This is most easily achieved by mapping frame types to different color variations, using * the x-position we can use different colors for adjacent blocks while keeping a similar hue @@ -38,10 +39,12 @@ export const FRAME_TYPE_COLOR_MAP = { [FrameType.Perl]: [0xf98bb9, 0xfaa2c7, 0xfbb9d5, 0xfdd1e3], [FrameType.JavaScript]: [0xcbc3e3, 0xd5cfe8, 0xdfdbee, 0xeae7f3], [FrameType.PHPJIT]: [0xccfc82, 0xd1fc8e, 0xd6fc9b, 0xdbfca7], + [FrameType.ErrorFlag]: [0x0, 0x0, 0x0, 0x0], // This is a special case, it's not a real frame type + [FrameType.Error]: [0xfd8484, 0xfd9d9d, 0xfeb5b5, 0xfecece], }; export function frameTypeToRGB(frameType: FrameType, x: number): number { - return FRAME_TYPE_COLOR_MAP[frameType][x % 4]; + return FRAME_TYPE_COLOR_MAP[normalizeFrameType(frameType)][x % 4]; } export function rgbToRGBA(rgb: number): number[] { diff --git a/x-pack/plugins/profiling/e2e/cypress/e2e/profiling_views/settings.cy.ts b/x-pack/plugins/profiling/e2e/cypress/e2e/profiling_views/settings.cy.ts index 7b347399de3dd..40927c5e38ae0 100644 --- a/x-pack/plugins/profiling/e2e/cypress/e2e/profiling_views/settings.cy.ts +++ b/x-pack/plugins/profiling/e2e/cypress/e2e/profiling_views/settings.cy.ts @@ -39,6 +39,7 @@ describe('Settings page', () => { cy.contains('Per vCPU Watts - arm64'); cy.contains('AWS EDP discount rate (%)'); cy.contains('Cost per vCPU per hour ($)'); + cy.contains('Show error frames in the Universal Profiling views'); }); it('updates values', () => { diff --git a/x-pack/plugins/profiling/public/embeddables/flamegraph/embeddable_flamegraph.tsx b/x-pack/plugins/profiling/public/embeddables/flamegraph/embeddable_flamegraph.tsx index b88881ea04cba..b7266049b57c4 100644 --- a/x-pack/plugins/profiling/public/embeddables/flamegraph/embeddable_flamegraph.tsx +++ b/x-pack/plugins/profiling/public/embeddables/flamegraph/embeddable_flamegraph.tsx @@ -9,6 +9,7 @@ import { EMBEDDABLE_FLAMEGRAPH } from '@kbn/observability-shared-plugin/public'; import { createFlameGraph } from '@kbn/profiling-utils'; import React from 'react'; import { render, unmountComponentAtNode } from 'react-dom'; +import { profilingShowErrorFrames } from '@kbn/observability-plugin/common'; import { FlameGraph } from '../../components/flamegraph'; import { AsyncEmbeddableComponent } from '../async_embeddable_component'; import { @@ -16,6 +17,7 @@ import { ProfilingEmbeddablesDependencies, } from '../profiling_embeddable_provider'; import { EmbeddableFlamegraphEmbeddableInput } from './embeddable_flamegraph_factory'; +import { useProfilingDependencies } from '../../components/contexts/profiling_dependencies/use_profiling_dependencies'; export class EmbeddableFlamegraph extends Embeddable< EmbeddableFlamegraphEmbeddableInput, @@ -34,18 +36,9 @@ export class EmbeddableFlamegraph extends Embeddable< render(domNode: HTMLElement) { this._domNode = domNode; - const { data, isLoading } = this.input; - const flamegraph = !isLoading && data ? createFlameGraph(data) : undefined; - render( - - <> - {flamegraph && ( - - )} - - + , domNode ); @@ -63,3 +56,18 @@ export class EmbeddableFlamegraph extends Embeddable< } } } + +function Flamegraph({ isLoading, data }: EmbeddableFlamegraphEmbeddableInput) { + const { core } = useProfilingDependencies().start; + const showErrorFrames = core.uiSettings.get(profilingShowErrorFrames); + const flamegraph = !isLoading && data ? createFlameGraph(data, showErrorFrames) : undefined; + return ( + + <> + {flamegraph && ( + + )} + + + ); +} diff --git a/x-pack/plugins/profiling/public/services.ts b/x-pack/plugins/profiling/public/services.ts index 7f16747f596c2..3a8aceed514c5 100644 --- a/x-pack/plugins/profiling/public/services.ts +++ b/x-pack/plugins/profiling/public/services.ts @@ -4,6 +4,7 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ + import { HttpFetchQuery } from '@kbn/core/public'; import { createFlameGraph, @@ -51,6 +52,7 @@ export interface Services { timeFrom: number; timeTo: number; kuery: string; + showErrorFrames: boolean; }) => Promise; fetchHasSetup: (params: { http: AutoAbortedHttpService }) => Promise; postSetupResources: (params: { http: AutoAbortedHttpService }) => Promise; @@ -101,7 +103,7 @@ export function getServices(): Services { return (await http.get(paths.TopNFunctions, { query })) as Promise; }, - fetchElasticFlamechart: async ({ http, timeFrom, timeTo, kuery }) => { + fetchElasticFlamechart: async ({ http, timeFrom, timeTo, kuery, showErrorFrames }) => { const query: HttpFetchQuery = { timeFrom, timeTo, @@ -109,7 +111,7 @@ export function getServices(): Services { }; const baseFlamegraph = (await http.get(paths.Flamechart, { query })) as BaseFlameGraph; - return createFlameGraph(baseFlamegraph); + return createFlameGraph(baseFlamegraph, showErrorFrames); }, fetchHasSetup: async ({ http }) => { const hasSetup = (await http.get(paths.HasSetupESResources, {})) as ProfilingSetupStatus; diff --git a/x-pack/plugins/profiling/public/views/flamegraphs/differential_flamegraphs/index.tsx b/x-pack/plugins/profiling/public/views/flamegraphs/differential_flamegraphs/index.tsx index a956a6d715fbb..4c87cfdab5f28 100644 --- a/x-pack/plugins/profiling/public/views/flamegraphs/differential_flamegraphs/index.tsx +++ b/x-pack/plugins/profiling/public/views/flamegraphs/differential_flamegraphs/index.tsx @@ -6,6 +6,7 @@ */ import { EuiFlexGroup, EuiFlexItem, EuiPanel, EuiSpacer } from '@elastic/eui'; import React from 'react'; +import { profilingShowErrorFrames } from '@kbn/observability-plugin/common'; import { AsyncComponent } from '../../../components/async_component'; import { useProfilingDependencies } from '../../../components/contexts/profiling_dependencies/use_profiling_dependencies'; import { FlameGraph } from '../../../components/flamegraph'; @@ -49,8 +50,11 @@ export function DifferentialFlameGraphsView() { const { services: { fetchElasticFlamechart }, + start: { core }, } = useProfilingDependencies(); + const showErrorFrames = core.uiSettings.get(profilingShowErrorFrames); + const state = useTimeRangeAsync( ({ http }) => { return Promise.all([ @@ -59,6 +63,7 @@ export function DifferentialFlameGraphsView() { timeFrom: new Date(timeRange.start).getTime(), timeTo: new Date(timeRange.end).getTime(), kuery, + showErrorFrames, }), comparisonTimeRange.start && comparisonTimeRange.end ? fetchElasticFlamechart({ @@ -66,6 +71,7 @@ export function DifferentialFlameGraphsView() { timeFrom: new Date(comparisonTimeRange.start).getTime(), timeTo: new Date(comparisonTimeRange.end).getTime(), kuery: comparisonKuery, + showErrorFrames, }) : Promise.resolve(undefined), ]).then(([primaryFlamegraph, comparisonFlamegraph]) => { @@ -83,6 +89,7 @@ export function DifferentialFlameGraphsView() { comparisonTimeRange.start, comparisonTimeRange.end, comparisonKuery, + showErrorFrames, ] ); diff --git a/x-pack/plugins/profiling/public/views/flamegraphs/flamegraph/index.tsx b/x-pack/plugins/profiling/public/views/flamegraphs/flamegraph/index.tsx index fae9504b00b01..70fffab585174 100644 --- a/x-pack/plugins/profiling/public/views/flamegraphs/flamegraph/index.tsx +++ b/x-pack/plugins/profiling/public/views/flamegraphs/flamegraph/index.tsx @@ -6,6 +6,7 @@ */ import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; import React from 'react'; +import { profilingShowErrorFrames } from '@kbn/observability-plugin/common'; import { AsyncComponent } from '../../../components/async_component'; import { useProfilingDependencies } from '../../../components/contexts/profiling_dependencies/use_profiling_dependencies'; import { FlameGraph } from '../../../components/flamegraph'; @@ -25,8 +26,11 @@ export function FlameGraphView() { const { services: { fetchElasticFlamechart }, + start: { core }, } = useProfilingDependencies(); + const showErrorFrames = core.uiSettings.get(profilingShowErrorFrames); + const state = useTimeRangeAsync( ({ http }) => { return fetchElasticFlamechart({ @@ -34,9 +38,10 @@ export function FlameGraphView() { timeFrom: new Date(timeRange.start).getTime(), timeTo: new Date(timeRange.end).getTime(), kuery, + showErrorFrames, }); }, - [fetchElasticFlamechart, timeRange.start, timeRange.end, kuery] + [fetchElasticFlamechart, timeRange.start, timeRange.end, kuery, showErrorFrames] ); const { data } = state; diff --git a/x-pack/plugins/profiling/public/views/settings/index.tsx b/x-pack/plugins/profiling/public/views/settings/index.tsx index 400a312e6f6be..7b86ec607d141 100644 --- a/x-pack/plugins/profiling/public/views/settings/index.tsx +++ b/x-pack/plugins/profiling/public/views/settings/index.tsx @@ -25,6 +25,7 @@ import { profilingPervCPUWattArm64, profilingAWSCostDiscountRate, profilingCostPervCPUPerHour, + profilingShowErrorFrames, } from '@kbn/observability-plugin/common'; import { useEditableSettings, useUiTracker } from '@kbn/observability-shared-plugin/public'; import { isEmpty } from 'lodash'; @@ -47,6 +48,7 @@ const co2Settings = [ profilingPervCPUWattArm64, ]; const costSettings = [profilingAWSCostDiscountRate, profilingCostPervCPUPerHour]; +const miscSettings = [profilingShowErrorFrames]; export function Settings() { const trackProfilingEvent = useUiTracker({ app: 'profiling' }); @@ -57,7 +59,7 @@ export function Settings() { } = useProfilingDependencies(); const { fields, handleFieldChange, unsavedChanges, saveAll, isSaving, cleanUnsavedChanges } = - useEditableSettings('profiling', [...co2Settings, ...costSettings]); + useEditableSettings('profiling', [...co2Settings, ...costSettings, ...miscSettings]); async function handleSave() { try { @@ -163,6 +165,20 @@ export function Settings() { }, settings: costSettings, }, + { + label: i18n.translate('xpack.profiling.settings.miscSection', { + defaultMessage: 'Miscellaneous settings', + }), + description: { + title: ( + + ), + }, + settings: miscSettings, + }, ].map((item) => ( <> diff --git a/x-pack/plugins/profiling/server/routes/search_stacktraces.ts b/x-pack/plugins/profiling/server/routes/search_stacktraces.ts index e93f71e84da08..a8f994842bc4f 100644 --- a/x-pack/plugins/profiling/server/routes/search_stacktraces.ts +++ b/x-pack/plugins/profiling/server/routes/search_stacktraces.ts @@ -13,11 +13,13 @@ export async function searchStackTraces({ filter, sampleSize, durationSeconds, + showErrorFrames, }: { client: ProfilingESClient; filter: ProjectTimeQuery; sampleSize: number; durationSeconds: number; + showErrorFrames: boolean; }) { const response = await client.profilingStacktraces({ query: filter, @@ -25,5 +27,5 @@ export async function searchStackTraces({ durationSeconds, }); - return decodeStackTraceResponse(response); + return decodeStackTraceResponse(response, showErrorFrames); } diff --git a/x-pack/plugins/profiling/server/routes/topn.test.ts b/x-pack/plugins/profiling/server/routes/topn.test.ts index fcc8a77a373bc..2985d8d9d78f7 100644 --- a/x-pack/plugins/profiling/server/routes/topn.test.ts +++ b/x-pack/plugins/profiling/server/routes/topn.test.ts @@ -86,6 +86,7 @@ describe('TopN data from Elasticsearch', () => { searchField: ProfilingESField.StacktraceID, highCardinality: false, kuery: '', + showErrorFrames: false, }); expect(client.search).toHaveBeenCalledTimes(2); diff --git a/x-pack/plugins/profiling/server/routes/topn.ts b/x-pack/plugins/profiling/server/routes/topn.ts index 2cca956c8a775..944245a9d15cc 100644 --- a/x-pack/plugins/profiling/server/routes/topn.ts +++ b/x-pack/plugins/profiling/server/routes/topn.ts @@ -13,6 +13,7 @@ import { ProfilingESField, TopNType, } from '@kbn/profiling-utils'; +import { profilingShowErrorFrames } from '@kbn/observability-plugin/common'; import { IDLE_SOCKET_TIMEOUT, RouteRegisterParameters } from '.'; import { getRoutePaths, INDEX_EVENTS } from '../../common'; import { computeBucketWidthFromTimeRangeAndBucketCount } from '../../common/histogram'; @@ -33,6 +34,7 @@ export async function topNElasticSearchQuery({ searchField, highCardinality, kuery, + showErrorFrames, }: { client: ProfilingESClient; logger: Logger; @@ -41,6 +43,7 @@ export async function topNElasticSearchQuery({ searchField: string; highCardinality: boolean; kuery: string; + showErrorFrames: boolean; }): Promise { const filter = createCommonFilter({ timeFrom, timeTo, kuery }); const targetSampleSize = 20000; // minimum number of samples to get statistically sound results @@ -136,6 +139,7 @@ export async function topNElasticSearchQuery({ filter: stackTraceFilter, sampleSize: targetSampleSize, durationSeconds: totalSeconds, + showErrorFrames, }); } ); @@ -178,7 +182,9 @@ export function queryTopNCommon({ }, async (context, request, response) => { const { timeFrom, timeTo, kuery } = request.query; - const client = await getClient(context); + const [client, core] = await Promise.all([getClient(context), context.core]); + + const showErrorFrames = await core.uiSettings.client.get(profilingShowErrorFrames); try { return response.ok({ @@ -190,6 +196,7 @@ export function queryTopNCommon({ searchField, highCardinality, kuery, + showErrorFrames, }), }); } catch (error) { diff --git a/x-pack/plugins/profiling_data_access/server/services/functions/index.ts b/x-pack/plugins/profiling_data_access/server/services/functions/index.ts index 8492562d9c775..9a2c7121199a9 100644 --- a/x-pack/plugins/profiling_data_access/server/services/functions/index.ts +++ b/x-pack/plugins/profiling_data_access/server/services/functions/index.ts @@ -11,6 +11,7 @@ import { profilingDatacenterPUE, profilingPervCPUWattArm64, profilingPervCPUWattX86, + profilingShowErrorFrames, } from '@kbn/observability-plugin/common'; import { CoreRequestHandlerContext, ElasticsearchClient } from '@kbn/core/server'; import { createTopNFunctions } from '@kbn/profiling-utils'; @@ -52,6 +53,7 @@ export function createFetchFunctions({ createProfilingEsClient }: RegisterServic pervCPUWattArm64, awsCostDiscountRate, costPervCPUPerHour, + showErrorFrames, ] = await Promise.all([ core.uiSettings.client.get(profilingCo2PerKWH), core.uiSettings.client.get(profilingDatacenterPUE), @@ -59,6 +61,7 @@ export function createFetchFunctions({ createProfilingEsClient }: RegisterServic core.uiSettings.client.get(profilingPervCPUWattArm64), core.uiSettings.client.get(profilingAWSCostDiscountRate), core.uiSettings.client.get(profilingCostPervCPUPerHour), + core.uiSettings.client.get(profilingShowErrorFrames), ]); const profilingEsClient = createProfilingEsClient({ esClient }); @@ -77,6 +80,7 @@ export function createFetchFunctions({ createProfilingEsClient }: RegisterServic pervCPUWattArm64, awsCostDiscountRate: percentToFactor(awsCostDiscountRate), costPervCPUPerHour, + showErrorFrames, } ); @@ -89,6 +93,7 @@ export function createFetchFunctions({ createProfilingEsClient }: RegisterServic stackFrames, stackTraces, startIndex, + showErrorFrames, }); }); diff --git a/x-pack/plugins/profiling_data_access/server/services/search_stack_traces/index.ts b/x-pack/plugins/profiling_data_access/server/services/search_stack_traces/index.ts index 6da9187d7a2ed..9881399482ce1 100644 --- a/x-pack/plugins/profiling_data_access/server/services/search_stack_traces/index.ts +++ b/x-pack/plugins/profiling_data_access/server/services/search_stack_traces/index.ts @@ -22,6 +22,7 @@ export async function searchStackTraces({ pervCPUWattArm64, awsCostDiscountRate, costPervCPUPerHour, + showErrorFrames, }: { client: ProfilingESClient; sampleSize: number; @@ -35,6 +36,7 @@ export async function searchStackTraces({ pervCPUWattArm64: number; awsCostDiscountRate: number; costPervCPUPerHour: number; + showErrorFrames: boolean; }) { const response = await client.profilingStacktraces({ query: { @@ -64,5 +66,5 @@ export async function searchStackTraces({ costPervCPUPerHour, }); - return decodeStackTraceResponse(response); + return decodeStackTraceResponse(response, showErrorFrames); } From f4f71d11307276f8eafcfbb8b3f872f60d03853b Mon Sep 17 00:00:00 2001 From: Yara Tercero Date: Mon, 12 Feb 2024 23:42:08 -0800 Subject: [PATCH 44/83] [Detections Response] Finish moving remaining legacy FTRs (#175837) **Resolves: https://github.com/elastic/kibana/issues/151902** ## Summary After this PR, all D&R FTRs are moved to new folder where they can be run in ESS and serverless. Please see below table for a summary of what tests need revisiting by the teams. During the test migration there may have been some tests that failed on serverless, but not ESS. Some we were able to fix and get running on both, others are still marked as `brokenInServerless` and need triage. --- .buildkite/ftr_configs.yml | 12 +- .../security_solution/api_integration.yml | 254 +++++++++++++----- .github/CODEOWNERS | 2 +- .../common/experimental_features.ts | 2 +- .../common/lib/alerts.ts | 18 +- .../tests/common/cases/delete_cases.ts | 6 +- .../tests/common/cases/patch_cases.ts | 46 ++-- .../tests/common/comments/delete_comment.ts | 6 +- .../tests/common/comments/delete_comments.ts | 6 +- .../tests/common/comments/post_comment.ts | 6 +- .../internal/bulk_create_attachments.ts | 6 +- .../alerts/create_alerts_index.ts | 0 .../alerts/delete_all_alerts.ts | 0 .../alerts/get_alerts_by_id.ts | 0 .../alerts/get_alerts_by_ids.ts | 0 .../alerts/get_query_alert_ids.ts | 0 .../alerts/get_query_alerts_ids.ts | 0 .../detections_response/alerts/index.ts | 14 + .../alerts/wait_for_alerts_to_be_present.ts | 0 .../detections_response}/count_down_test.ts | 0 .../detections_response/index.ts} | 10 +- .../route_with_namespace.ts | 0 .../detections_response}/rules/create_rule.ts | 0 .../rules/delete_all_rules.ts | 0 .../detections_response/rules}/delete_rule.ts | 0 .../rules/get_rule_for_alert_testing.ts | 0 .../detections_response/rules/index.ts} | 14 +- .../rules/wait_for_rule_status.ts | 0 .../detections_response}/wait_for.ts | 0 .../utils/security_solution/index.ts} | 2 +- .../basic/tests/index.ts | 26 -- .../common/config.ts | 105 -------- .../utils/binary_to_string.ts | 22 -- .../utils/count_down_es.ts | 46 ---- .../utils/create_rule.ts | 74 ----- .../utils/create_rule_saved_object.ts | 35 --- .../utils/create_signals_index.ts | 37 --- .../utils/delete_all_alerts.ts | 49 ---- .../utils/delete_all_rules.ts | 47 ---- .../utils/get_complex_rule.ts | 97 ------- .../utils/get_complex_rule_output.ts | 105 -------- .../utils/get_query_signals_ids.ts | 23 -- .../utils/get_rule_for_signal_testing.ts | 32 --- .../utils/get_signals_by_ids.ts | 59 ---- .../utils/get_simple_ml_rule_update.ts | 25 -- .../utils/get_simple_rule.ts | 25 -- .../utils/get_simple_rule_as_ndjson.ts | 21 -- .../utils/get_simple_rule_output.ts | 85 ------ .../get_simple_rule_output_without_rule_id.ts | 21 -- .../utils/get_simple_rule_update.ts | 25 -- .../utils/get_simple_rule_without_rule_id.ts | 19 -- .../utils/index.ts | 41 --- .../create_prebuilt_rule_saved_objects.ts | 113 -------- .../install_prebuilt_rules_and_timelines.ts | 56 ---- .../remove_server_generated_properties.ts | 30 --- ..._generated_properties_including_rule_id.ts | 23 -- .../utils/rule_to_ndjson.ts | 18 -- .../utils/rule_to_update_schema.ts | 37 --- .../utils/update_rule.ts | 41 --- .../utils/wait_for_rule_status.ts | 82 ------ .../utils/wait_for_signals_to_be_present.ts | 41 --- .../es_archives/rule_keyword_family/README.md | 2 +- .../es_archives/security_solution/README.md | 4 +- .../package.json | 30 +++ .../add_actions.ts | 6 +- .../check_privileges.ts | 9 +- .../trial_license_complete_tier/throttle.ts | 10 +- .../update_actions.ts | 8 +- .../open_close_alerts.ts | 8 +- .../query_alerts.ts | 6 +- .../query_alerts_backword_compatibility.ts | 5 +- .../alerts_compatibility.ts | 18 +- .../trial_license_complete_tier/aliases.ts | 2 +- .../assignments/assignments.ts | 4 +- .../assignments/assignments_ess.ts | 4 +- .../assignments/assignments_serverless.ts | 4 +- .../create_index.ts | 2 +- .../migrations/create_alerts_migrations.ts | 6 +- .../migrations/delete_alerts_migrations.ts | 4 +- .../migrations/finalize_alerts_migrations.ts | 5 +- .../migrations/get_alerts_migration_status.ts | 6 +- .../open_close_alerts.ts | 6 +- .../set_alert_tags.ts | 4 +- .../trial_license_complete_tier/date.ts | 4 +- .../trial_license_complete_tier/double.ts | 4 +- .../trial_license_complete_tier/float.ts | 4 +- .../trial_license_complete_tier/integer.ts | 4 +- .../ips/trial_license_complete_tier/ip.ts | 4 +- .../trial_license_complete_tier/ip_array.ts | 4 +- .../trial_license_complete_tier/keyword.ts | 4 +- .../keyword_array.ts | 4 +- .../long/trial_license_complete_tier/long.ts | 4 +- .../text/trial_license_complete_tier/text.ts | 4 +- .../trial_license_complete_tier/text_array.ts | 4 +- .../create_endpoint_exceptions.ts | 4 +- .../create_rule_exceptions.ts | 10 +- .../find_rule_exception_references.ts | 5 +- .../role_based_rule_exceptions_workflows.ts | 17 +- .../rule_exception_synchronizations.ts | 11 +- .../execution_logic/eql.ts | 8 +- .../execution_logic/esql.ts | 8 +- .../execution_logic/machine_learning.ts | 8 +- .../execution_logic/new_terms.ts | 8 +- .../execution_logic/non_ecs_fields.ts | 8 +- .../execution_logic/query.ts | 10 +- .../execution_logic/saved_query.ts | 4 +- .../execution_logic/threat_match.ts | 6 +- .../threat_match_alert_suppression.ts | 2 +- .../execution_logic/threshold.ts | 2 +- .../threshold_alert_suppression.ts | 2 +- .../ignore_fields.ts | 4 +- .../keyword_family/const_keyword.ts | 5 +- .../keyword_family/keyword.ts | 5 +- .../keyword_mixed_with_const.ts | 4 +- .../trial_license_complete_tier/runtime.ts | 2 +- .../trial_license_complete_tier/timestamps.ts | 5 +- .../install_latest_bundled_prebuilt_rules.ts | 2 +- .../prerelease_packages.ts | 2 +- .../install_large_prebuilt_rules_package.ts | 2 +- .../fleet_integration.ts | 2 +- .../get_prebuilt_rules_status.ts | 8 +- .../install_prebuilt_rules.ts | 3 +- ...prebuilt_rules_with_historical_versions.ts | 3 +- .../upgrade_prebuilt_rules.ts | 2 +- ...prebuilt_rules_with_historical_versions.ts | 2 +- .../update_prebuilt_rules_package.ts | 2 +- .../perform_bulk_action.ts | 10 +- .../perform_bulk_action_dry_run.ts | 6 +- .../perform_bulk_action_dry_run_ess.ts | 7 +- .../perform_bulk_action_ess.ts | 18 +- .../create_ml_rules_privileges.ts | 6 +- .../create_rules.ts | 48 +++- .../create_rules_bulk.ts | 42 ++- .../basic_license_essentials_tier/index.ts | 1 + .../create_new_terms.ts | 2 +- .../create_rules.ts | 14 +- .../create_rules_bulk.ts | 12 +- .../preview_rules.ts | 3 +- .../configs/ess.config.ts | 22 ++ .../configs/serverless.config.ts | 16 ++ .../delete_rules.ts | 40 ++- .../delete_rules_bulk.ts | 74 +++-- .../basic_license_essentials_tier/index.ts | 15 ++ .../delete_rules.ts | 10 +- .../delete_rules_bulk.ts | 10 +- .../delete_rules_bulk_legacy.ts | 10 +- .../delete_rules_ess.ts | 10 +- .../delete_rules_legacy.ts | 11 +- .../configs/ess.config.ts | 22 ++ .../configs/serverless.config.ts | 16 ++ .../export_rules.ts | 33 +-- .../import_rules.ts | 46 ++-- .../basic_license_essentials_tier/index.ts | 15 ++ .../export_rules.ts | 12 +- .../export_rules_ess.ts | 10 +- .../import_export_rules.ts | 21 +- .../import_rules.ts | 10 +- .../import_rules_ess.ts | 3 +- .../coverage_overview.ts | 3 +- .../get_rule_execution_results.ts | 10 +- .../get_rule_management_filters.ts | 2 +- .../configs/ess.config.ts | 22 ++ .../configs/serverless.config.ts | 16 ++ .../basic_license_essentials_tier/index.ts | 15 ++ .../patch_rules.ts | 41 ++- .../patch_rules_bulk.ts | 55 ++-- .../patch_rules.ts | 10 +- .../patch_rules_bulk.ts | 10 +- .../patch_rules_ess.ts | 10 +- .../configs/ess.config.ts | 22 ++ .../configs/serverless.config.ts | 16 ++ .../find_rules.ts | 20 +- .../basic_license_essentials_tier/index.ts | 15 ++ .../read_rules.ts | 37 ++- .../trial_license_complete_tier/find_rules.ts | 3 +- .../find_rules_ess.ts | 3 +- .../trial_license_complete_tier/read_rules.ts | 10 +- .../read_rules_ess.ts | 10 +- .../resolve_read_rules.ts | 6 +- .../configs/ess.config.ts | 22 ++ .../configs/serverless.config.ts | 16 ++ .../basic_license_essentials_tier/index.ts | 15 ++ .../update_rules.ts | 40 ++- .../update_rules_bulk.ts | 56 ++-- .../configs/serverless.config.ts | 3 +- .../update_rules.ts | 10 +- .../update_rules_bulk.ts | 10 +- .../update_rules_ess.ts | 10 +- .../task_based/all_types.ts | 5 +- .../task_based/detection_rules.ts | 12 +- .../task_based/security_lists.ts | 8 +- .../usage_collector/all_types.ts | 8 +- .../usage_collector/detection_rule_status.ts | 14 +- .../usage_collector/detection_rules.ts | 16 +- .../detection_rules_legacy_action.ts | 16 +- .../utils/alerts/get_open_alerts.ts | 3 +- .../detections_response/utils/alerts/index.ts | 7 - .../alerts/wait_for_alert_to_complete.ts | 2 +- .../utils/count_down_es.ts | 2 +- .../utils/count_down_test.ts | 78 ------ .../wait_for_event_log_execute_complete.ts | 2 +- .../create_container_with_endpoint_entries.ts | 2 +- .../list/create_container_with_entries.ts | 2 +- .../detections_response/utils/index.ts | 3 - .../utils/route_with_namespace.ts | 14 - .../create_rule_with_exception_entries.ts | 2 +- .../utils/rules/delete_rule.ts | 30 --- .../rules/get_eql_rule_for_alert_testing.ts | 2 +- .../get_saved_query_rule_for_alert_testing.ts | 2 +- ...get_threat_match_rule_for_alert_testing.ts | 2 +- .../get_threshold_rule_for_alert_testing.ts | 2 +- .../detections_response/utils/rules/index.ts | 5 - .../utils/wait_for_index_to_populate.ts | 2 +- .../risk_score_calculation.ts | 7 +- .../risk_score_preview.ts | 4 +- .../risk_scoring_task/task_execution.ts | 7 +- .../task_execution_nondefault_spaces.ts | 7 +- .../telemetry_usage.ts | 7 +- .../utils/asset_criticality.ts | 2 +- .../entity_analytics/utils/risk_engine.ts | 2 +- .../lists_and_exception_lists/utils.ts | 2 +- .../tests/basic}/import_timelines.ts | 4 +- .../basic}/install_prepackaged_timelines.ts | 16 +- .../utils/delete_all_timelines.ts | 0 .../config.ts => timeline/utils/index.ts} | 12 +- .../utils/wait_for.ts | 0 226 files changed, 1349 insertions(+), 2282 deletions(-) rename x-pack/test/{security_solution_api_integration/test_suites/detections_response/utils => common/utils/security_solution/detections_response}/alerts/create_alerts_index.ts (100%) rename x-pack/test/{security_solution_api_integration/test_suites/detections_response/utils => common/utils/security_solution/detections_response}/alerts/delete_all_alerts.ts (100%) rename x-pack/test/{security_solution_api_integration/test_suites/detections_response/utils => common/utils/security_solution/detections_response}/alerts/get_alerts_by_id.ts (100%) rename x-pack/test/{security_solution_api_integration/test_suites/detections_response/utils => common/utils/security_solution/detections_response}/alerts/get_alerts_by_ids.ts (100%) rename x-pack/test/{security_solution_api_integration/test_suites/detections_response/utils => common/utils/security_solution/detections_response}/alerts/get_query_alert_ids.ts (100%) rename x-pack/test/{security_solution_api_integration/test_suites/detections_response/utils => common/utils/security_solution/detections_response}/alerts/get_query_alerts_ids.ts (100%) create mode 100644 x-pack/test/common/utils/security_solution/detections_response/alerts/index.ts rename x-pack/test/{security_solution_api_integration/test_suites/detections_response/utils => common/utils/security_solution/detections_response}/alerts/wait_for_alerts_to_be_present.ts (100%) rename x-pack/test/{detection_engine_api_integration/utils => common/utils/security_solution/detections_response}/count_down_test.ts (100%) rename x-pack/test/{detection_engine_api_integration/common/ftr_provider_context.d.ts => common/utils/security_solution/detections_response/index.ts} (58%) rename x-pack/test/{detection_engine_api_integration/utils => common/utils/security_solution/detections_response}/route_with_namespace.ts (100%) rename x-pack/test/{security_solution_api_integration/test_suites/detections_response/utils => common/utils/security_solution/detections_response}/rules/create_rule.ts (100%) rename x-pack/test/{security_solution_api_integration/test_suites/detections_response/utils => common/utils/security_solution/detections_response}/rules/delete_all_rules.ts (100%) rename x-pack/test/{detection_engine_api_integration/utils => common/utils/security_solution/detections_response/rules}/delete_rule.ts (100%) rename x-pack/test/{security_solution_api_integration/test_suites/detections_response/utils => common/utils/security_solution/detections_response}/rules/get_rule_for_alert_testing.ts (100%) rename x-pack/test/{detection_engine_api_integration/utils/get_query_signal_ids.ts => common/utils/security_solution/detections_response/rules/index.ts} (53%) rename x-pack/test/{security_solution_api_integration/test_suites/detections_response/utils => common/utils/security_solution/detections_response}/rules/wait_for_rule_status.ts (100%) rename x-pack/test/{detection_engine_api_integration/utils => common/utils/security_solution/detections_response}/wait_for.ts (100%) rename x-pack/test/{detection_engine_api_integration/common/services.ts => common/utils/security_solution/index.ts} (81%) delete mode 100644 x-pack/test/detection_engine_api_integration/basic/tests/index.ts delete mode 100644 x-pack/test/detection_engine_api_integration/common/config.ts delete mode 100644 x-pack/test/detection_engine_api_integration/utils/binary_to_string.ts delete mode 100644 x-pack/test/detection_engine_api_integration/utils/count_down_es.ts delete mode 100644 x-pack/test/detection_engine_api_integration/utils/create_rule.ts delete mode 100644 x-pack/test/detection_engine_api_integration/utils/create_rule_saved_object.ts delete mode 100644 x-pack/test/detection_engine_api_integration/utils/create_signals_index.ts delete mode 100644 x-pack/test/detection_engine_api_integration/utils/delete_all_alerts.ts delete mode 100644 x-pack/test/detection_engine_api_integration/utils/delete_all_rules.ts delete mode 100644 x-pack/test/detection_engine_api_integration/utils/get_complex_rule.ts delete mode 100644 x-pack/test/detection_engine_api_integration/utils/get_complex_rule_output.ts delete mode 100644 x-pack/test/detection_engine_api_integration/utils/get_query_signals_ids.ts delete mode 100644 x-pack/test/detection_engine_api_integration/utils/get_rule_for_signal_testing.ts delete mode 100644 x-pack/test/detection_engine_api_integration/utils/get_signals_by_ids.ts delete mode 100644 x-pack/test/detection_engine_api_integration/utils/get_simple_ml_rule_update.ts delete mode 100644 x-pack/test/detection_engine_api_integration/utils/get_simple_rule.ts delete mode 100644 x-pack/test/detection_engine_api_integration/utils/get_simple_rule_as_ndjson.ts delete mode 100644 x-pack/test/detection_engine_api_integration/utils/get_simple_rule_output.ts delete mode 100644 x-pack/test/detection_engine_api_integration/utils/get_simple_rule_output_without_rule_id.ts delete mode 100644 x-pack/test/detection_engine_api_integration/utils/get_simple_rule_update.ts delete mode 100644 x-pack/test/detection_engine_api_integration/utils/get_simple_rule_without_rule_id.ts delete mode 100644 x-pack/test/detection_engine_api_integration/utils/index.ts delete mode 100644 x-pack/test/detection_engine_api_integration/utils/prebuilt_rules/create_prebuilt_rule_saved_objects.ts delete mode 100644 x-pack/test/detection_engine_api_integration/utils/prebuilt_rules/install_prebuilt_rules_and_timelines.ts delete mode 100644 x-pack/test/detection_engine_api_integration/utils/remove_server_generated_properties.ts delete mode 100644 x-pack/test/detection_engine_api_integration/utils/remove_server_generated_properties_including_rule_id.ts delete mode 100644 x-pack/test/detection_engine_api_integration/utils/rule_to_ndjson.ts delete mode 100644 x-pack/test/detection_engine_api_integration/utils/rule_to_update_schema.ts delete mode 100644 x-pack/test/detection_engine_api_integration/utils/update_rule.ts delete mode 100644 x-pack/test/detection_engine_api_integration/utils/wait_for_rule_status.ts delete mode 100644 x-pack/test/detection_engine_api_integration/utils/wait_for_signals_to_be_present.ts rename x-pack/test/{detection_engine_api_integration/basic/tests => security_solution_api_integration/test_suites/detections_response/rules_management/rule_creation/basic_license_essentials_tier}/create_rules_bulk.ts (74%) create mode 100644 x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_delete/basic_license_essentials_tier/configs/ess.config.ts create mode 100644 x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_delete/basic_license_essentials_tier/configs/serverless.config.ts rename x-pack/test/{detection_engine_api_integration/basic/tests => security_solution_api_integration/test_suites/detections_response/rules_management/rule_delete/basic_license_essentials_tier}/delete_rules.ts (79%) rename x-pack/test/{detection_engine_api_integration/basic/tests => security_solution_api_integration/test_suites/detections_response/rules_management/rule_delete/basic_license_essentials_tier}/delete_rules_bulk.ts (83%) create mode 100644 x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_delete/basic_license_essentials_tier/index.ts create mode 100644 x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_import_export/basic_license_essentials_tier/configs/ess.config.ts create mode 100644 x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_import_export/basic_license_essentials_tier/configs/serverless.config.ts rename x-pack/test/{detection_engine_api_integration/basic/tests => security_solution_api_integration/test_suites/detections_response/rules_management/rule_import_export/basic_license_essentials_tier}/export_rules.ts (82%) rename x-pack/test/{detection_engine_api_integration/basic/tests => security_solution_api_integration/test_suites/detections_response/rules_management/rule_import_export/basic_license_essentials_tier}/import_rules.ts (94%) create mode 100644 x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_import_export/basic_license_essentials_tier/index.ts create mode 100644 x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_patch/basic_license_essentials_tier/configs/ess.config.ts create mode 100644 x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_patch/basic_license_essentials_tier/configs/serverless.config.ts create mode 100644 x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_patch/basic_license_essentials_tier/index.ts rename x-pack/test/{detection_engine_api_integration/basic/tests => security_solution_api_integration/test_suites/detections_response/rules_management/rule_patch/basic_license_essentials_tier}/patch_rules.ts (84%) rename x-pack/test/{detection_engine_api_integration/basic/tests => security_solution_api_integration/test_suites/detections_response/rules_management/rule_patch/basic_license_essentials_tier}/patch_rules_bulk.ts (85%) create mode 100644 x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_read/basic_license_essentials_tier/configs/ess.config.ts create mode 100644 x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_read/basic_license_essentials_tier/configs/serverless.config.ts rename x-pack/test/{detection_engine_api_integration/basic/tests => security_solution_api_integration/test_suites/detections_response/rules_management/rule_read/basic_license_essentials_tier}/find_rules.ts (80%) create mode 100644 x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_read/basic_license_essentials_tier/index.ts rename x-pack/test/{detection_engine_api_integration/basic/tests => security_solution_api_integration/test_suites/detections_response/rules_management/rule_read/basic_license_essentials_tier}/read_rules.ts (79%) create mode 100644 x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_update/basic_license_essentials_tier/configs/ess.config.ts create mode 100644 x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_update/basic_license_essentials_tier/configs/serverless.config.ts create mode 100644 x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_update/basic_license_essentials_tier/index.ts rename x-pack/test/{detection_engine_api_integration/basic/tests => security_solution_api_integration/test_suites/detections_response/rules_management/rule_update/basic_license_essentials_tier}/update_rules.ts (86%) rename x-pack/test/{detection_engine_api_integration/basic/tests => security_solution_api_integration/test_suites/detections_response/rules_management/rule_update/basic_license_essentials_tier}/update_rules_bulk.ts (86%) delete mode 100644 x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/count_down_test.ts delete mode 100644 x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/route_with_namespace.ts delete mode 100644 x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/delete_rule.ts rename x-pack/test/{detection_engine_api_integration/basic/tests => timeline/security_and_spaces/tests/basic}/import_timelines.ts (99%) rename x-pack/test/{detection_engine_api_integration/basic/tests => timeline/security_and_spaces/tests/basic}/install_prepackaged_timelines.ts (87%) rename x-pack/test/{detection_engine_api_integration => timeline}/utils/delete_all_timelines.ts (100%) rename x-pack/test/{detection_engine_api_integration/basic/config.ts => timeline/utils/index.ts} (53%) rename x-pack/test/{security_solution_api_integration/test_suites/detections_response => timeline}/utils/wait_for.ts (100%) diff --git a/.buildkite/ftr_configs.yml b/.buildkite/ftr_configs.yml index 9404a7aab18c0..e909a09509804 100644 --- a/.buildkite/ftr_configs.yml +++ b/.buildkite/ftr_configs.yml @@ -6,7 +6,6 @@ disabled: - test/functional/config.base.js - test/functional/firefox/config.base.ts - x-pack/test/functional/config.base.js - - x-pack/test/detection_engine_api_integration/security_and_spaces/config.base.ts - x-pack/test/functional_enterprise_search/base_config.ts - x-pack/test/localization/config.base.ts - test/server_integration/config.base.js @@ -232,7 +231,6 @@ enabled: - x-pack/test/cloud_security_posture_functional/config.ts - x-pack/test/cloud_security_posture_api/config.ts - x-pack/test/dataset_quality_api_integration/basic/config.ts - - x-pack/test/detection_engine_api_integration/basic/config.ts - x-pack/test/disable_ems/config.ts - x-pack/test/encrypted_saved_objects_api_integration/config.ts - x-pack/test/examples/config.ts @@ -493,8 +491,12 @@ enabled: - x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_creation/trial_license_complete_tier/configs/serverless.config.ts - x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_patch/trial_license_complete_tier/configs/ess.config.ts - x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_patch/trial_license_complete_tier/configs/serverless.config.ts + - x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_patch/basic_license_essentials_tier/configs/ess.config.ts + - x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_patch/basic_license_essentials_tier/configs/serverless.config.ts - x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_update/trial_license_complete_tier/configs/ess.config.ts - x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_update/trial_license_complete_tier/configs/serverless.config.ts + - x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_update/basic_license_essentials_tier/configs/ess.config.ts + - x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_update/basic_license_essentials_tier/configs/serverless.config.ts - x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/bundled_prebuilt_rules_package/trial_license_complete_tier/configs/ess.config.ts - x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/bundled_prebuilt_rules_package/trial_license_complete_tier/configs/serverless.config.ts - x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/large_prebuilt_rules_package/trial_license_complete_tier/configs/ess.config.ts @@ -507,14 +509,20 @@ enabled: - x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_bulk_actions/trial_license_complete_tier/configs/serverless.config.ts - x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_delete/trial_license_complete_tier/configs/ess.config.ts - x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_delete/trial_license_complete_tier/configs/serverless.config.ts + - x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_delete/basic_license_essentials_tier/configs/ess.config.ts + - x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_delete/basic_license_essentials_tier/configs/serverless.config.ts - x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_import_export/trial_license_complete_tier/configs/ess.config.ts - x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_import_export/trial_license_complete_tier/configs/serverless.config.ts + - x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_import_export/basic_license_essentials_tier/configs/ess.config.ts + - x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_import_export/basic_license_essentials_tier/configs/serverless.config.ts - x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_management/basic_license_essentials_tier/configs/ess.config.ts - x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_management/basic_license_essentials_tier/configs/serverless.config.ts - x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_management/trial_license_complete_tier/configs/ess.config.ts - x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_management/trial_license_complete_tier/configs/serverless.config.ts - x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_read/trial_license_complete_tier/configs/ess.config.ts - x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_read/trial_license_complete_tier/configs/serverless.config.ts + - x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_read/basic_license_essentials_tier/configs/ess.config.ts + - x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_read/basic_license_essentials_tier/configs/serverless.config.ts - x-pack/test/security_solution_api_integration/test_suites/detections_response/telemetry/trial_license_complete_tier/configs/ess.config.ts - x-pack/test/security_solution_api_integration/test_suites/detections_response/telemetry/trial_license_complete_tier/configs/serverless.config.ts - x-pack/test/security_solution_api_integration/test_suites/detections_response/user_roles/trial_license_complete_tier/configs/ess.config.ts diff --git a/.buildkite/pipelines/security_solution/api_integration.yml b/.buildkite/pipelines/security_solution/api_integration.yml index b0c7832b967d5..fd5ec303a7c66 100644 --- a/.buildkite/pipelines/security_solution/api_integration.yml +++ b/.buildkite/pipelines/security_solution/api_integration.yml @@ -7,11 +7,11 @@ steps: timeout_in_minutes: 60 retry: automatic: - - exit_status: "-1" + - exit_status: '-1' limit: 3 - command: .buildkite/scripts/pipelines/security_solution_quality_gate/upload_image_metadata.sh - label: "Upload runtime info" + label: 'Upload runtime info' key: upload_runtime_info depends_on: build_image agents: @@ -19,10 +19,10 @@ steps: timeout_in_minutes: 300 retry: automatic: - - exit_status: "-1" + - exit_status: '-1' limit: 2 - - group: "Execute Tests" + - group: 'Execute Tests' depends_on: build_image steps: - label: Running exception_workflows:qa:serverless @@ -33,7 +33,7 @@ steps: timeout_in_minutes: 120 retry: automatic: - - exit_status: "*" + - exit_status: '*' limit: 2 - label: Running exception_operators_date_numeric_types:qa:serverless @@ -44,7 +44,7 @@ steps: timeout_in_minutes: 120 retry: automatic: - - exit_status: "*" + - exit_status: '*' limit: 2 - label: Running exception_operators_keyword:qa:serverless @@ -55,7 +55,7 @@ steps: timeout_in_minutes: 120 retry: automatic: - - exit_status: "*" + - exit_status: '*' limit: 2 - label: Running exception_operators_ips:qa:serverless @@ -66,7 +66,7 @@ steps: timeout_in_minutes: 120 retry: automatic: - - exit_status: "*" + - exit_status: '*' limit: 2 - label: Running exception_operators_long:qa:serverless @@ -77,7 +77,7 @@ steps: timeout_in_minutes: 120 retry: automatic: - - exit_status: "1" + - exit_status: '1' limit: 2 - label: Running exception_operators_text:qa:serverless @@ -86,17 +86,6 @@ steps: agents: queue: n2-4-spot timeout_in_minutes: 120 - retry: - automatic: - - exit_status: "1" - limit: 2 - - - label: Running rule_creation:essentials:qa:serverless - command: .buildkite/scripts/pipelines/security_solution_quality_gate/api_integration/api-integration-tests.sh rule_creation:essentials:qa:serverless - key: rule_creation:essentials:qa:serverless - agents: - queue: n2-4-spot - timeout_in_minutes: 120 retry: automatic: - exit_status: '1' @@ -135,9 +124,9 @@ steps: - exit_status: '1' limit: 2 - - label: Running entity_analytics:qa:serverless - command: .buildkite/scripts/pipelines/security_solution_quality_gate/api_integration/api-integration-tests.sh entity_analytics:qa:serverless - key: entity_analytics:qa:serverless + - label: Running genai:qa:serverless + command: .buildkite/scripts/pipelines/security_solution_quality_gate/api_integration/api-integration-tests.sh genai:qa:serverless + key: genai:qa:serverless agents: queue: n2-4-spot timeout_in_minutes: 120 @@ -146,125 +135,169 @@ steps: - exit_status: "1" limit: 2 - - label: Running genai:qa:serverless - command: .buildkite/scripts/pipelines/security_solution_quality_gate/api_integration/api-integration-tests.sh genai:qa:serverless - key: genai:qa:serverless + - label: Running rule_execution_logic:qa:serverless + command: .buildkite/scripts/pipelines/security_solution_quality_gate/api_integration/api-integration-tests.sh rule_execution_logic:qa:serverless + key: rule_execution_logic:qa:serverless agents: queue: n2-4-spot timeout_in_minutes: 120 retry: automatic: - - exit_status: "1" + - exit_status: '1' limit: 2 - - label: Running prebuilt_rules_management:qa:serverless - command: .buildkite/scripts/pipelines/security_solution_quality_gate/api_integration/api-integration-tests.sh prebuilt_rules_management:qa:serverless - key: prebuilt_rules_management:qa:serverless + - label: Running rule_patch:qa:serverless + command: .buildkite/scripts/pipelines/security_solution_quality_gate/api_integration/api-integration-tests.sh rule_patch:qa:serverless + key: rule_patch:qa:serverless agents: queue: n2-4-spot timeout_in_minutes: 120 retry: automatic: - - exit_status: "1" + - exit_status: '1' limit: 2 - - label: Running prebuilt_rules_bundled_prebuilt_rules_package:qa:serverless - command: .buildkite/scripts/pipelines/security_solution_quality_gate/api_integration/api-integration-tests.sh prebuilt_rules_bundled_prebuilt_rules_package:qa:serverless - key: prebuilt_rules_bundled_prebuilt_rules_package:qa:serverless + - label: Running rule_patch:essentials:qa:serverless + command: .buildkite/scripts/pipelines/security_solution_quality_gate/api-integration-tests.sh rule_patch:essentials:qa:serverless + key: rule_patch:essentials:qa:serverless agents: queue: n2-4-spot timeout_in_minutes: 120 retry: automatic: - - exit_status: "1" + - exit_status: '1' limit: 2 - - label: Running prebuilt_rules_large_prebuilt_rules_package:qa:serverless - command: .buildkite/scripts/pipelines/security_solution_quality_gate/api_integration/api-integration-tests.sh prebuilt_rules_large_prebuilt_rules_package:qa:serverless - key: prebuilt_rules_large_prebuilt_rules_package:qa:serverless + - label: Running rule_update:qa:serverless + command: .buildkite/scripts/pipelines/security_solution_quality_gate/api_integration/api-integration-tests.sh rule_update:qa:serverless + key: rule_update:qa:serverless agents: queue: n2-4-spot timeout_in_minutes: 120 retry: automatic: - - exit_status: "1" + - exit_status: '1' limit: 2 - - label: Running prebuilt_rules_update_prebuilt_rules_package:qa:serverless - command: .buildkite/scripts/pipelines/security_solution_quality_gate/api_integration/api-integration-tests.sh prebuilt_rules_update_prebuilt_rules_package:qa:serverless - key: prebuilt_rules_update_prebuilt_rules_package:qa:serverless + - label: Running rule_update:essentials:qa:serverless + command: .buildkite/scripts/pipelines/security_solution_quality_gate/api-integration-tests.sh rule_update:essentials:qa:serverless + key: rule_update:essentials:qa:serverless agents: queue: n2-4-spot timeout_in_minutes: 120 retry: automatic: - - exit_status: "1" + - exit_status: '1' limit: 2 - - label: Running rule_execution_logic:qa:serverless - command: .buildkite/scripts/pipelines/security_solution_quality_gate/api_integration/api-integration-tests.sh rule_execution_logic:qa:serverless - key: rule_execution_logic:qa:serverless + - label: Running rules_management:essentials:qa:serverless + command: .buildkite/scripts/pipelines/security_solution_quality_gate/api-integration-tests.sh rules_management:essentials:qa:serverless + key: rules_management:essentials:qa:serverless agents: queue: n2-4-spot timeout_in_minutes: 120 retry: automatic: - - exit_status: "1" + - exit_status: '1' limit: 2 - - label: Running user_roles:qa:serverless - command: .buildkite/scripts/pipelines/security_solution_quality_gate/api_integration/api-integration-tests.sh user_roles:qa:serverless - key: user_roles:qa:serverless + - label: Running rule_management:qa:serverless + command: .buildkite/scripts/pipelines/security_solution_quality_gate/api_integration/api-integration-tests.sh rule_management:qa:serverless + key: rule_management:qa:serverless agents: queue: n2-4-spot timeout_in_minutes: 120 retry: automatic: - - exit_status: "1" + - exit_status: '1' limit: 2 - - label: Running telemetry:qa:serverless - command: .buildkite/scripts/pipelines/security_solution_quality_gate/api_integration/api-integration-tests.sh telemetry:qa:serverless - key: telemetry:qa:serverless + - label: Running prebuilt_rules_management:qa:serverless + command: .buildkite/scripts/pipelines/security_solution_quality_gate/api-integration-tests.sh prebuilt_rules_management:qa:serverless + key: prebuilt_rules_management:qa:serverless agents: queue: n2-4-spot timeout_in_minutes: 120 retry: automatic: - - exit_status: "1" + - exit_status: '1' limit: 2 - - label: Running rule_delete:qa:serverless - command: .buildkite/scripts/pipelines/security_solution_quality_gate/api_integration/api-integration-tests.sh rule_delete:qa:serverless - key: rule_delete:qa:serverless + - label: Running prebuilt_rules_bundled_prebuilt_rules_package:qa:serverless + command: .buildkite/scripts/pipelines/security_solution_quality_gate/api-integration-tests.sh prebuilt_rules_bundled_prebuilt_rules_package:qa:serverless + key: prebuilt_rules_bundled_prebuilt_rules_package:qa:serverless agents: queue: n2-4-spot timeout_in_minutes: 120 retry: automatic: - - exit_status: "1" + - exit_status: '1' limit: 2 - - label: Running rule_update:qa:serverless - command: .buildkite/scripts/pipelines/security_solution_quality_gate/api_integration/api-integration-tests.sh rule_update:qa:serverless - key: rule_update:qa:serverless + - label: Running prebuilt_rules_large_prebuilt_rules_package:qa:serverless + command: .buildkite/scripts/pipelines/security_solution_quality_gate/api-integration-tests.sh prebuilt_rules_large_prebuilt_rules_package:qa:serverless + key: prebuilt_rules_large_prebuilt_rules_package:qa:serverless agents: queue: n2-4-spot timeout_in_minutes: 120 retry: automatic: - - exit_status: "1" + - exit_status: '1' limit: 2 - - label: Running rule_patch:qa:serverless - command: .buildkite/scripts/pipelines/security_solution_quality_gate/api_integration/api-integration-tests.sh rule_patch:qa:serverless - key: rule_patch:qa:serverless + - label: Running prebuilt_rules_update_prebuilt_rules_package:qa:serverless + command: .buildkite/scripts/pipelines/security_solution_quality_gate/api-integration-tests.sh prebuilt_rules_update_prebuilt_rules_package:qa:serverless + key: prebuilt_rules_update_prebuilt_rules_package:qa:serverless agents: queue: n2-4-spot timeout_in_minutes: 120 retry: automatic: - - exit_status: "1" + - exit_status: '1' + limit: 2 + + - label: Running rule_bulk_actions:qa:serverless + command: .buildkite/scripts/pipelines/security_solution_quality_gate/api-integration-tests.sh rule_bulk_actions:qa:serverless + key: rule_bulk_actions:qa:serverless + agents: + queue: n2-4-spot + timeout_in_minutes: 120 + retry: + automatic: + - exit_status: '1' + limit: 2 + + - label: Running rule_read:qa:serverless + command: .buildkite/scripts/pipelines/security_solution_quality_gate/api-integration-tests.sh rule_read:qa:serverless + key: rule_read:qa:serverless + agents: + queue: n2-4-spot + timeout_in_minutes: 120 + retry: + automatic: + - exit_status: '1' + limit: 2 + + - label: Running rule_read:essentials:qa:serverless + command: .buildkite/scripts/pipelines/security_solution_quality_gate/api-integration-tests.sh rule_read:essentials:qa:serverless + key: rule_read:essentials:qa:serverless + agents: + queue: n2-4-spot + timeout_in_minutes: 120 + retry: + automatic: + - exit_status: '1' + limit: 2 + + - label: Running rule_import_export:essentials:qa:serverless + command: .buildkite/scripts/pipelines/security_solution_quality_gate/api-integration-tests.sh rule_import_export:essentials:qa:serverless + key: rule_import_export:essentials:qa:serverless + agents: + queue: n2-4-spot + timeout_in_minutes: 120 + retry: + automatic: + - exit_status: '1' limit: 2 - label: Running rule_import_export:qa:serverless @@ -275,9 +308,10 @@ steps: timeout_in_minutes: 120 retry: automatic: - - exit_status: "1" + - exit_status: '1' limit: 2 + - label: Running rule_management:qa:serverless command: .buildkite/scripts/pipelines/security_solution_quality_gate/api_integration/api-integration-tests.sh rule_management:qa:serverless key: rule_management:qa:serverless @@ -286,7 +320,7 @@ steps: timeout_in_minutes: 120 retry: automatic: - - exit_status: "1" + - exit_status: '1' limit: 2 - label: Running rule_read:qa:serverless @@ -300,9 +334,53 @@ steps: - exit_status: '1' limit: 2 - - label: Running rules_management:essentials:qa:serverless - command: .buildkite/scripts/pipelines/security_solution_quality_gate/api_integration/api-integration-tests.sh rules_management:essentials:qa:serverless - key: rules_management:essentials:qa:serverless + - label: Running rule_read:essentials:qa:serverless + command: .buildkite/scripts/pipelines/security_solution_quality_gate/api-integration-tests.sh rule_read:essentials:qa:serverless + key: rule_read:essentials:qa:serverless + agents: + queue: n2-4-spot + timeout_in_minutes: 120 + retry: + automatic: + - exit_status: '1' + limit: 2 + + - label: Running rule_creation:qa:serverless + command: .buildkite/scripts/pipelines/security_solution_quality_gate/api_integration/api-integration-tests.sh rule_creation:qa:serverless + key: rule_creation:qa:serverless + agents: + queue: n2-4-spot + timeout_in_minutes: 120 + retry: + automatic: + - exit_status: '1' + limit: 2 + + - label: Running rule_creation:essentials:qa:serverless + command: .buildkite/scripts/pipelines/security_solution_quality_gate/api_integration/api-integration-tests.sh rule_creation:essentials:qa:serverless + key: rule_creation:essentials:qa:serverless + agents: + queue: n2-4-spot + timeout_in_minutes: 120 + retry: + automatic: + - exit_status: '1' + limit: 2 + + - label: Running rule_delete:qa:serverless + command: .buildkite/scripts/pipelines/security_solution_quality_gate/api_integration/api-integration-tests.sh rule_delete:qa:serverless + key: rule_delete:qa:serverless + agents: + queue: n2-4-spot + timeout_in_minutes: 120 + retry: + automatic: + - exit_status: '1' + limit: 2 + + - label: Running rule_delete:essentials:qa:serverless + command: .buildkite/scripts/pipelines/security_solution_quality_gate/api-integration-tests.sh rule_delete:essentials:qa:serverless + key: rule_delete:essentials:qa:serverless agents: queue: n2-4-spot timeout_in_minutes: 120 @@ -332,3 +410,35 @@ steps: automatic: - exit_status: '1' limit: 2 + + - label: Running user_roles:qa:serverless + command: .buildkite/scripts/pipelines/security_solution_quality_gate/api_integration/api-integration-tests.sh user_roles:qa:serverless + key: user_roles:qa:serverless + agents: + queue: n2-4-spot + timeout_in_minutes: 120 + retry: + automatic: + - exit_status: '1' + limit: 2 + + - label: Running telemetry:qa:serverless + command: .buildkite/scripts/pipelines/security_solution_quality_gate/api_integration/api-integration-tests.sh telemetry:qa:serverless + key: telemetry:qa:serverless + agents: + queue: n2-4-spot + timeout_in_minutes: 120 + retry: + automatic: + - exit_status: '1' + limit: 2 + - label: Running entity_analytics:qa:serverless + command: .buildkite/scripts/pipelines/security_solution_quality_gate/api_integration/api-integration-tests.sh entity_analytics:qa:serverless + key: entity_analytics:qa:serverless + agents: + queue: n2-4-spot + timeout_in_minutes: 120 + retry: + automatic: + - exit_status: '1' + limit: 2 diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 8f40c55a2bea3..3566a855a765f 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1257,7 +1257,7 @@ x-pack/plugins/cloud_integrations/cloud_full_story/server/config.ts @elastic/kib # Security Solution /x-pack/test/functional/es_archives/endpoint/ @elastic/security-solution /x-pack/test/plugin_functional/test_suites/resolver/ @elastic/security-solution -/x-pack/test/detection_engine_api_integration @elastic/security-solution +/x-pack/test/security_solution_api_integration @elastic/security-solution /x-pack/test/api_integration/apis/security_solution @elastic/security-solution #CC# /x-pack/plugins/security_solution/ @elastic/security-solution diff --git a/x-pack/plugins/security_solution/common/experimental_features.ts b/x-pack/plugins/security_solution/common/experimental_features.ts index 7ded25a070227..19af0a010342c 100644 --- a/x-pack/plugins/security_solution/common/experimental_features.ts +++ b/x-pack/plugins/security_solution/common/experimental_features.ts @@ -27,7 +27,7 @@ export const allowedExperimentalValues = Object.freeze({ * we don't want people to be able to violate security by getting access to whole documents * around telemetry they should not. * @see telemetry_detection_rules_preview_route.ts - * @see test/detection_engine_api_integration/security_and_spaces/tests/telemetry/README.md + * @see test/security_solution_api_integration/test_suites/telemetry/README.md */ previewTelemetryUrlEnabled: false, diff --git a/x-pack/test/cases_api_integration/common/lib/alerts.ts b/x-pack/test/cases_api_integration/common/lib/alerts.ts index e0d4b1537190b..a74524c448493 100644 --- a/x-pack/test/cases_api_integration/common/lib/alerts.ts +++ b/x-pack/test/cases_api_integration/common/lib/alerts.ts @@ -15,13 +15,13 @@ import { RiskEnrichmentFields } from '@kbn/security-solution-plugin/server/lib/d import { AttachmentType, Case } from '@kbn/cases-plugin/common'; import { ALERT_CASE_IDS } from '@kbn/rule-data-utils'; import { - getRuleForSignalTesting, + getRuleForAlertTesting, createRule, waitForRuleSuccess, - waitForSignalsToBePresent, - getSignalsByIds, - getQuerySignalIds, -} from '../../../detection_engine_api_integration/utils'; + waitForAlertsToBePresent, + getAlertsByIds, + getQueryAlertIds, +} from '../../../common/utils/security_solution'; import { superUser } from './authentication/users'; import { User } from './authentication/types'; import { getSpaceUrlPrefix } from './api/helpers'; @@ -35,13 +35,13 @@ export const createSecuritySolutionAlerts = async ( numberOfSignals: number = 1 ): Promise> => { const rule = { - ...getRuleForSignalTesting(['auditbeat-*']), + ...getRuleForAlertTesting(['auditbeat-*']), query: 'process.executable: "/usr/bin/sudo"', }; const { id } = await createRule(supertest, log, rule); await waitForRuleSuccess({ supertest, log, id }); - await waitForSignalsToBePresent(supertest, log, numberOfSignals, [id]); - const signals = await getSignalsByIds(supertest, log, [id]); + await waitForAlertsToBePresent(supertest, log, numberOfSignals, [id]); + const signals = await getAlertsByIds(supertest, log, [id]); return signals; }; @@ -53,7 +53,7 @@ export const getSecuritySolutionAlerts = async ( const { body: updatedAlert } = await supertest .post(DETECTION_ENGINE_QUERY_SIGNALS_URL) .set('kbn-xsrf', 'true') - .send(getQuerySignalIds(alertIds)) + .send(getQueryAlertIds(alertIds)) .expect(200); return updatedAlert; diff --git a/x-pack/test/cases_api_integration/security_and_spaces/tests/common/cases/delete_cases.ts b/x-pack/test/cases_api_integration/security_and_spaces/tests/common/cases/delete_cases.ts index 5166f7b135380..4c3239fe0d126 100644 --- a/x-pack/test/cases_api_integration/security_and_spaces/tests/common/cases/delete_cases.ts +++ b/x-pack/test/cases_api_integration/security_and_spaces/tests/common/cases/delete_cases.ts @@ -64,10 +64,10 @@ import { } from '../../../../common/lib/constants'; import { User } from '../../../../common/lib/authentication/types'; import { - createSignalsIndex, + createAlertsIndex, deleteAllRules, deleteAllAlerts, -} from '../../../../../detection_engine_api_integration/utils'; +} from '../../../../../common/utils/security_solution'; // eslint-disable-next-line import/no-default-export export default ({ getService }: FtrProviderContext): void => { @@ -260,7 +260,7 @@ export default ({ getService }: FtrProviderContext): void => { beforeEach(async () => { await esArchiver.load('x-pack/test/functional/es_archives/auditbeat/hosts'); - await createSignalsIndex(supertest, log); + await createAlertsIndex(supertest, log); const signals = await createSecuritySolutionAlerts(supertest, log, 2); alerts = [signals.hits.hits[0], signals.hits.hits[1]]; }); diff --git a/x-pack/test/cases_api_integration/security_and_spaces/tests/common/cases/patch_cases.ts b/x-pack/test/cases_api_integration/security_and_spaces/tests/common/cases/patch_cases.ts index 3a965b73004ef..07349749f0a2a 100644 --- a/x-pack/test/cases_api_integration/security_and_spaces/tests/common/cases/patch_cases.ts +++ b/x-pack/test/cases_api_integration/security_and_spaces/tests/common/cases/patch_cases.ts @@ -44,16 +44,16 @@ import { getConfigurationRequest, } from '../../../../common/lib/api'; import { - createSignalsIndex, + createAlertsIndex, deleteAllAlerts, deleteAllRules, - getRuleForSignalTesting, + getRuleForAlertTesting, waitForRuleSuccess, - waitForSignalsToBePresent, - getSignalsByIds, + waitForAlertsToBePresent, + getAlertsByIds, createRule, - getQuerySignalIds, -} from '../../../../../detection_engine_api_integration/utils'; + getQueryAlertIds, +} from '../../../../../common/utils/security_solution'; import { globalRead, noKibanaPrivileges, @@ -1714,7 +1714,7 @@ export default ({ getService }: FtrProviderContext): void => { describe('detections rule', () => { beforeEach(async () => { await esArchiver.load('x-pack/test/functional/es_archives/auditbeat/hosts'); - await createSignalsIndex(supertest, log); + await createAlertsIndex(supertest, log); }); afterEach(async () => { @@ -1725,15 +1725,15 @@ export default ({ getService }: FtrProviderContext): void => { it('updates alert status when the status is updated and syncAlerts=true', async () => { const rule = { - ...getRuleForSignalTesting(['auditbeat-*']), + ...getRuleForAlertTesting(['auditbeat-*']), query: 'process.executable: "/usr/bin/sudo"', }; const postedCase = await createCase(supertest, postCaseReq); const { id } = await createRule(supertest, log, rule); await waitForRuleSuccess({ supertest, log, id }); - await waitForSignalsToBePresent(supertest, log, 1, [id]); - const signals = await getSignalsByIds(supertest, log, [id]); + await waitForAlertsToBePresent(supertest, log, 1, [id]); + const signals = await getAlertsByIds(supertest, log, [id]); const alert = signals.hits.hits[0]; expect(alert._source?.[ALERT_WORKFLOW_STATUS]).eql('open'); @@ -1774,7 +1774,7 @@ export default ({ getService }: FtrProviderContext): void => { const { body: updatedAlert } = await supertest .post(DETECTION_ENGINE_QUERY_SIGNALS_URL) .set('kbn-xsrf', 'true') - .send(getQuerySignalIds([alert._id])) + .send(getQueryAlertIds([alert._id])) .expect(200); expect(updatedAlert.hits.hits[0]._source?.['kibana.alert.workflow_status']).eql( @@ -1784,7 +1784,7 @@ export default ({ getService }: FtrProviderContext): void => { it('does NOT updates alert status when the status is updated and syncAlerts=false', async () => { const rule = { - ...getRuleForSignalTesting(['auditbeat-*']), + ...getRuleForAlertTesting(['auditbeat-*']), query: 'process.executable: "/usr/bin/sudo"', }; @@ -1795,8 +1795,8 @@ export default ({ getService }: FtrProviderContext): void => { const { id } = await createRule(supertest, log, rule); await waitForRuleSuccess({ supertest, log, id }); - await waitForSignalsToBePresent(supertest, log, 1, [id]); - const signals = await getSignalsByIds(supertest, log, [id]); + await waitForAlertsToBePresent(supertest, log, 1, [id]); + const signals = await getAlertsByIds(supertest, log, [id]); const alert = signals.hits.hits[0]; expect(alert._source?.[ALERT_WORKFLOW_STATUS]).eql('open'); @@ -1832,7 +1832,7 @@ export default ({ getService }: FtrProviderContext): void => { const { body: updatedAlert } = await supertest .post(DETECTION_ENGINE_QUERY_SIGNALS_URL) .set('kbn-xsrf', 'true') - .send(getQuerySignalIds([alert._id])) + .send(getQueryAlertIds([alert._id])) .expect(200); expect(updatedAlert.hits.hits[0]._source?.['kibana.alert.workflow_status']).eql('open'); @@ -1840,7 +1840,7 @@ export default ({ getService }: FtrProviderContext): void => { it('it updates alert status when syncAlerts is turned on', async () => { const rule = { - ...getRuleForSignalTesting(['auditbeat-*']), + ...getRuleForAlertTesting(['auditbeat-*']), query: 'process.executable: "/usr/bin/sudo"', }; @@ -1851,8 +1851,8 @@ export default ({ getService }: FtrProviderContext): void => { const { id } = await createRule(supertest, log, rule); await waitForRuleSuccess({ supertest, log, id }); - await waitForSignalsToBePresent(supertest, log, 1, [id]); - const signals = await getSignalsByIds(supertest, log, [id]); + await waitForAlertsToBePresent(supertest, log, 1, [id]); + const signals = await getAlertsByIds(supertest, log, [id]); const alert = signals.hits.hits[0]; expect(alert._source?.[ALERT_WORKFLOW_STATUS]).eql('open'); @@ -1906,7 +1906,7 @@ export default ({ getService }: FtrProviderContext): void => { const { body: updatedAlert } = await supertest .post(DETECTION_ENGINE_QUERY_SIGNALS_URL) .set('kbn-xsrf', 'true') - .send(getQuerySignalIds([alert._id])) + .send(getQueryAlertIds([alert._id])) .expect(200); expect(updatedAlert.hits.hits[0]._source?.['kibana.alert.workflow_status']).eql( @@ -1916,15 +1916,15 @@ export default ({ getService }: FtrProviderContext): void => { it('it does NOT updates alert status when syncAlerts is turned off', async () => { const rule = { - ...getRuleForSignalTesting(['auditbeat-*']), + ...getRuleForAlertTesting(['auditbeat-*']), query: 'process.executable: "/usr/bin/sudo"', }; const postedCase = await createCase(supertest, postCaseReq); const { id } = await createRule(supertest, log, rule); await waitForRuleSuccess({ supertest, log, id }); - await waitForSignalsToBePresent(supertest, log, 1, [id]); - const signals = await getSignalsByIds(supertest, log, [id]); + await waitForAlertsToBePresent(supertest, log, 1, [id]); + const signals = await getAlertsByIds(supertest, log, [id]); const alert = signals.hits.hits[0]; expect(alert._source?.[ALERT_WORKFLOW_STATUS]).eql('open'); @@ -1975,7 +1975,7 @@ export default ({ getService }: FtrProviderContext): void => { const { body: updatedAlert } = await supertest .post(DETECTION_ENGINE_QUERY_SIGNALS_URL) .set('kbn-xsrf', 'true') - .send(getQuerySignalIds([alert._id])) + .send(getQueryAlertIds([alert._id])) .expect(200); expect(updatedAlert.hits.hits[0]._source['kibana.alert.workflow_status']).eql('open'); diff --git a/x-pack/test/cases_api_integration/security_and_spaces/tests/common/comments/delete_comment.ts b/x-pack/test/cases_api_integration/security_and_spaces/tests/common/comments/delete_comment.ts index 2cc6d249ef130..6a7426dd95104 100644 --- a/x-pack/test/cases_api_integration/security_and_spaces/tests/common/comments/delete_comment.ts +++ b/x-pack/test/cases_api_integration/security_and_spaces/tests/common/comments/delete_comment.ts @@ -14,10 +14,10 @@ import { getSecuritySolutionAlerts, } from '../../../../common/lib/alerts'; import { - createSignalsIndex, + createAlertsIndex, deleteAllAlerts, deleteAllRules, -} from '../../../../../detection_engine_api_integration/utils'; +} from '../../../../../common/utils/security_solution'; import { FtrProviderContext } from '../../../../common/ftr_provider_context'; import { getPostCaseRequest, postCaseReq, postCommentUserReq } from '../../../../common/lib/mock'; @@ -125,7 +125,7 @@ export default ({ getService }: FtrProviderContext): void => { beforeEach(async () => { await esArchiver.load('x-pack/test/functional/es_archives/auditbeat/hosts'); - await createSignalsIndex(supertest, log); + await createAlertsIndex(supertest, log); const signals = await createSecuritySolutionAlerts(supertest, log, 2); alerts = [signals.hits.hits[0], signals.hits.hits[1]]; }); diff --git a/x-pack/test/cases_api_integration/security_and_spaces/tests/common/comments/delete_comments.ts b/x-pack/test/cases_api_integration/security_and_spaces/tests/common/comments/delete_comments.ts index 64c84f552d507..3a73f14aca9b0 100644 --- a/x-pack/test/cases_api_integration/security_and_spaces/tests/common/comments/delete_comments.ts +++ b/x-pack/test/cases_api_integration/security_and_spaces/tests/common/comments/delete_comments.ts @@ -14,10 +14,10 @@ import { getSecuritySolutionAlerts, } from '../../../../common/lib/alerts'; import { - createSignalsIndex, + createAlertsIndex, deleteAllAlerts, deleteAllRules, -} from '../../../../../detection_engine_api_integration/utils'; +} from '../../../../../common/utils/security_solution'; import { FtrProviderContext } from '../../../../common/ftr_provider_context'; import { @@ -127,7 +127,7 @@ export default ({ getService }: FtrProviderContext): void => { beforeEach(async () => { await esArchiver.load('x-pack/test/functional/es_archives/auditbeat/hosts'); - await createSignalsIndex(supertest, log); + await createAlertsIndex(supertest, log); const signals = await createSecuritySolutionAlerts(supertest, log, 2); alerts = [signals.hits.hits[0], signals.hits.hits[1]]; }); diff --git a/x-pack/test/cases_api_integration/security_and_spaces/tests/common/comments/post_comment.ts b/x-pack/test/cases_api_integration/security_and_spaces/tests/common/comments/post_comment.ts index fcb376e4df522..1d2f58fed13f3 100644 --- a/x-pack/test/cases_api_integration/security_and_spaces/tests/common/comments/post_comment.ts +++ b/x-pack/test/cases_api_integration/security_and_spaces/tests/common/comments/post_comment.ts @@ -46,10 +46,10 @@ import { bulkCreateAttachments, } from '../../../../common/lib/api'; import { - createSignalsIndex, + createAlertsIndex, deleteAllAlerts, deleteAllRules, -} from '../../../../../detection_engine_api_integration/utils'; +} from '../../../../../common/utils/security_solution'; import { globalRead, noKibanaPrivileges, @@ -546,7 +546,7 @@ export default ({ getService }: FtrProviderContext): void => { describe('security_solution', () => { beforeEach(async () => { await esArchiver.load('x-pack/test/functional/es_archives/auditbeat/hosts'); - await createSignalsIndex(supertest, log); + await createAlertsIndex(supertest, log); }); afterEach(async () => { diff --git a/x-pack/test/cases_api_integration/security_and_spaces/tests/common/internal/bulk_create_attachments.ts b/x-pack/test/cases_api_integration/security_and_spaces/tests/common/internal/bulk_create_attachments.ts index 6c929f67b8c90..1e460515e9f84 100644 --- a/x-pack/test/cases_api_integration/security_and_spaces/tests/common/internal/bulk_create_attachments.ts +++ b/x-pack/test/cases_api_integration/security_and_spaces/tests/common/internal/bulk_create_attachments.ts @@ -45,10 +45,10 @@ import { createComment, } from '../../../../common/lib/api'; import { - createSignalsIndex, + createAlertsIndex, deleteAllAlerts, deleteAllRules, -} from '../../../../../detection_engine_api_integration/utils'; +} from '../../../../../common/utils/security_solution'; import { globalRead, noKibanaPrivileges, @@ -796,7 +796,7 @@ export default ({ getService }: FtrProviderContext): void => { describe('security_solution', () => { beforeEach(async () => { await esArchiver.load('x-pack/test/functional/es_archives/auditbeat/hosts'); - await createSignalsIndex(supertest, log); + await createAlertsIndex(supertest, log); }); afterEach(async () => { diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/alerts/create_alerts_index.ts b/x-pack/test/common/utils/security_solution/detections_response/alerts/create_alerts_index.ts similarity index 100% rename from x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/alerts/create_alerts_index.ts rename to x-pack/test/common/utils/security_solution/detections_response/alerts/create_alerts_index.ts diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/alerts/delete_all_alerts.ts b/x-pack/test/common/utils/security_solution/detections_response/alerts/delete_all_alerts.ts similarity index 100% rename from x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/alerts/delete_all_alerts.ts rename to x-pack/test/common/utils/security_solution/detections_response/alerts/delete_all_alerts.ts diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/alerts/get_alerts_by_id.ts b/x-pack/test/common/utils/security_solution/detections_response/alerts/get_alerts_by_id.ts similarity index 100% rename from x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/alerts/get_alerts_by_id.ts rename to x-pack/test/common/utils/security_solution/detections_response/alerts/get_alerts_by_id.ts diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/alerts/get_alerts_by_ids.ts b/x-pack/test/common/utils/security_solution/detections_response/alerts/get_alerts_by_ids.ts similarity index 100% rename from x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/alerts/get_alerts_by_ids.ts rename to x-pack/test/common/utils/security_solution/detections_response/alerts/get_alerts_by_ids.ts diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/alerts/get_query_alert_ids.ts b/x-pack/test/common/utils/security_solution/detections_response/alerts/get_query_alert_ids.ts similarity index 100% rename from x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/alerts/get_query_alert_ids.ts rename to x-pack/test/common/utils/security_solution/detections_response/alerts/get_query_alert_ids.ts diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/alerts/get_query_alerts_ids.ts b/x-pack/test/common/utils/security_solution/detections_response/alerts/get_query_alerts_ids.ts similarity index 100% rename from x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/alerts/get_query_alerts_ids.ts rename to x-pack/test/common/utils/security_solution/detections_response/alerts/get_query_alerts_ids.ts diff --git a/x-pack/test/common/utils/security_solution/detections_response/alerts/index.ts b/x-pack/test/common/utils/security_solution/detections_response/alerts/index.ts new file mode 100644 index 0000000000000..160f2cc322675 --- /dev/null +++ b/x-pack/test/common/utils/security_solution/detections_response/alerts/index.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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export * from './create_alerts_index'; +export * from './delete_all_alerts'; +export * from './get_query_alert_ids'; +export * from './get_query_alerts_ids'; +export * from './get_alerts_by_ids'; +export * from './get_alerts_by_id'; +export * from './wait_for_alerts_to_be_present'; diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/alerts/wait_for_alerts_to_be_present.ts b/x-pack/test/common/utils/security_solution/detections_response/alerts/wait_for_alerts_to_be_present.ts similarity index 100% rename from x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/alerts/wait_for_alerts_to_be_present.ts rename to x-pack/test/common/utils/security_solution/detections_response/alerts/wait_for_alerts_to_be_present.ts diff --git a/x-pack/test/detection_engine_api_integration/utils/count_down_test.ts b/x-pack/test/common/utils/security_solution/detections_response/count_down_test.ts similarity index 100% rename from x-pack/test/detection_engine_api_integration/utils/count_down_test.ts rename to x-pack/test/common/utils/security_solution/detections_response/count_down_test.ts diff --git a/x-pack/test/detection_engine_api_integration/common/ftr_provider_context.d.ts b/x-pack/test/common/utils/security_solution/detections_response/index.ts similarity index 58% rename from x-pack/test/detection_engine_api_integration/common/ftr_provider_context.d.ts rename to x-pack/test/common/utils/security_solution/detections_response/index.ts index aa56557c09df8..d6a06f8e57797 100644 --- a/x-pack/test/detection_engine_api_integration/common/ftr_provider_context.d.ts +++ b/x-pack/test/common/utils/security_solution/detections_response/index.ts @@ -5,8 +5,8 @@ * 2.0. */ -import { GenericFtrProviderContext } from '@kbn/test'; - -import { services } from './services'; - -export type FtrProviderContext = GenericFtrProviderContext; +export * from './rules'; +export * from './alerts'; +export * from './count_down_test'; +export * from './route_with_namespace'; +export * from './wait_for'; diff --git a/x-pack/test/detection_engine_api_integration/utils/route_with_namespace.ts b/x-pack/test/common/utils/security_solution/detections_response/route_with_namespace.ts similarity index 100% rename from x-pack/test/detection_engine_api_integration/utils/route_with_namespace.ts rename to x-pack/test/common/utils/security_solution/detections_response/route_with_namespace.ts diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/create_rule.ts b/x-pack/test/common/utils/security_solution/detections_response/rules/create_rule.ts similarity index 100% rename from x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/create_rule.ts rename to x-pack/test/common/utils/security_solution/detections_response/rules/create_rule.ts diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/delete_all_rules.ts b/x-pack/test/common/utils/security_solution/detections_response/rules/delete_all_rules.ts similarity index 100% rename from x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/delete_all_rules.ts rename to x-pack/test/common/utils/security_solution/detections_response/rules/delete_all_rules.ts diff --git a/x-pack/test/detection_engine_api_integration/utils/delete_rule.ts b/x-pack/test/common/utils/security_solution/detections_response/rules/delete_rule.ts similarity index 100% rename from x-pack/test/detection_engine_api_integration/utils/delete_rule.ts rename to x-pack/test/common/utils/security_solution/detections_response/rules/delete_rule.ts diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/get_rule_for_alert_testing.ts b/x-pack/test/common/utils/security_solution/detections_response/rules/get_rule_for_alert_testing.ts similarity index 100% rename from x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/get_rule_for_alert_testing.ts rename to x-pack/test/common/utils/security_solution/detections_response/rules/get_rule_for_alert_testing.ts diff --git a/x-pack/test/detection_engine_api_integration/utils/get_query_signal_ids.ts b/x-pack/test/common/utils/security_solution/detections_response/rules/index.ts similarity index 53% rename from x-pack/test/detection_engine_api_integration/utils/get_query_signal_ids.ts rename to x-pack/test/common/utils/security_solution/detections_response/rules/index.ts index 28e59ff7a07f6..41559d7c01c05 100644 --- a/x-pack/test/detection_engine_api_integration/utils/get_query_signal_ids.ts +++ b/x-pack/test/common/utils/security_solution/detections_response/rules/index.ts @@ -5,12 +5,8 @@ * 2.0. */ -import type { SignalIds } from '@kbn/security-solution-plugin/common/api/detection_engine'; - -export const getQuerySignalIds = (signalIds: SignalIds) => ({ - query: { - terms: { - _id: signalIds, - }, - }, -}); +export * from './create_rule'; +export * from './delete_all_rules'; +export * from './delete_rule'; +export * from './get_rule_for_alert_testing'; +export * from './wait_for_rule_status'; diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/wait_for_rule_status.ts b/x-pack/test/common/utils/security_solution/detections_response/rules/wait_for_rule_status.ts similarity index 100% rename from x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/wait_for_rule_status.ts rename to x-pack/test/common/utils/security_solution/detections_response/rules/wait_for_rule_status.ts diff --git a/x-pack/test/detection_engine_api_integration/utils/wait_for.ts b/x-pack/test/common/utils/security_solution/detections_response/wait_for.ts similarity index 100% rename from x-pack/test/detection_engine_api_integration/utils/wait_for.ts rename to x-pack/test/common/utils/security_solution/detections_response/wait_for.ts diff --git a/x-pack/test/detection_engine_api_integration/common/services.ts b/x-pack/test/common/utils/security_solution/index.ts similarity index 81% rename from x-pack/test/detection_engine_api_integration/common/services.ts rename to x-pack/test/common/utils/security_solution/index.ts index 7e415338c405f..2c70d14a79098 100644 --- a/x-pack/test/detection_engine_api_integration/common/services.ts +++ b/x-pack/test/common/utils/security_solution/index.ts @@ -5,4 +5,4 @@ * 2.0. */ -export { services } from '../../api_integration/services'; +export * from './detections_response'; diff --git a/x-pack/test/detection_engine_api_integration/basic/tests/index.ts b/x-pack/test/detection_engine_api_integration/basic/tests/index.ts deleted file mode 100644 index 3ef462f7add2a..0000000000000 --- a/x-pack/test/detection_engine_api_integration/basic/tests/index.ts +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { FtrProviderContext } from '../../common/ftr_provider_context'; - -// eslint-disable-next-line import/no-default-export -export default ({ loadTestFile }: FtrProviderContext): void => { - describe('detection engine api basic license', function () { - loadTestFile(require.resolve('./create_rules_bulk')); - loadTestFile(require.resolve('./delete_rules')); - loadTestFile(require.resolve('./delete_rules_bulk')); - loadTestFile(require.resolve('./export_rules')); - loadTestFile(require.resolve('./find_rules')); - loadTestFile(require.resolve('./import_rules')); - loadTestFile(require.resolve('./read_rules')); - loadTestFile(require.resolve('./update_rules')); - loadTestFile(require.resolve('./update_rules_bulk')); - loadTestFile(require.resolve('./patch_rules_bulk')); - loadTestFile(require.resolve('./patch_rules')); - loadTestFile(require.resolve('./import_timelines')); - }); -}; diff --git a/x-pack/test/detection_engine_api_integration/common/config.ts b/x-pack/test/detection_engine_api_integration/common/config.ts deleted file mode 100644 index a1a71bf907b86..0000000000000 --- a/x-pack/test/detection_engine_api_integration/common/config.ts +++ /dev/null @@ -1,105 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { CA_CERT_PATH } from '@kbn/dev-utils'; -import { FtrConfigProviderContext } from '@kbn/test'; -import { services } from './services'; - -interface CreateTestConfigOptions { - license: string; - ssl?: boolean; -} - -// test.not-enabled is specifically not enabled -const enabledActionTypes = [ - '.email', - '.index', - '.pagerduty', - '.swimlane', - '.server-log', - '.servicenow', - '.slack', - '.webhook', - 'test.authorization', - 'test.failing', - 'test.index-record', - 'test.noop', - 'test.rate-limit', -]; - -export function createTestConfig(options: CreateTestConfigOptions, testFiles?: string[]) { - const { license = 'trial', ssl = false } = options; - - return async ({ readConfigFile }: FtrConfigProviderContext) => { - const xPackApiIntegrationTestsConfig = await readConfigFile( - require.resolve('../../api_integration/config.ts') - ); - const servers = { - ...xPackApiIntegrationTestsConfig.get('servers'), - elasticsearch: { - ...xPackApiIntegrationTestsConfig.get('servers.elasticsearch'), - protocol: ssl ? 'https' : 'http', - }, - }; - - return { - testFiles, - servers, - services, - junit: { - reportName: 'X-Pack Detection Engine API Integration Tests', - }, - esTestCluster: { - ...xPackApiIntegrationTestsConfig.get('esTestCluster'), - license, - ssl, - serverArgs: [`xpack.license.self_generated.type=${license}`], - }, - kbnTestServer: { - ...xPackApiIntegrationTestsConfig.get('kbnTestServer'), - serverArgs: [ - ...xPackApiIntegrationTestsConfig.get('kbnTestServer.serverArgs'), - `--xpack.actions.allowedHosts=${JSON.stringify(['localhost', 'some.non.existent.com'])}`, - `--xpack.actions.enabledActionTypes=${JSON.stringify(enabledActionTypes)}`, - '--xpack.eventLog.logEntries=true', - `--xpack.securitySolution.alertIgnoreFields=${JSON.stringify([ - 'testing_ignored.constant', - '/testing_regex*/', - ])}`, // See tests within the file "ignore_fields.ts" which use these values in "alertIgnoreFields" - '--xpack.ruleRegistry.write.enabled=true', - '--xpack.ruleRegistry.write.cache.enabled=false', - '--xpack.ruleRegistry.unsafe.indexUpgrade.enabled=true', - '--xpack.ruleRegistry.unsafe.legacyMultiTenancy.enabled=true', - `--xpack.securitySolution.enableExperimental=${JSON.stringify([ - 'previewTelemetryUrlEnabled', - ])}`, - '--xpack.task_manager.poll_interval=1000', - `--xpack.actions.preconfigured=${JSON.stringify({ - 'my-test-email': { - actionTypeId: '.email', - name: 'TestEmail#xyz', - config: { - from: 'me@test.com', - service: '__json', - }, - secrets: { - user: 'user', - password: 'password', - }, - }, - })}`, - ...(ssl - ? [ - `--elasticsearch.hosts=${servers.elasticsearch.protocol}://${servers.elasticsearch.hostname}:${servers.elasticsearch.port}`, - `--elasticsearch.ssl.certificateAuthorities=${CA_CERT_PATH}`, - ] - : []), - ], - }, - }; - }; -} diff --git a/x-pack/test/detection_engine_api_integration/utils/binary_to_string.ts b/x-pack/test/detection_engine_api_integration/utils/binary_to_string.ts deleted file mode 100644 index 47202a385de56..0000000000000 --- a/x-pack/test/detection_engine_api_integration/utils/binary_to_string.ts +++ /dev/null @@ -1,22 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -/** - * Useful for export_api testing to convert from a multi-part binary back to a string - * @param res Response - * @param callback Callback - */ -export const binaryToString = (res: any, callback: any): void => { - res.setEncoding('binary'); - res.data = ''; - res.on('data', (chunk: any) => { - res.data += chunk; - }); - res.on('end', () => { - callback(null, Buffer.from(res.data)); - }); -}; diff --git a/x-pack/test/detection_engine_api_integration/utils/count_down_es.ts b/x-pack/test/detection_engine_api_integration/utils/count_down_es.ts deleted file mode 100644 index cfbcafbc06cb6..0000000000000 --- a/x-pack/test/detection_engine_api_integration/utils/count_down_es.ts +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import type { TransportResult } from '@elastic/elasticsearch'; -import type { ToolingLog } from '@kbn/tooling-log'; -import { countDownTest } from './count_down_test'; - -/** - * Does a plain countdown and checks against es queries for either conflicts in the error - * or for any over the wire issues such as timeouts or temp 404's to make the tests more - * reliant. - * @param esFunction The function to test against - * @param esFunctionName The name of the function to print if we encounter errors - * @param log The tooling logger - * @param retryCount The number of times to retry before giving up (has default) - * @param timeoutWait Time to wait before trying again (has default) - */ -export const countDownES = async ( - esFunction: () => Promise, unknown>>, - esFunctionName: string, - log: ToolingLog, - retryCount: number = 50, - timeoutWait = 250 -): Promise => { - await countDownTest( - async () => { - const result = await esFunction(); - if (result.body.version_conflicts !== 0) { - return { - passed: false, - errorMessage: 'Version conflicts for ${result.body.version_conflicts}', - }; - } else { - return { passed: true }; - } - }, - esFunctionName, - log, - retryCount, - timeoutWait - ); -}; diff --git a/x-pack/test/detection_engine_api_integration/utils/create_rule.ts b/x-pack/test/detection_engine_api_integration/utils/create_rule.ts deleted file mode 100644 index d831aba44948f..0000000000000 --- a/x-pack/test/detection_engine_api_integration/utils/create_rule.ts +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import type { ToolingLog } from '@kbn/tooling-log'; -import type SuperTest from 'supertest'; -import type { - RuleCreateProps, - RuleResponse, -} from '@kbn/security-solution-plugin/common/api/detection_engine'; - -import { DETECTION_ENGINE_RULES_URL } from '@kbn/security-solution-plugin/common/constants'; -import { deleteRule } from './delete_rule'; -import { routeWithNamespace } from './route_with_namespace'; - -/** - * Helper to cut down on the noise in some of the tests. If this detects - * a conflict it will try to manually remove the rule before re-adding the rule one time and log - * and error about the race condition. - * rule a second attempt. It only re-tries adding the rule if it encounters a conflict once. - * @param supertest The supertest deps - * @param log The tooling logger - * @param rule The rule to create - */ -export const createRule = async ( - supertest: SuperTest.SuperTest, - log: ToolingLog, - rule: RuleCreateProps, - namespace?: string -): Promise => { - const route = routeWithNamespace(DETECTION_ENGINE_RULES_URL, namespace); - const response = await supertest - .post(route) - .set('kbn-xsrf', 'true') - .set('elastic-api-version', '2023-10-31') - .send(rule); - if (response.status === 409) { - if (rule.rule_id != null) { - log.debug( - `Did not get an expected 200 "ok" when creating a rule (createRule). CI issues could happen. Suspect this line if you are seeing CI issues. body: ${JSON.stringify( - response.body - )}, status: ${JSON.stringify(response.status)}` - ); - await deleteRule(supertest, rule.rule_id); - const secondResponseTry = await supertest - .post(DETECTION_ENGINE_RULES_URL) - .set('kbn-xsrf', 'true') - .set('elastic-api-version', '2023-10-31') - .send(rule); - if (secondResponseTry.status !== 200) { - throw new Error( - `Unexpected non 200 ok when attempting to create a rule (second try): ${JSON.stringify( - response.body - )}` - ); - } else { - return secondResponseTry.body; - } - } else { - throw new Error('When creating a rule found an unexpected conflict (404)'); - } - } else if (response.status !== 200) { - throw new Error( - `Unexpected non 200 ok when attempting to create a rule: ${JSON.stringify( - response.status - )},${JSON.stringify(response, null, 4)}` - ); - } else { - return response.body; - } -}; diff --git a/x-pack/test/detection_engine_api_integration/utils/create_rule_saved_object.ts b/x-pack/test/detection_engine_api_integration/utils/create_rule_saved_object.ts deleted file mode 100644 index 93a6322011623..0000000000000 --- a/x-pack/test/detection_engine_api_integration/utils/create_rule_saved_object.ts +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import type SuperTest from 'supertest'; - -import { Rule } from '@kbn/alerting-plugin/common'; -import { - BaseRuleParams, - InternalRuleCreate, -} from '@kbn/security-solution-plugin/server/lib/detection_engine/rule_schema'; - -/** - * Creates a rule using the alerting APIs directly. - * This allows us to test some legacy types that are not exposed - * on our APIs - * - * @param supertest - */ -export const createRuleThroughAlertingEndpoint = async ( - supertest: SuperTest.SuperTest, - rule: InternalRuleCreate -): Promise> => { - const { body } = await supertest - .post('/api/alerting/rule') - .set('kbn-xsrf', 'true') - .set('elastic-api-version', '2023-10-31') - .send(rule) - .expect(200); - - return body; -}; diff --git a/x-pack/test/detection_engine_api_integration/utils/create_signals_index.ts b/x-pack/test/detection_engine_api_integration/utils/create_signals_index.ts deleted file mode 100644 index 59fd8828e667f..0000000000000 --- a/x-pack/test/detection_engine_api_integration/utils/create_signals_index.ts +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import type SuperTest from 'supertest'; -import { ToolingLog } from '@kbn/tooling-log'; - -import { DETECTION_ENGINE_INDEX_URL } from '@kbn/security-solution-plugin/common/constants'; -import { countDownTest } from './count_down_test'; - -/** - * Creates the signals index for use inside of beforeEach blocks of tests - * This will retry 50 times before giving up and hopefully still not interfere with other tests - * @param supertest The supertest client library - */ -export const createSignalsIndex = async ( - supertest: SuperTest.SuperTest, - log: ToolingLog -): Promise => { - await countDownTest( - async () => { - await supertest - .post(DETECTION_ENGINE_INDEX_URL) - .set('kbn-xsrf', 'true') - .set('elastic-api-version', '2023-10-31') - .send(); - return { - passed: true, - }; - }, - 'createSignalsIndex', - log - ); -}; diff --git a/x-pack/test/detection_engine_api_integration/utils/delete_all_alerts.ts b/x-pack/test/detection_engine_api_integration/utils/delete_all_alerts.ts deleted file mode 100644 index 8a4447e931120..0000000000000 --- a/x-pack/test/detection_engine_api_integration/utils/delete_all_alerts.ts +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import type SuperTest from 'supertest'; -import type { ToolingLog } from '@kbn/tooling-log'; -import type { Client } from '@elastic/elasticsearch'; -import { DETECTION_ENGINE_INDEX_URL } from '@kbn/security-solution-plugin/common/constants'; -import { countDownTest } from './count_down_test'; - -/** - * Deletes all alerts from a given index or indices, defaults to `.alerts-security.alerts-*` - * For use inside of afterEach blocks of tests - */ -export const deleteAllAlerts = async ( - supertest: SuperTest.SuperTest, - log: ToolingLog, - es: Client, - index: Array<'.alerts-security.alerts-*' | '.preview.alerts-security.alerts-*'> = [ - '.alerts-security.alerts-*', - ] -): Promise => { - await countDownTest( - async () => { - await supertest - .delete(DETECTION_ENGINE_INDEX_URL) - .set('kbn-xsrf', 'true') - .set('elastic-api-version', '2023-10-31') - .send(); - await es.deleteByQuery({ - index, - body: { - query: { - match_all: {}, - }, - }, - refresh: true, - }); - return { - passed: true, - }; - }, - 'deleteAllAlerts', - log - ); -}; diff --git a/x-pack/test/detection_engine_api_integration/utils/delete_all_rules.ts b/x-pack/test/detection_engine_api_integration/utils/delete_all_rules.ts deleted file mode 100644 index e0903a8df6f13..0000000000000 --- a/x-pack/test/detection_engine_api_integration/utils/delete_all_rules.ts +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import type { ToolingLog } from '@kbn/tooling-log'; -import type SuperTest from 'supertest'; - -import { - DETECTION_ENGINE_RULES_BULK_ACTION, - DETECTION_ENGINE_RULES_URL, -} from '@kbn/security-solution-plugin/common/constants'; -import { countDownTest } from './count_down_test'; - -/** - * Removes all rules by looping over any found and removing them from REST. - * @param supertest The supertest agent. - */ -export const deleteAllRules = async ( - supertest: SuperTest.SuperTest, - log: ToolingLog -): Promise => { - await countDownTest( - async () => { - await supertest - .post(DETECTION_ENGINE_RULES_BULK_ACTION) - .send({ action: 'delete', query: '' }) - .set('kbn-xsrf', 'true') - .set('elastic-api-version', '2023-10-31'); - - const { body: finalCheck } = await supertest - .get(`${DETECTION_ENGINE_RULES_URL}/_find`) - .set('kbn-xsrf', 'true') - .set('elastic-api-version', '2023-10-31') - .send(); - return { - passed: finalCheck.data.length === 0, - }; - }, - 'deleteAllRules', - log, - 50, - 1000 - ); -}; diff --git a/x-pack/test/detection_engine_api_integration/utils/get_complex_rule.ts b/x-pack/test/detection_engine_api_integration/utils/get_complex_rule.ts deleted file mode 100644 index 3e507259ce685..0000000000000 --- a/x-pack/test/detection_engine_api_integration/utils/get_complex_rule.ts +++ /dev/null @@ -1,97 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import type { RuleCreateProps } from '@kbn/security-solution-plugin/common/api/detection_engine'; - -/** - * This will return a complex rule with all the outputs possible - * @param ruleId The ruleId to set which is optional and defaults to rule-1 - */ -export const getComplexRule = (ruleId = 'rule-1'): RuleCreateProps => ({ - actions: [], - author: [], - name: 'Complex Rule Query', - description: 'Complex Rule Query', - false_positives: [ - 'https://www.example.com/some-article-about-a-false-positive', - 'some text string about why another condition could be a false positive', - ], - risk_score: 1, - risk_score_mapping: [], - rule_id: ruleId, - filters: [ - { - query: { - match_phrase: { - 'host.name': 'siem-windows', - }, - }, - }, - ], - enabled: false, - index: ['auditbeat-*', 'filebeat-*'], - interval: '5m', - output_index: '', - meta: { - anything_you_want_ui_related_or_otherwise: { - as_deep_structured_as_you_need: { - any_data_type: {}, - }, - }, - }, - max_signals: 10, - tags: ['tag 1', 'tag 2', 'any tag you want'], - to: 'now', - from: 'now-6m', - severity: 'high', - severity_mapping: [], - language: 'kuery', - type: 'query', - threat: [ - { - framework: 'MITRE ATT&CK', - tactic: { - id: 'TA0040', - name: 'impact', - reference: 'https://attack.mitre.org/tactics/TA0040/', - }, - technique: [ - { - id: 'T1499', - name: 'endpoint denial of service', - reference: 'https://attack.mitre.org/techniques/T1499/', - }, - ], - }, - { - framework: 'Some other Framework you want', - tactic: { - id: 'some-other-id', - name: 'Some other name', - reference: 'https://example.com', - }, - technique: [ - { - id: 'some-other-id', - name: 'some other technique name', - reference: 'https://example.com', - }, - ], - }, - ], - references: [ - 'http://www.example.com/some-article-about-attack', - 'Some plain text string here explaining why this is a valid thing to look out for', - ], - timeline_id: 'timeline_id', - timeline_title: 'timeline_title', - note: '# some investigation documentation', - version: 1, - query: 'user.name: root or user.name: admin', - throttle: 'no_actions', - exceptions_list: [], -}); diff --git a/x-pack/test/detection_engine_api_integration/utils/get_complex_rule_output.ts b/x-pack/test/detection_engine_api_integration/utils/get_complex_rule_output.ts deleted file mode 100644 index 0115b00c4b46b..0000000000000 --- a/x-pack/test/detection_engine_api_integration/utils/get_complex_rule_output.ts +++ /dev/null @@ -1,105 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import type { RuleResponse } from '@kbn/security-solution-plugin/common/api/detection_engine'; - -// TODO: Follow up https://github.com/elastic/kibana/pull/137628 and add an explicit type to this object -// without using Partial -/** - * This will return a complex rule with all the outputs possible - * @param ruleId The ruleId to set which is optional and defaults to rule-1 - */ -export const getComplexRuleOutput = (ruleId = 'rule-1'): Partial => ({ - actions: [], - author: [], - created_by: 'elastic', - name: 'Complex Rule Query', - description: 'Complex Rule Query', - false_positives: [ - 'https://www.example.com/some-article-about-a-false-positive', - 'some text string about why another condition could be a false positive', - ], - risk_score: 1, - risk_score_mapping: [], - rule_id: ruleId, - filters: [ - { - query: { - match_phrase: { - 'host.name': 'siem-windows', - }, - }, - }, - ], - enabled: false, - index: ['auditbeat-*', 'filebeat-*'], - immutable: false, - interval: '5m', - output_index: '', - meta: { - anything_you_want_ui_related_or_otherwise: { - as_deep_structured_as_you_need: { - any_data_type: {}, - }, - }, - }, - max_signals: 10, - tags: ['tag 1', 'tag 2', 'any tag you want'], - to: 'now', - from: 'now-6m', - revision: 0, - severity: 'high', - severity_mapping: [], - language: 'kuery', - type: 'query', - threat: [ - { - framework: 'MITRE ATT&CK', - tactic: { - id: 'TA0040', - name: 'impact', - reference: 'https://attack.mitre.org/tactics/TA0040/', - }, - technique: [ - { - id: 'T1499', - name: 'endpoint denial of service', - reference: 'https://attack.mitre.org/techniques/T1499/', - }, - ], - }, - { - framework: 'Some other Framework you want', - tactic: { - id: 'some-other-id', - name: 'Some other name', - reference: 'https://example.com', - }, - technique: [ - { - id: 'some-other-id', - name: 'some other technique name', - reference: 'https://example.com', - }, - ], - }, - ], - references: [ - 'http://www.example.com/some-article-about-attack', - 'Some plain text string here explaining why this is a valid thing to look out for', - ], - timeline_id: 'timeline_id', - timeline_title: 'timeline_title', - updated_by: 'elastic', - note: '# some investigation documentation', - version: 1, - query: 'user.name: root or user.name: admin', - exceptions_list: [], - related_integrations: [], - required_fields: [], - setup: '', -}); diff --git a/x-pack/test/detection_engine_api_integration/utils/get_query_signals_ids.ts b/x-pack/test/detection_engine_api_integration/utils/get_query_signals_ids.ts deleted file mode 100644 index 75b8696625301..0000000000000 --- a/x-pack/test/detection_engine_api_integration/utils/get_query_signals_ids.ts +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { ALERT_RULE_UUID } from '@kbn/rule-data-utils'; - -/** - * Given an array of ids for a test this will get the signals - * created from that rule's regular id. - * @param ids The rule_id to search for signals - */ -export const getQuerySignalsId = (ids: string[], size = 10) => ({ - size, - sort: ['@timestamp'], - query: { - terms: { - [ALERT_RULE_UUID]: ids, - }, - }, -}); diff --git a/x-pack/test/detection_engine_api_integration/utils/get_rule_for_signal_testing.ts b/x-pack/test/detection_engine_api_integration/utils/get_rule_for_signal_testing.ts deleted file mode 100644 index 931a7d2c1aeeb..0000000000000 --- a/x-pack/test/detection_engine_api_integration/utils/get_rule_for_signal_testing.ts +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import type { QueryRuleCreateProps } from '@kbn/security-solution-plugin/common/api/detection_engine'; - -/** - * This is a typical signal testing rule that is easy for most basic testing of output of signals. - * It starts out in an enabled true state. The 'from' is set very far back to test the basics of signal - * creation and testing by getting all the signals at once. - * @param ruleId The optional ruleId which is rule-1 by default. - * @param enabled Enables the rule on creation or not. Defaulted to true. - */ -export const getRuleForSignalTesting = ( - index: string[], - ruleId = 'rule-1', - enabled = true -): QueryRuleCreateProps => ({ - name: 'Signal Testing Query', - description: 'Tests a simple query', - enabled, - risk_score: 1, - rule_id: ruleId, - severity: 'high', - index, - type: 'query', - query: '*:*', - from: '1900-01-01T00:00:00.000Z', -}); diff --git a/x-pack/test/detection_engine_api_integration/utils/get_signals_by_ids.ts b/x-pack/test/detection_engine_api_integration/utils/get_signals_by_ids.ts deleted file mode 100644 index ae76f12e05930..0000000000000 --- a/x-pack/test/detection_engine_api_integration/utils/get_signals_by_ids.ts +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { SearchResponse } from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; -import type { ToolingLog } from '@kbn/tooling-log'; -import type SuperTest from 'supertest'; -import type { DetectionAlert } from '@kbn/security-solution-plugin/common/api/detection_engine'; -import type { RiskEnrichmentFields } from '@kbn/security-solution-plugin/server/lib/detection_engine/rule_types/utils/enrichments/types'; - -import { DETECTION_ENGINE_QUERY_SIGNALS_URL } from '@kbn/security-solution-plugin/common/constants'; -import { countDownTest } from './count_down_test'; -import { getQuerySignalsId } from './get_query_signals_ids'; -import { routeWithNamespace } from './route_with_namespace'; - -/** - * Given an array of rule ids this will return only signals based on that rule id both - * open and closed - * @param supertest agent - * @param ids Array of the rule ids - */ -export const getSignalsByIds = async ( - supertest: SuperTest.SuperTest, - log: ToolingLog, - ids: string[], - size?: number, - namespace?: string -): Promise> => { - const signalsOpen = await countDownTest>( - async () => { - const route = routeWithNamespace(DETECTION_ENGINE_QUERY_SIGNALS_URL, namespace); - const response = await supertest - .post(route) - .set('kbn-xsrf', 'true') - .send(getQuerySignalsId(ids, size)); - if (response.status !== 200) { - return { - passed: false, - errorMessage: `Status is not 200 as expected, it is: ${response.status}`, - }; - } else { - return { - passed: true, - returnValue: response.body, - }; - } - }, - 'getSignalsByIds', - log - ); - if (signalsOpen == null) { - throw new Error('Signals not defined after countdown, cannot continue'); - } else { - return signalsOpen; - } -}; diff --git a/x-pack/test/detection_engine_api_integration/utils/get_simple_ml_rule_update.ts b/x-pack/test/detection_engine_api_integration/utils/get_simple_ml_rule_update.ts deleted file mode 100644 index 3098ede4f9712..0000000000000 --- a/x-pack/test/detection_engine_api_integration/utils/get_simple_ml_rule_update.ts +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import type { RuleUpdateProps } from '@kbn/security-solution-plugin/common/api/detection_engine'; - -/** - * This is a representative ML rule payload as expected by the server for an update - * @param ruleId The rule id - * @param enabled Set to tru to enable it, by default it is off - */ -export const getSimpleMlRuleUpdate = (ruleId = 'rule-1', enabled = false): RuleUpdateProps => ({ - name: 'Simple ML Rule', - description: 'Simple Machine Learning Rule', - enabled, - anomaly_threshold: 44, - risk_score: 1, - rule_id: ruleId, - severity: 'high', - machine_learning_job_id: ['some_job_id'], - type: 'machine_learning', -}); diff --git a/x-pack/test/detection_engine_api_integration/utils/get_simple_rule.ts b/x-pack/test/detection_engine_api_integration/utils/get_simple_rule.ts deleted file mode 100644 index f5e88e34bd62c..0000000000000 --- a/x-pack/test/detection_engine_api_integration/utils/get_simple_rule.ts +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import type { QueryRuleCreateProps } from '@kbn/security-solution-plugin/common/api/detection_engine'; - -/** - * This is a typical simple rule for testing that is easy for most basic testing - * @param ruleId - * @param enabled Enables the rule on creation or not. Defaulted to true. - */ -export const getSimpleRule = (ruleId = 'rule-1', enabled = false): QueryRuleCreateProps => ({ - name: 'Simple Rule Query', - description: 'Simple Rule Query', - enabled, - risk_score: 1, - rule_id: ruleId, - severity: 'high', - index: ['auditbeat-*'], - type: 'query', - query: 'user.name: root or user.name: admin', -}); diff --git a/x-pack/test/detection_engine_api_integration/utils/get_simple_rule_as_ndjson.ts b/x-pack/test/detection_engine_api_integration/utils/get_simple_rule_as_ndjson.ts deleted file mode 100644 index fd416b1682b3d..0000000000000 --- a/x-pack/test/detection_engine_api_integration/utils/get_simple_rule_as_ndjson.ts +++ /dev/null @@ -1,21 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { getSimpleRule } from './get_simple_rule'; - -/** - * Given an array of rule_id strings this will return a ndjson buffer which is useful - * for testing uploads. - * @param ruleIds Array of strings of rule_ids - */ -export const getSimpleRuleAsNdjson = (ruleIds: string[], enabled = false): Buffer => { - const stringOfRules = ruleIds.map((ruleId) => { - const simpleRule = getSimpleRule(ruleId, enabled); - return JSON.stringify(simpleRule); - }); - return Buffer.from(stringOfRules.join('\n')); -}; diff --git a/x-pack/test/detection_engine_api_integration/utils/get_simple_rule_output.ts b/x-pack/test/detection_engine_api_integration/utils/get_simple_rule_output.ts deleted file mode 100644 index 0a9eec4906a14..0000000000000 --- a/x-pack/test/detection_engine_api_integration/utils/get_simple_rule_output.ts +++ /dev/null @@ -1,85 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import type { - RuleResponse, - SharedResponseProps, -} from '@kbn/security-solution-plugin/common/api/detection_engine'; -import { removeServerGeneratedProperties } from './remove_server_generated_properties'; - -export const getMockSharedResponseSchema = ( - ruleId = 'rule-1', - enabled = false -): SharedResponseProps => ({ - actions: [], - author: [], - created_by: 'elastic', - description: 'Simple Rule Query', - enabled, - false_positives: [], - from: 'now-6m', - immutable: false, - interval: '5m', - rule_id: ruleId, - output_index: '', - max_signals: 100, - related_integrations: [], - required_fields: [], - risk_score: 1, - risk_score_mapping: [], - name: 'Simple Rule Query', - references: [], - setup: '', - severity: 'high' as const, - severity_mapping: [], - updated_by: 'elastic', - tags: [], - to: 'now', - threat: [], - throttle: undefined, - exceptions_list: [], - version: 1, - revision: 0, - id: 'id', - updated_at: '2020-07-08T16:36:32.377Z', - created_at: '2020-07-08T16:36:32.377Z', - building_block_type: undefined, - note: undefined, - license: undefined, - outcome: undefined, - alias_target_id: undefined, - alias_purpose: undefined, - timeline_id: undefined, - timeline_title: undefined, - meta: undefined, - rule_name_override: undefined, - timestamp_override: undefined, - timestamp_override_fallback_disabled: undefined, - namespace: undefined, - investigation_fields: undefined, -}); - -const getQueryRuleOutput = (ruleId = 'rule-1', enabled = false): RuleResponse => ({ - ...getMockSharedResponseSchema(ruleId, enabled), - index: ['auditbeat-*'], - language: 'kuery', - query: 'user.name: root or user.name: admin', - type: 'query', - data_view_id: undefined, - filters: undefined, - saved_id: undefined, - response_actions: undefined, - alert_suppression: undefined, -}); - -/** - * This is the typical output of a simple rule that Kibana will output with all the defaults - * except for the server generated properties. Useful for testing end to end tests. - */ -export const getSimpleRuleOutput = (ruleId = 'rule-1', enabled = false) => { - return removeServerGeneratedProperties(getQueryRuleOutput(ruleId, enabled)); -}; diff --git a/x-pack/test/detection_engine_api_integration/utils/get_simple_rule_output_without_rule_id.ts b/x-pack/test/detection_engine_api_integration/utils/get_simple_rule_output_without_rule_id.ts deleted file mode 100644 index 56b5ab66773bb..0000000000000 --- a/x-pack/test/detection_engine_api_integration/utils/get_simple_rule_output_without_rule_id.ts +++ /dev/null @@ -1,21 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { getSimpleRuleOutput } from './get_simple_rule_output'; -import { RuleWithoutServerGeneratedProperties } from './remove_server_generated_properties'; - -/** - * This is the typical output of a simple rule that Kibana will output with all the defaults except - * for all the server generated properties such as created_by. Useful for testing end to end tests. - */ -export const getSimpleRuleOutputWithoutRuleId = ( - ruleId = 'rule-1' -): Omit => { - const rule = getSimpleRuleOutput(ruleId); - const { rule_id: rId, ...ruleWithoutRuleId } = rule; - return ruleWithoutRuleId; -}; diff --git a/x-pack/test/detection_engine_api_integration/utils/get_simple_rule_update.ts b/x-pack/test/detection_engine_api_integration/utils/get_simple_rule_update.ts deleted file mode 100644 index 6764a1d801dd5..0000000000000 --- a/x-pack/test/detection_engine_api_integration/utils/get_simple_rule_update.ts +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import type { RuleUpdateProps } from '@kbn/security-solution-plugin/common/api/detection_engine'; - -/** - * This is a typical simple rule for testing that is easy for most basic testing - * @param ruleId The rule id - * @param enabled Set to true to enable it, by default it is off - */ -export const getSimpleRuleUpdate = (ruleId = 'rule-1', enabled = false): RuleUpdateProps => ({ - name: 'Simple Rule Query', - description: 'Simple Rule Query', - enabled, - risk_score: 1, - rule_id: ruleId, - severity: 'high', - index: ['auditbeat-*'], - type: 'query', - query: 'user.name: root or user.name: admin', -}); diff --git a/x-pack/test/detection_engine_api_integration/utils/get_simple_rule_without_rule_id.ts b/x-pack/test/detection_engine_api_integration/utils/get_simple_rule_without_rule_id.ts deleted file mode 100644 index ad6ab7803ec21..0000000000000 --- a/x-pack/test/detection_engine_api_integration/utils/get_simple_rule_without_rule_id.ts +++ /dev/null @@ -1,19 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import type { RuleCreateProps } from '@kbn/security-solution-plugin/common/api/detection_engine'; -import { getSimpleRule } from './get_simple_rule'; - -/** - * This is a typical simple rule for testing that is easy for most basic testing - */ -export const getSimpleRuleWithoutRuleId = (): RuleCreateProps => { - const simpleRule = getSimpleRule(); - // eslint-disable-next-line @typescript-eslint/naming-convention - const { rule_id, ...ruleWithoutId } = simpleRule; - return ruleWithoutId; -}; diff --git a/x-pack/test/detection_engine_api_integration/utils/index.ts b/x-pack/test/detection_engine_api_integration/utils/index.ts deleted file mode 100644 index baa4be0491625..0000000000000 --- a/x-pack/test/detection_engine_api_integration/utils/index.ts +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -export * from './binary_to_string'; -export * from './count_down_es'; -export * from './count_down_test'; -export * from './create_rule'; -export * from './create_rule_saved_object'; -export * from './create_signals_index'; -export * from './delete_all_rules'; -export * from './delete_all_alerts'; -export * from './delete_all_timelines'; -export * from './get_complex_rule'; -export * from './get_complex_rule_output'; -export * from './get_simple_rule'; -export * from './get_simple_rule_output'; -export * from './get_simple_rule_output_without_rule_id'; -export * from './get_simple_rule_without_rule_id'; -export * from './route_with_namespace'; -export * from './remove_server_generated_properties'; -export * from './remove_server_generated_properties_including_rule_id'; -export * from './rule_to_update_schema'; -export * from './update_rule'; -export * from './wait_for'; -export * from './wait_for_rule_status'; -export * from './prebuilt_rules/create_prebuilt_rule_saved_objects'; -export * from './prebuilt_rules/install_prebuilt_rules_and_timelines'; -export * from './get_simple_rule_update'; -export * from './get_simple_ml_rule_update'; -export * from './get_simple_rule_as_ndjson'; -export * from './rule_to_ndjson'; -export * from './delete_rule'; -export * from './get_query_signal_ids'; -export * from './get_query_signals_ids'; -export * from './get_signals_by_ids'; -export * from './wait_for_signals_to_be_present'; -export * from './get_rule_for_signal_testing'; diff --git a/x-pack/test/detection_engine_api_integration/utils/prebuilt_rules/create_prebuilt_rule_saved_objects.ts b/x-pack/test/detection_engine_api_integration/utils/prebuilt_rules/create_prebuilt_rule_saved_objects.ts deleted file mode 100644 index 0b4bfd9254b15..0000000000000 --- a/x-pack/test/detection_engine_api_integration/utils/prebuilt_rules/create_prebuilt_rule_saved_objects.ts +++ /dev/null @@ -1,113 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { Client } from '@elastic/elasticsearch'; -import { PrebuiltRuleAsset } from '@kbn/security-solution-plugin/server/lib/detection_engine/prebuilt_rules'; -import { - getPrebuiltRuleMock, - getPrebuiltRuleWithExceptionsMock, -} from '@kbn/security-solution-plugin/server/lib/detection_engine/prebuilt_rules/mocks'; -import { ELASTIC_SECURITY_RULE_ID } from '@kbn/security-solution-plugin/common'; -import { SECURITY_SOLUTION_SAVED_OBJECT_INDEX } from '@kbn/core-saved-objects-server'; - -/** - * A helper function to create a rule asset saved object - * - * @param overrideParams Params to override the default mock - * @returns Created rule asset saved object - */ -export const createRuleAssetSavedObject = (overrideParams: Partial) => ({ - 'security-rule': { - ...getPrebuiltRuleMock(), - ...overrideParams, - }, - type: 'security-rule', - references: [], - coreMigrationVersion: '8.6.0', - updated_at: '2022-11-01T12:56:39.717Z', - created_at: '2022-11-01T12:56:39.717Z', -}); - -export const SAMPLE_PREBUILT_RULES = [ - createRuleAssetSavedObject({ - ...getPrebuiltRuleWithExceptionsMock(), - rule_id: ELASTIC_SECURITY_RULE_ID, - tags: ['test-tag-1'], - enabled: true, - }), - createRuleAssetSavedObject({ - rule_id: '000047bb-b27a-47ec-8b62-ef1a5d2c9e19', - tags: ['test-tag-2'], - }), - createRuleAssetSavedObject({ - rule_id: '00140285-b827-4aee-aa09-8113f58a08f3', - tags: ['test-tag-3'], - }), -]; - -export const SAMPLE_PREBUILT_RULES_WITH_HISTORICAL_VERSIONS = [ - createRuleAssetSavedObject({ rule_id: 'rule-1', version: 1 }), - createRuleAssetSavedObject({ rule_id: 'rule-1', version: 2 }), - createRuleAssetSavedObject({ rule_id: 'rule-2', version: 1 }), - createRuleAssetSavedObject({ rule_id: 'rule-2', version: 2 }), - createRuleAssetSavedObject({ rule_id: 'rule-2', version: 3 }), -]; - -/** - * Creates saved objects with prebuilt rule assets which can be used for - * installing actual prebuilt rules after that. It creates saved objects with - * only latest versions of the rules. Tha matches the behavior of a rules - * package without historical versions. - * - * NOTE: Version is not added to the rule asset saved object id. - * - * @param es Elasticsearch client - */ -export const createPrebuiltRuleAssetSavedObjects = async ( - es: Client, - rules = SAMPLE_PREBUILT_RULES -): Promise => { - await es.bulk({ - refresh: true, - body: rules.flatMap((doc) => [ - { - index: { - _index: SECURITY_SOLUTION_SAVED_OBJECT_INDEX, - _id: `security-rule:${doc['security-rule'].rule_id}`, - }, - }, - doc, - ]), - }); -}; - -/** - * Creates saved objects with prebuilt rule assets which can be used for - * installing actual prebuilt rules after that. It creates saved objects with - * historical versions of the rules. - * - * NOTE: Version is added to the rule asset saved object id. - * - * @param es Elasticsearch client - */ -export const createHistoricalPrebuiltRuleAssetSavedObjects = async ( - es: Client, - rules = SAMPLE_PREBUILT_RULES_WITH_HISTORICAL_VERSIONS -): Promise => { - await es.bulk({ - refresh: true, - body: rules.flatMap((doc) => [ - { - index: { - _index: SECURITY_SOLUTION_SAVED_OBJECT_INDEX, - _id: `security-rule:${doc['security-rule'].rule_id}_${doc['security-rule'].version}`, - }, - }, - doc, - ]), - }); -}; diff --git a/x-pack/test/detection_engine_api_integration/utils/prebuilt_rules/install_prebuilt_rules_and_timelines.ts b/x-pack/test/detection_engine_api_integration/utils/prebuilt_rules/install_prebuilt_rules_and_timelines.ts deleted file mode 100644 index 776af6074e07e..0000000000000 --- a/x-pack/test/detection_engine_api_integration/utils/prebuilt_rules/install_prebuilt_rules_and_timelines.ts +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { - InstallPrebuiltRulesAndTimelinesResponse, - PREBUILT_RULES_URL, -} from '@kbn/security-solution-plugin/common/api/detection_engine/prebuilt_rules'; -import type { Client } from '@elastic/elasticsearch'; -import type SuperTest from 'supertest'; -import { ALL_SAVED_OBJECT_INDICES } from '@kbn/core-saved-objects-server'; - -/** - * (LEGACY) - * Installs all prebuilt rules and timelines available in Kibana. Rules are - * installed from the security-rule saved objects. - * This is a legacy endpoint and has been replaced by: - * POST /internal/detection_engine/prebuilt_rules/installation/_perform - * - * - No rules will be installed if there are no security-rule assets (e.g., the - * package is not installed or mocks are not created). - * - * - If some prebuilt rules are already installed, they will be upgraded in case - * there are newer versions of them in security-rule assets. - * - * @param supertest SuperTest instance - * @returns Install prebuilt rules response - */ -export const installPrebuiltRulesAndTimelines = async ( - es: Client, - supertest: SuperTest.SuperTest -): Promise => { - const response = await supertest - .put(PREBUILT_RULES_URL) - .set('kbn-xsrf', 'true') - .set('elastic-api-version', '2023-10-31') - .send() - .expect(200); - - // Before we proceed, we need to refresh saved object indices. - // At the previous step we installed the prebuilt detection rules SO of type 'security-rule'. - // The savedObjectsClient does this with a call with explicit `refresh: false`. - // So, despite of the fact that the endpoint waits until the prebuilt rule will be - // successfully indexed, it doesn't wait until they become "visible" for subsequent read - // operations. - // And this is usually what we do next in integration tests: we read these SOs with utility - // function such as getPrebuiltRulesAndTimelinesStatus(). - // This can cause race condition between a write and subsequent read operation, and to - // fix it deterministically we have to refresh saved object indices and wait until it's done. - await es.indices.refresh({ index: ALL_SAVED_OBJECT_INDICES }); - - return response.body; -}; diff --git a/x-pack/test/detection_engine_api_integration/utils/remove_server_generated_properties.ts b/x-pack/test/detection_engine_api_integration/utils/remove_server_generated_properties.ts deleted file mode 100644 index d36f43ef179a5..0000000000000 --- a/x-pack/test/detection_engine_api_integration/utils/remove_server_generated_properties.ts +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import type { RuleResponse } from '@kbn/security-solution-plugin/common/api/detection_engine'; -import { omit, pickBy } from 'lodash'; - -const serverGeneratedProperties = ['id', 'created_at', 'updated_at', 'execution_summary'] as const; - -type ServerGeneratedProperties = typeof serverGeneratedProperties[number]; -export type RuleWithoutServerGeneratedProperties = Omit; - -/** - * This will remove server generated properties such as date times, etc... - * @param rule Rule to pass in to remove typical server generated properties - */ -export const removeServerGeneratedProperties = ( - rule: RuleResponse -): RuleWithoutServerGeneratedProperties => { - const removedProperties = omit(rule, serverGeneratedProperties); - - // We're only removing undefined values, so this cast correctly narrows the type - return pickBy( - removedProperties, - (value) => value !== undefined - ) as RuleWithoutServerGeneratedProperties; -}; diff --git a/x-pack/test/detection_engine_api_integration/utils/remove_server_generated_properties_including_rule_id.ts b/x-pack/test/detection_engine_api_integration/utils/remove_server_generated_properties_including_rule_id.ts deleted file mode 100644 index 1b57b5663ec23..0000000000000 --- a/x-pack/test/detection_engine_api_integration/utils/remove_server_generated_properties_including_rule_id.ts +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import type { RuleResponse } from '@kbn/security-solution-plugin/common/api/detection_engine'; - -import { removeServerGeneratedProperties } from './remove_server_generated_properties'; - -/** - * This will remove server generated properties such as date times, etc... including the rule_id - * @param rule Rule to pass in to remove typical server generated properties - */ -export const removeServerGeneratedPropertiesIncludingRuleId = ( - rule: RuleResponse -): Partial => { - const ruleWithRemovedProperties = removeServerGeneratedProperties(rule); - // eslint-disable-next-line @typescript-eslint/naming-convention - const { rule_id, ...additionalRuledIdRemoved } = ruleWithRemovedProperties; - return additionalRuledIdRemoved; -}; diff --git a/x-pack/test/detection_engine_api_integration/utils/rule_to_ndjson.ts b/x-pack/test/detection_engine_api_integration/utils/rule_to_ndjson.ts deleted file mode 100644 index 404f3c1baa962..0000000000000 --- a/x-pack/test/detection_engine_api_integration/utils/rule_to_ndjson.ts +++ /dev/null @@ -1,18 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import type { RuleCreateProps } from '@kbn/security-solution-plugin/common/api/detection_engine'; - -/** - * Given a rule this will convert it to an ndjson buffer which is useful for - * testing upload features. - * @param rule The rule to convert to ndjson - */ -export const ruleToNdjson = (rule: RuleCreateProps): Buffer => { - const stringified = JSON.stringify(rule); - return Buffer.from(`${stringified}\n`); -}; diff --git a/x-pack/test/detection_engine_api_integration/utils/rule_to_update_schema.ts b/x-pack/test/detection_engine_api_integration/utils/rule_to_update_schema.ts deleted file mode 100644 index f6669a1325eb1..0000000000000 --- a/x-pack/test/detection_engine_api_integration/utils/rule_to_update_schema.ts +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import type { - RuleResponse, - RuleUpdateProps, -} from '@kbn/security-solution-plugin/common/api/detection_engine'; -import { omit, pickBy } from 'lodash'; - -const propertiesToRemove = [ - 'id', - 'immutable', - 'updated_at', - 'updated_by', - 'created_at', - 'created_by', - 'related_integrations', - 'required_fields', - 'revision', - 'setup', - 'execution_summary', -]; - -/** - * transforms RuleResponse rule to RuleUpdateProps - * returned result can be used in rule update API calls - */ -export const ruleToUpdateSchema = (rule: RuleResponse): RuleUpdateProps => { - const removedProperties = omit(rule, propertiesToRemove); - - // We're only removing undefined values, so this cast correctly narrows the type - return pickBy(removedProperties, (value) => value !== undefined) as RuleUpdateProps; -}; diff --git a/x-pack/test/detection_engine_api_integration/utils/update_rule.ts b/x-pack/test/detection_engine_api_integration/utils/update_rule.ts deleted file mode 100644 index 53c1beb272764..0000000000000 --- a/x-pack/test/detection_engine_api_integration/utils/update_rule.ts +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import type { ToolingLog } from '@kbn/tooling-log'; -import type SuperTest from 'supertest'; - -import { DETECTION_ENGINE_RULES_URL } from '@kbn/security-solution-plugin/common/constants'; -import { - RuleUpdateProps, - RuleResponse, -} from '@kbn/security-solution-plugin/common/api/detection_engine'; - -/** - * Helper to cut down on the noise in some of the tests. This checks for - * an expected 200 still and does not do any retries. - * @param supertest The supertest deps - * @param rule The rule to create - */ -export const updateRule = async ( - supertest: SuperTest.SuperTest, - log: ToolingLog, - updatedRule: RuleUpdateProps -): Promise => { - const response = await supertest - .put(DETECTION_ENGINE_RULES_URL) - .set('kbn-xsrf', 'true') - .set('elastic-api-version', '2023-10-31') - .send(updatedRule); - if (response.status !== 200) { - log.error( - `Did not get an expected 200 "ok" when updating a rule (updateRule). CI issues could happen. Suspect this line if you are seeing CI issues. body: ${JSON.stringify( - response.body - )}, status: ${JSON.stringify(response.status)}` - ); - } - return response.body; -}; diff --git a/x-pack/test/detection_engine_api_integration/utils/wait_for_rule_status.ts b/x-pack/test/detection_engine_api_integration/utils/wait_for_rule_status.ts deleted file mode 100644 index 59607eeb47d45..0000000000000 --- a/x-pack/test/detection_engine_api_integration/utils/wait_for_rule_status.ts +++ /dev/null @@ -1,82 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import type { ToolingLog } from '@kbn/tooling-log'; -import type SuperTest from 'supertest'; -import { DETECTION_ENGINE_RULES_URL } from '@kbn/security-solution-plugin/common/constants'; -import { - RuleExecutionStatus, - RuleExecutionStatusEnum, -} from '@kbn/security-solution-plugin/common/api/detection_engine/rule_monitoring'; -import { waitFor } from './wait_for'; -import { routeWithNamespace } from './route_with_namespace'; - -interface WaitForRuleStatusBaseParams { - supertest: SuperTest.SuperTest; - log: ToolingLog; - afterDate?: Date; - namespace?: string; -} - -interface WaitForRuleStatusWithId extends WaitForRuleStatusBaseParams { - id: string; - ruleId?: never; -} - -interface WaitForRuleStatusWithRuleId extends WaitForRuleStatusBaseParams { - ruleId: string; - id?: never; -} - -export type WaitForRuleStatusParams = WaitForRuleStatusWithId | WaitForRuleStatusWithRuleId; - -/** - * Waits for rule to settle in a provided status. - * Depending on wether `id` or `ruleId` provided it may impact the behavior. - * - `id` leads to fetching a rule via ES Get API (rulesClient.resolve -> SOClient.resolve -> ES Get API) - * - `ruleId` leads to fetching a rule via ES Search API (rulesClient.find -> SOClient.find -> ES Search API) - * ES Search API may return outdated data while ES Get API always returns fresh data - */ -export const waitForRuleStatus = async ( - expectedStatus: RuleExecutionStatus, - { supertest, log, afterDate, namespace, ...idOrRuleId }: WaitForRuleStatusParams -): Promise => { - await waitFor( - async () => { - const query = 'id' in idOrRuleId ? { id: idOrRuleId.id } : { rule_id: idOrRuleId.ruleId }; - const route = routeWithNamespace(DETECTION_ENGINE_RULES_URL, namespace); - const response = await supertest - .get(route) - .set('kbn-xsrf', 'true') - .set('elastic-api-version', '2023-10-31') - .query(query) - .expect(200); - - // TODO: https://github.com/elastic/kibana/pull/121644 clean up, make type-safe - const rule = response.body; - const ruleStatus = rule?.execution_summary?.last_execution.status; - const ruleStatusDate = rule?.execution_summary?.last_execution.date; - - return ( - rule != null && - ruleStatus === expectedStatus && - (afterDate ? new Date(ruleStatusDate) > afterDate : true) - ); - }, - 'waitForRuleStatus', - log - ); -}; - -export const waitForRuleSuccess = (params: WaitForRuleStatusParams): Promise => - waitForRuleStatus(RuleExecutionStatusEnum.succeeded, params); - -export const waitForRulePartialFailure = (params: WaitForRuleStatusParams): Promise => - waitForRuleStatus(RuleExecutionStatusEnum['partial failure'], params); - -export const waitForRuleFailure = (params: WaitForRuleStatusParams): Promise => - waitForRuleStatus(RuleExecutionStatusEnum.failed, params); diff --git a/x-pack/test/detection_engine_api_integration/utils/wait_for_signals_to_be_present.ts b/x-pack/test/detection_engine_api_integration/utils/wait_for_signals_to_be_present.ts deleted file mode 100644 index b98ef40671981..0000000000000 --- a/x-pack/test/detection_engine_api_integration/utils/wait_for_signals_to_be_present.ts +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import type { ToolingLog } from '@kbn/tooling-log'; -import type SuperTest from 'supertest'; - -import { getSignalsByIds } from './get_signals_by_ids'; -import { waitFor } from './wait_for'; - -/** - * Waits for the signal hits to be greater than the supplied number - * before continuing with a default of at least one signal - * @param supertest Deps - * @param numberOfSignals The number of signals to wait for, default is 1 - */ -export const waitForSignalsToBePresent = async ( - supertest: SuperTest.SuperTest, - log: ToolingLog, - numberOfSignals = 1, - signalIds: string[], - namespace?: string -): Promise => { - await waitFor( - async () => { - const signalsOpen = await getSignalsByIds( - supertest, - log, - signalIds, - numberOfSignals, - namespace - ); - return signalsOpen.hits.hits.length >= numberOfSignals; - }, - 'waitForSignalsToBePresent', - log - ); -}; diff --git a/x-pack/test/functional/es_archives/rule_keyword_family/README.md b/x-pack/test/functional/es_archives/rule_keyword_family/README.md index b6849e7ea5915..945620015d6e1 100644 --- a/x-pack/test/functional/es_archives/rule_keyword_family/README.md +++ b/x-pack/test/functional/es_archives/rule_keyword_family/README.md @@ -1,7 +1,7 @@ Within this folder is input test data for tests within the folder: ```ts -x-pack/test/detection_engine_api_integration/security_and_spaces/tests/keyword_family +x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/keyword_family ``` where these are small ECS compliant input indexes that try to express tests that exercise different parts of diff --git a/x-pack/test/functional/es_archives/security_solution/README.md b/x-pack/test/functional/es_archives/security_solution/README.md index 897da48316155..5b1cbcbbcf8e7 100644 --- a/x-pack/test/functional/es_archives/security_solution/README.md +++ b/x-pack/test/functional/es_archives/security_solution/README.md @@ -1,7 +1,7 @@ Collection of data sets for use within various tests. Most of the tests to these live in either: ``` -x-pack/test/detection_engine_api_integrations/security_and_spaces/tests +x-pack/test/security_solution_api_integration/test_suites/ ``` or @@ -10,4 +10,4 @@ or x-pack/test/api_integration/apis/security_solution ``` -* Folder `telemetry` is for the tests underneath `detection_engine_api_integration/security_and_spaces/tests/telemetry`. +- Folder `telemetry` is for the tests underneath `x-pack/test/security_solution_api_integration/test_suites/detections_response/telemetry`. diff --git a/x-pack/test/security_solution_api_integration/package.json b/x-pack/test/security_solution_api_integration/package.json index fd8751b8c1820..10e1346c678f3 100644 --- a/x-pack/test/security_solution_api_integration/package.json +++ b/x-pack/test/security_solution_api_integration/package.json @@ -143,12 +143,24 @@ "rule_update:server:ess": "npm run initialize-server:rm rule_update ess", "rule_update:runner:ess": "npm run run-tests:rm rule_update ess essEnv", + "rule_update:essentials:server:serverless": "npm run initialize-server:rm:basic_essentials rule_update serverless", + "rule_update:essentials:runner:serverless": "npm run run-tests:rm:basic_essentials rule_update serverless serverlessEnv", + "rule_update:essentials:qa:serverless": "npm run run-tests:rm:basic_essentials rule_update serverless qaEnv", + "rule_update:basic:server:ess": "npm run initialize-server:rm:basic_essentials rule_update ess", + "rule_update:basic:runner:ess": "npm run run-tests:rm:basic_essentials rule_update ess essEnv", + "rule_patch:server:serverless": "npm run initialize-server:rm rule_patch serverless", "rule_patch:runner:serverless": "npm run run-tests:rm rule_patch serverless serverlessEnv", "rule_patch:qa:serverless": "npm run run-tests:rm rule_patch serverless qaEnv", "rule_patch:server:ess": "npm run initialize-server:rm rule_patch ess", "rule_patch:runner:ess": "npm run run-tests:rm rule_patch ess essEnv", + "rule_patch:essentials:server:serverless": "npm run initialize-server:rm:basic_essentials rule_patch serverless", + "rule_patch:essentials:runner:serverless": "npm run run-tests:rm:basic_essentials rule_patch serverless serverlessEnv", + "rule_patch:essentials:qa:serverless": "npm run run-tests:rm:basic_essentials rule_patch serverless qaEnv", + "rule_patch:basic:server:ess": "npm run initialize-server:rm:basic_essentials rule_patch ess", + "rule_patch:basic:runner:ess": "npm run run-tests:rm:basic_essentials rule_patch ess essEnv", + "prebuilt_rules_management:server:serverless": "npm run initialize-server:rm prebuilt_rules/management serverless", "prebuilt_rules_management:runner:serverless": "npm run run-tests:rm prebuilt_rules/management serverless serverlessEnv", "prebuilt_rules_management:qa:serverless": "npm run run-tests:rm prebuilt_rules/management serverless qaEnv", @@ -179,12 +191,24 @@ "rule_delete:server:ess": "npm run initialize-server:rm rule_delete ess", "rule_delete:runner:ess": "npm run run-tests:rm rule_delete ess essEnv", + "rule_delete:essentials:server:serverless": "npm run initialize-server:rm:basic_essentials rule_delete serverless", + "rule_delete:essentials:runner:serverless": "npm run run-tests:rm:basic_essentials rule_delete serverless serverlessEnv", + "rule_delete:essentials:qa:serverless": "npm run run-tests:rm:basic_essentials rule_delete serverless qaEnv", + "rule_delete:basic:server:ess": "npm run initialize-server:rm:basic_essentials rule_delete ess", + "rule_delete:basic:runner:ess": "npm run run-tests:rm:basic_essentials rule_delete ess essEnv", + "rule_import_export:server:serverless": "npm run initialize-server:rm rule_import_export serverless", "rule_import_export:runner:serverless": "npm run run-tests:rm rule_import_export serverless serverlessEnv", "rule_import_export:qa:serverless": "npm run run-tests:rm rule_import_export serverless qaEnv", "rule_import_export:server:ess": "npm run initialize-server:rm rule_import_export ess", "rule_import_export:runner:ess": "npm run run-tests:rm rule_import_export ess essEnv", + "rule_import_export:essentials:server:serverless": "npm run initialize-server:rm:basic_essentials rule_import_export serverless", + "rule_import_export:essentials:runner:serverless": "npm run run-tests:rm:basic_essentials rule_import_export serverless serverlessEnv", + "rule_import_export:essentials:qa:serverless": "npm run run-tests:rm:basic_essentials rule_import_export serverless qaEnv", + "rule_import_export:basic:server:ess": "npm run initialize-server:rm:basic_essentials rule_import_export ess", + "rule_import_export:basic:runner:ess": "npm run run-tests:rm:basic_essentials rule_import_export ess essEnv", + "rule_management:server:serverless": "npm run initialize-server:rm rule_management serverless", "rule_management:runner:serverless": "npm run run-tests:rm rule_management serverless serverlessEnv", "rule_management:qa:serverless": "npm run run-tests:rm rule_management serverless qaEnv", @@ -203,6 +227,12 @@ "rule_read:server:ess": "npm run initialize-server:rm rule_read ess", "rule_read:runner:ess": "npm run run-tests:rm rule_read ess essEnv", + "rule_read:essentials:server:serverless": "npm run initialize-server:rm:basic_essentials rule_read serverless", + "rule_read:essentials:runner:serverless": "npm run run-tests:rm:basic_essentials rule_read serverless serverlessEnv", + "rule_read:essentials:qa:serverless": "npm run run-tests:rm:basic_essentials rule_read serverless qaEnv", + "rule_read:basic:server:ess": "npm run initialize-server:rm:basic_essentials rule_read ess", + "rule_read:basic:runner:ess": "npm run run-tests:rm:basic_essentials rule_read ess essEnv", + "rules_management:essentials:server:serverless": "npm run initialize-server:rm:basic_essentials rule_management serverless", "rules_management:essentials:runner:serverless": "npm run run-tests:rm:basic_essentials rule_management serverless serverlessEnv", "rules_management:essentials:qa:serverless": "npm run run-tests:rm:basic_essentials rule_management serverless qaEnv", diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/actions/trial_license_complete_tier/add_actions.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/actions/trial_license_complete_tier/add_actions.ts index 17a2b4af95cd7..442385ed5e2f7 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/actions/trial_license_complete_tier/add_actions.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/actions/trial_license_complete_tier/add_actions.ts @@ -12,11 +12,9 @@ import { deleteAllRules, waitForRuleSuccess, deleteAllAlerts, - getCustomQueryRuleParams, - createWebHookRuleAction, - fetchRule, -} from '../../../utils'; +} from '../../../../../../common/utils/security_solution'; import { FtrProviderContext } from '../../../../../ftr_provider_context'; +import { createWebHookRuleAction, fetchRule, getCustomQueryRuleParams } from '../../../utils'; export default ({ getService }: FtrProviderContext) => { const supertest = getService('supertest'); diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/actions/trial_license_complete_tier/check_privileges.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/actions/trial_license_complete_tier/check_privileges.ts index b171542f28766..fb120059f44be 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/actions/trial_license_complete_tier/check_privileges.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/actions/trial_license_complete_tier/check_privileges.ts @@ -10,15 +10,14 @@ import { DETECTION_ENGINE_RULES_URL } from '@kbn/security-solution-plugin/common import { ROLES } from '@kbn/security-solution-plugin/common/test'; import { ThresholdRuleCreateProps } from '@kbn/security-solution-plugin/common/api/detection_engine'; +import { createRuleWithAuth, getThresholdRuleForAlertTesting } from '../../../utils'; import { - createAlertsIndex, deleteAllRules, + deleteAllAlerts, + createAlertsIndex, waitForRulePartialFailure, getRuleForAlertTesting, - createRuleWithAuth, - getThresholdRuleForAlertTesting, - deleteAllAlerts, -} from '../../../utils'; +} from '../../../../../../common/utils/security_solution'; import { createUserAndRole, deleteUserAndRole, diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/actions/trial_license_complete_tier/throttle.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/actions/trial_license_complete_tier/throttle.ts index 37b5b534b258c..029ef428394e2 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/actions/trial_license_complete_tier/throttle.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/actions/trial_license_complete_tier/throttle.ts @@ -18,16 +18,18 @@ import { X_ELASTIC_INTERNAL_ORIGIN_REQUEST, } from '@kbn/core-http-common'; import { - createAlertsIndex, - deleteAllRules, - deleteAllAlerts, getWebHookAction, getRuleWithWebHookAction, - createRule, getSimpleRule, fetchRule, updateRule, } from '../../../utils'; +import { + createRule, + createAlertsIndex, + deleteAllRules, + deleteAllAlerts, +} from '../../../../../../common/utils/security_solution'; import { FtrProviderContext } from '../../../../../ftr_provider_context'; diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/actions/trial_license_complete_tier/update_actions.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/actions/trial_license_complete_tier/update_actions.ts index 156107fffe49a..abbb039793bf3 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/actions/trial_license_complete_tier/update_actions.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/actions/trial_license_complete_tier/update_actions.ts @@ -10,10 +10,7 @@ import expect from 'expect'; import { ELASTIC_SECURITY_RULE_ID } from '@kbn/security-solution-plugin/common'; import { DETECTION_ENGINE_RULES_URL } from '@kbn/security-solution-plugin/common/constants'; import { - deleteAllRules, - deleteAllAlerts, getRuleWithWebHookAction, - waitForRuleSuccess, updateRule, installMockPrebuiltRules, fetchRule, @@ -23,6 +20,11 @@ import { getCustomQueryRuleParams, getPrebuiltRulesAndTimelinesStatus, } from '../../../utils'; +import { + deleteAllRules, + deleteAllAlerts, + waitForRuleSuccess, +} from '../../../../../../common/utils/security_solution'; import { FtrProviderContext } from '../../../../../ftr_provider_context'; export default ({ getService }: FtrProviderContext) => { diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/alerts/basic_license_essentials_tier/open_close_alerts.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/alerts/basic_license_essentials_tier/open_close_alerts.ts index ae9533d8d3ce2..120155ac26eee 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/alerts/basic_license_essentials_tier/open_close_alerts.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/alerts/basic_license_essentials_tier/open_close_alerts.ts @@ -14,18 +14,18 @@ import { DETECTION_ENGINE_QUERY_SIGNALS_URL, } from '@kbn/security-solution-plugin/common/constants'; import { DetectionAlert } from '@kbn/security-solution-plugin/common/api/detection_engine'; +import { setAlertStatus } from '../../../utils'; import { - createAlertsIndex, - setAlertStatus, getQueryAlertIds, - deleteAllRules, createRule, waitForAlertsToBePresent, getAlertsByIds, waitForRuleSuccess, getRuleForAlertTesting, + deleteAllRules, deleteAllAlerts, -} from '../../../utils'; + createAlertsIndex, +} from '../../../../../../common/utils/security_solution'; import { FtrProviderContext } from '../../../../../ftr_provider_context'; import { EsArchivePathBuilder } from '../../../../../es_archive_path_builder'; diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/alerts/basic_license_essentials_tier/query_alerts.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/alerts/basic_license_essentials_tier/query_alerts.ts index 3b372597cffd3..22f77825e36b7 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/alerts/basic_license_essentials_tier/query_alerts.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/alerts/basic_license_essentials_tier/query_alerts.ts @@ -12,7 +12,11 @@ import { ALERTS_AS_DATA_FIND_URL, } from '@kbn/security-solution-plugin/common/constants'; import { X_ELASTIC_INTERNAL_ORIGIN_REQUEST } from '@kbn/core-http-common'; -import { getAlertStatus, createAlertsIndex, deleteAllAlerts } from '../../../utils'; +import { getAlertStatus } from '../../../utils'; +import { + createAlertsIndex, + deleteAllAlerts, +} from '../../../../../../common/utils/security_solution'; import { FtrProviderContext } from '../../../../../ftr_provider_context'; export default ({ getService }: FtrProviderContext) => { diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/alerts/basic_license_essentials_tier/query_alerts_backword_compatibility.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/alerts/basic_license_essentials_tier/query_alerts_backword_compatibility.ts index 76f85dd323976..d040d902e6b05 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/alerts/basic_license_essentials_tier/query_alerts_backword_compatibility.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/alerts/basic_license_essentials_tier/query_alerts_backword_compatibility.ts @@ -8,7 +8,10 @@ import expect from '@kbn/expect'; import { DETECTION_ENGINE_QUERY_SIGNALS_URL } from '@kbn/security-solution-plugin/common/constants'; -import { createAlertsIndex, deleteAllAlerts } from '../../../utils'; +import { + createAlertsIndex, + deleteAllAlerts, +} from '../../../../../../common/utils/security_solution'; import { FtrProviderContext } from '../../../../../ftr_provider_context'; export default ({ getService }: FtrProviderContext) => { diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/alerts/trial_license_complete_tier/alerts_compatibility.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/alerts/trial_license_complete_tier/alerts_compatibility.ts index 25ef93cafaac0..ace23491ba2f7 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/alerts/trial_license_complete_tier/alerts_compatibility.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/alerts/trial_license_complete_tier/alerts_compatibility.ts @@ -21,23 +21,25 @@ import { ThresholdRuleCreateProps, } from '@kbn/security-solution-plugin/common/api/detection_engine'; import { - createRule, - createAlertsIndex, - deleteAllRules, - deleteAllAlerts, finalizeAlertsMigration, getEqlRuleForAlertTesting, - getRuleForAlertTesting, getSavedQueryRuleForAlertTesting, - getAlertsByIds, getThreatMatchRuleForAlertTesting, getThresholdRuleForAlertTesting, startAlertsMigration, + removeRandomValuedPropertiesFromAlert, +} from '../../../utils'; +import { + createRule, + createAlertsIndex, + deleteAllRules, + deleteAllAlerts, + getAlertsByIds, waitFor, waitForRuleSuccess, waitForAlertsToBePresent, - removeRandomValuedPropertiesFromAlert, -} from '../../../utils'; + getRuleForAlertTesting, +} from '../../../../../../common/utils/security_solution'; import { FtrProviderContext } from '../../../../../ftr_provider_context'; export default ({ getService }: FtrProviderContext) => { diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/alerts/trial_license_complete_tier/aliases.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/alerts/trial_license_complete_tier/aliases.ts index fc99b2a7bc301..faef3e8b272dc 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/alerts/trial_license_complete_tier/aliases.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/alerts/trial_license_complete_tier/aliases.ts @@ -16,7 +16,7 @@ import { getAlertsById, waitForRuleSuccess, waitForAlertsToBePresent, -} from '../../../utils'; +} from '../../../../../../common/utils/security_solution'; import { FtrProviderContext } from '../../../../../ftr_provider_context'; export default ({ getService }: FtrProviderContext) => { diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/alerts/trial_license_complete_tier/assignments/assignments.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/alerts/trial_license_complete_tier/assignments/assignments.ts index 1832dffcc4fad..4e4ecb21ca157 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/alerts/trial_license_complete_tier/assignments/assignments.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/alerts/trial_license_complete_tier/assignments/assignments.ts @@ -14,6 +14,7 @@ import { } from '@kbn/security-solution-plugin/common/constants'; import { DetectionAlert } from '@kbn/security-solution-plugin/common/api/detection_engine'; +import { setAlertAssignees } from '../../../../utils'; import { createAlertsIndex, createRule, @@ -22,10 +23,9 @@ import { getAlertsByIds, getQueryAlertIds, getRuleForAlertTesting, - setAlertAssignees, waitForAlertsToBePresent, waitForRuleSuccess, -} from '../../../../utils'; +} from '../../../../../../../common/utils/security_solution'; import { FtrProviderContext } from '../../../../../../ftr_provider_context'; import { EsArchivePathBuilder } from '../../../../../../es_archive_path_builder'; diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/alerts/trial_license_complete_tier/assignments/assignments_ess.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/alerts/trial_license_complete_tier/assignments/assignments_ess.ts index a55e27f707df5..3ec8bbf7bdbfc 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/alerts/trial_license_complete_tier/assignments/assignments_ess.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/alerts/trial_license_complete_tier/assignments/assignments_ess.ts @@ -12,6 +12,7 @@ import { createUserAndRole, deleteUserAndRole, } from '../../../../../../../common/services/security_solution'; +import { setAlertAssignees } from '../../../../utils'; import { createAlertsIndex, createRule, @@ -19,10 +20,9 @@ import { deleteAllRules, getAlertsByIds, getRuleForAlertTesting, - setAlertAssignees, waitForAlertsToBePresent, waitForRuleSuccess, -} from '../../../../utils'; +} from '../../../../../../../common/utils/security_solution'; import { FtrProviderContext } from '../../../../../../ftr_provider_context'; import { EsArchivePathBuilder } from '../../../../../../es_archive_path_builder'; diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/alerts/trial_license_complete_tier/assignments/assignments_serverless.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/alerts/trial_license_complete_tier/assignments/assignments_serverless.ts index 732399fe0bc7b..7064f27cfd3bd 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/alerts/trial_license_complete_tier/assignments/assignments_serverless.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/alerts/trial_license_complete_tier/assignments/assignments_serverless.ts @@ -8,6 +8,7 @@ import { DETECTION_ENGINE_ALERT_ASSIGNEES_URL } from '@kbn/security-solution-plugin/common/constants'; import { ROLES } from '@kbn/security-solution-plugin/common/test'; +import { setAlertAssignees } from '../../../../utils'; import { createAlertsIndex, createRule, @@ -15,10 +16,9 @@ import { deleteAllRules, getAlertsByIds, getRuleForAlertTesting, - setAlertAssignees, waitForAlertsToBePresent, waitForRuleSuccess, -} from '../../../../utils'; +} from '../../../../../../../common/utils/security_solution'; import { FtrProviderContext } from '../../../../../../ftr_provider_context'; import { EsArchivePathBuilder } from '../../../../../../es_archive_path_builder'; diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/alerts/trial_license_complete_tier/create_index.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/alerts/trial_license_complete_tier/create_index.ts index d86d547c8e949..0538c0ea6e390 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/alerts/trial_license_complete_tier/create_index.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/alerts/trial_license_complete_tier/create_index.ts @@ -13,7 +13,7 @@ import { import { SIGNALS_FIELD_ALIASES_VERSION } from '@kbn/security-solution-plugin/server/lib/detection_engine/routes/index/get_signals_template'; -import { deleteAllAlerts } from '../../../utils'; +import { deleteAllAlerts } from '../../../../../../common/utils/security_solution'; import { FtrProviderContext } from '../../../../../ftr_provider_context'; diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/alerts/trial_license_complete_tier/migrations/create_alerts_migrations.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/alerts/trial_license_complete_tier/migrations/create_alerts_migrations.ts index b7cd04488fb52..d1c8107bf2881 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/alerts/trial_license_complete_tier/migrations/create_alerts_migrations.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/alerts/trial_license_complete_tier/migrations/create_alerts_migrations.ts @@ -16,13 +16,11 @@ import { ROLES } from '@kbn/security-solution-plugin/common/test'; import { SIGNALS_TEMPLATE_VERSION } from '@kbn/security-solution-plugin/server/lib/detection_engine/routes/index/get_signals_template'; import { Signal } from '@kbn/security-solution-plugin/server/lib/detection_engine/rule_types/types'; +import { deleteMigrations, getIndexNameFromLoad, waitForIndexToPopulate } from '../../../../utils'; import { createAlertsIndex, - deleteMigrations, deleteAllAlerts, - getIndexNameFromLoad, - waitForIndexToPopulate, -} from '../../../../utils'; +} from '../../../../../../../common/utils/security_solution'; import { createUserAndRole, deleteUserAndRole, diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/alerts/trial_license_complete_tier/migrations/delete_alerts_migrations.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/alerts/trial_license_complete_tier/migrations/delete_alerts_migrations.ts index a3c133992259f..01f2ec0062f13 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/alerts/trial_license_complete_tier/migrations/delete_alerts_migrations.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/alerts/trial_license_complete_tier/migrations/delete_alerts_migrations.ts @@ -13,12 +13,12 @@ import { DETECTION_ENGINE_SIGNALS_MIGRATION_URL, } from '@kbn/security-solution-plugin/common/constants'; import { ROLES } from '@kbn/security-solution-plugin/common/test'; +import { getIndexNameFromLoad } from '../../../../utils'; import { createAlertsIndex, deleteAllAlerts, - getIndexNameFromLoad, waitFor, -} from '../../../../utils'; +} from '../../../../../../../common/utils/security_solution'; import { createUserAndRole } from '../../../../../../../common/services/security_solution'; interface CreateResponse { diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/alerts/trial_license_complete_tier/migrations/finalize_alerts_migrations.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/alerts/trial_license_complete_tier/migrations/finalize_alerts_migrations.ts index 74fbb7099fbd9..e63993369bad2 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/alerts/trial_license_complete_tier/migrations/finalize_alerts_migrations.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/alerts/trial_license_complete_tier/migrations/finalize_alerts_migrations.ts @@ -13,13 +13,12 @@ import { DETECTION_ENGINE_SIGNALS_MIGRATION_URL, } from '@kbn/security-solution-plugin/common/constants'; import { ROLES } from '@kbn/security-solution-plugin/common/test'; +import { deleteMigrations, getIndexNameFromLoad } from '../../../../utils'; import { createAlertsIndex, - deleteMigrations, deleteAllAlerts, - getIndexNameFromLoad, waitFor, -} from '../../../../utils'; +} from '../../../../../../../common/utils/security_solution'; import { createUserAndRole, deleteUserAndRole, diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/alerts/trial_license_complete_tier/migrations/get_alerts_migration_status.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/alerts/trial_license_complete_tier/migrations/get_alerts_migration_status.ts index 2c4576caba4fc..1ed26a7bb5423 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/alerts/trial_license_complete_tier/migrations/get_alerts_migration_status.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/alerts/trial_license_complete_tier/migrations/get_alerts_migration_status.ts @@ -9,7 +9,11 @@ import expect from '@kbn/expect'; import { DETECTION_ENGINE_SIGNALS_MIGRATION_STATUS_URL } from '@kbn/security-solution-plugin/common/constants'; import { ROLES } from '@kbn/security-solution-plugin/common/test'; -import { createAlertsIndex, deleteAllAlerts, getIndexNameFromLoad } from '../../../../utils'; +import { getIndexNameFromLoad } from '../../../../utils'; +import { + createAlertsIndex, + deleteAllAlerts, +} from '../../../../../../../common/utils/security_solution'; import { createUserAndRole, deleteUserAndRole, diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/alerts/trial_license_complete_tier/open_close_alerts.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/alerts/trial_license_complete_tier/open_close_alerts.ts index 2746a40f57dc5..7a1ea17d1530a 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/alerts/trial_license_complete_tier/open_close_alerts.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/alerts/trial_license_complete_tier/open_close_alerts.ts @@ -17,11 +17,10 @@ import { } from '@kbn/security-solution-plugin/common/constants'; import { ROLES } from '@kbn/security-solution-plugin/common/test'; import { DetectionAlert } from '@kbn/security-solution-plugin/common/api/detection_engine'; +import { setAlertStatus, getAlertUpdateByQueryEmptyResponse, refreshIndex } from '../../../utils'; import { createAlertsIndex, deleteAllAlerts, - setAlertStatus, - getAlertUpdateByQueryEmptyResponse, getQueryAlertIds, deleteAllRules, createRule, @@ -29,8 +28,7 @@ import { getAlertsByIds, waitForRuleSuccess, getRuleForAlertTesting, - refreshIndex, -} from '../../../utils'; +} from '../../../../../../common/utils/security_solution'; import { createUserAndRole, deleteUserAndRole, diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/alerts/trial_license_complete_tier/set_alert_tags.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/alerts/trial_license_complete_tier/set_alert_tags.ts index e1492b2f4b63d..25ed0c62d0d58 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/alerts/trial_license_complete_tier/set_alert_tags.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/alerts/trial_license_complete_tier/set_alert_tags.ts @@ -14,6 +14,7 @@ import { } from '@kbn/security-solution-plugin/common/constants'; import { DetectionAlert } from '@kbn/security-solution-plugin/common/api/detection_engine'; +import { setAlertTags } from '../../../utils'; import { createAlertsIndex, deleteAllAlerts, @@ -24,8 +25,7 @@ import { getAlertsByIds, waitForRuleSuccess, getRuleForAlertTesting, - setAlertTags, -} from '../../../utils'; +} from '../../../../../../common/utils/security_solution'; import { FtrProviderContext } from '../../../../../ftr_provider_context'; import { EsArchivePathBuilder } from '../../../../../es_archive_path_builder'; diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/exceptions/operators_data_types/date_numeric_types/trial_license_complete_tier/date.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/exceptions/operators_data_types/date_numeric_types/trial_license_complete_tier/date.ts index d060785e600c0..9c3a3afa52d68 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/exceptions/operators_data_types/date_numeric_types/trial_license_complete_tier/date.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/exceptions/operators_data_types/date_numeric_types/trial_license_complete_tier/date.ts @@ -13,9 +13,9 @@ import { deleteListsIndex, importFile, } from '../../../../../../lists_and_exception_lists/utils'; +import { createRuleWithExceptionEntries } from '../../../../../utils'; import { createRule, - createRuleWithExceptionEntries, createAlertsIndex, deleteAllRules, deleteAllAlerts, @@ -23,7 +23,7 @@ import { getAlertsById, waitForRuleSuccess, waitForAlertsToBePresent, -} from '../../../../../utils'; +} from '../../../../../../../../common/utils/security_solution'; import { FtrProviderContext } from '../../../../../../../ftr_provider_context'; export default ({ getService }: FtrProviderContext) => { diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/exceptions/operators_data_types/date_numeric_types/trial_license_complete_tier/double.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/exceptions/operators_data_types/date_numeric_types/trial_license_complete_tier/double.ts index a0a278ccc775e..6b4ecfab9024e 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/exceptions/operators_data_types/date_numeric_types/trial_license_complete_tier/double.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/exceptions/operators_data_types/date_numeric_types/trial_license_complete_tier/double.ts @@ -13,9 +13,9 @@ import { deleteListsIndex, importFile, } from '../../../../../../lists_and_exception_lists/utils'; +import { createRuleWithExceptionEntries } from '../../../../../utils'; import { createRule, - createRuleWithExceptionEntries, createAlertsIndex, deleteAllRules, deleteAllAlerts, @@ -23,7 +23,7 @@ import { getAlertsById, waitForRuleSuccess, waitForAlertsToBePresent, -} from '../../../../../utils'; +} from '../../../../../../../../common/utils/security_solution'; import { FtrProviderContext } from '../../../../../../../ftr_provider_context'; export default ({ getService }: FtrProviderContext) => { diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/exceptions/operators_data_types/date_numeric_types/trial_license_complete_tier/float.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/exceptions/operators_data_types/date_numeric_types/trial_license_complete_tier/float.ts index b25f13c027820..16408aba28834 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/exceptions/operators_data_types/date_numeric_types/trial_license_complete_tier/float.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/exceptions/operators_data_types/date_numeric_types/trial_license_complete_tier/float.ts @@ -13,9 +13,9 @@ import { deleteListsIndex, importFile, } from '../../../../../../lists_and_exception_lists/utils'; +import { createRuleWithExceptionEntries } from '../../../../../utils'; import { createRule, - createRuleWithExceptionEntries, createAlertsIndex, deleteAllRules, deleteAllAlerts, @@ -23,7 +23,7 @@ import { getAlertsById, waitForRuleSuccess, waitForAlertsToBePresent, -} from '../../../../../utils'; +} from '../../../../../../../../common/utils/security_solution'; import { FtrProviderContext } from '../../../../../../../ftr_provider_context'; export default ({ getService }: FtrProviderContext) => { diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/exceptions/operators_data_types/date_numeric_types/trial_license_complete_tier/integer.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/exceptions/operators_data_types/date_numeric_types/trial_license_complete_tier/integer.ts index 6b2803892ddc8..fd5b2e6fd9bcb 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/exceptions/operators_data_types/date_numeric_types/trial_license_complete_tier/integer.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/exceptions/operators_data_types/date_numeric_types/trial_license_complete_tier/integer.ts @@ -13,9 +13,9 @@ import { deleteListsIndex, importFile, } from '../../../../../../lists_and_exception_lists/utils'; +import { createRuleWithExceptionEntries } from '../../../../../utils'; import { createRule, - createRuleWithExceptionEntries, createAlertsIndex, deleteAllRules, deleteAllAlerts, @@ -23,7 +23,7 @@ import { getAlertsById, waitForRuleSuccess, waitForAlertsToBePresent, -} from '../../../../../utils'; +} from '../../../../../../../../common/utils/security_solution'; import { FtrProviderContext } from '../../../../../../../ftr_provider_context'; export default ({ getService }: FtrProviderContext) => { diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/exceptions/operators_data_types/ips/trial_license_complete_tier/ip.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/exceptions/operators_data_types/ips/trial_license_complete_tier/ip.ts index 7fcb5b09103fe..cdffe6d65aa23 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/exceptions/operators_data_types/ips/trial_license_complete_tier/ip.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/exceptions/operators_data_types/ips/trial_license_complete_tier/ip.ts @@ -13,9 +13,9 @@ import { deleteListsIndex, importFile, } from '../../../../../../lists_and_exception_lists/utils'; +import { createRuleWithExceptionEntries } from '../../../../../utils'; import { createRule, - createRuleWithExceptionEntries, createAlertsIndex, deleteAllRules, deleteAllAlerts, @@ -23,7 +23,7 @@ import { getAlertsById, waitForRuleSuccess, waitForAlertsToBePresent, -} from '../../../../../utils'; +} from '../../../../../../../../common/utils/security_solution'; import { FtrProviderContext } from '../../../../../../../ftr_provider_context'; export default ({ getService }: FtrProviderContext) => { diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/exceptions/operators_data_types/ips/trial_license_complete_tier/ip_array.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/exceptions/operators_data_types/ips/trial_license_complete_tier/ip_array.ts index d65679ed93216..398e5d5f13573 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/exceptions/operators_data_types/ips/trial_license_complete_tier/ip_array.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/exceptions/operators_data_types/ips/trial_license_complete_tier/ip_array.ts @@ -13,9 +13,9 @@ import { deleteListsIndex, importFile, } from '../../../../../../lists_and_exception_lists/utils'; +import { createRuleWithExceptionEntries } from '../../../../../utils'; import { createRule, - createRuleWithExceptionEntries, createAlertsIndex, deleteAllRules, deleteAllAlerts, @@ -23,7 +23,7 @@ import { getAlertsById, waitForRuleSuccess, waitForAlertsToBePresent, -} from '../../../../../utils'; +} from '../../../../../../../../common/utils/security_solution'; import { FtrProviderContext } from '../../../../../../../ftr_provider_context'; export default ({ getService }: FtrProviderContext) => { diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/exceptions/operators_data_types/keyword/trial_license_complete_tier/keyword.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/exceptions/operators_data_types/keyword/trial_license_complete_tier/keyword.ts index f8896bf7a43e8..f8272e6bf4b0b 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/exceptions/operators_data_types/keyword/trial_license_complete_tier/keyword.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/exceptions/operators_data_types/keyword/trial_license_complete_tier/keyword.ts @@ -13,9 +13,9 @@ import { deleteListsIndex, importFile, } from '../../../../../../lists_and_exception_lists/utils'; +import { createRuleWithExceptionEntries } from '../../../../../utils'; import { createRule, - createRuleWithExceptionEntries, createAlertsIndex, deleteAllRules, deleteAllAlerts, @@ -23,7 +23,7 @@ import { getAlertsById, waitForRuleSuccess, waitForAlertsToBePresent, -} from '../../../../../utils'; +} from '../../../../../../../../common/utils/security_solution'; import { FtrProviderContext } from '../../../../../../../ftr_provider_context'; export default ({ getService }: FtrProviderContext) => { diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/exceptions/operators_data_types/keyword/trial_license_complete_tier/keyword_array.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/exceptions/operators_data_types/keyword/trial_license_complete_tier/keyword_array.ts index 54cbe4285c240..6d8da5cd51159 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/exceptions/operators_data_types/keyword/trial_license_complete_tier/keyword_array.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/exceptions/operators_data_types/keyword/trial_license_complete_tier/keyword_array.ts @@ -14,9 +14,9 @@ import { importFile, } from '../../../../../../lists_and_exception_lists/utils'; +import { createRuleWithExceptionEntries } from '../../../../../utils'; import { createRule, - createRuleWithExceptionEntries, createAlertsIndex, deleteAllRules, deleteAllAlerts, @@ -24,7 +24,7 @@ import { getAlertsById, waitForRuleSuccess, waitForAlertsToBePresent, -} from '../../../../../utils'; +} from '../../../../../../../../common/utils/security_solution'; import { FtrProviderContext } from '../../../../../../../ftr_provider_context'; export default ({ getService }: FtrProviderContext) => { diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/exceptions/operators_data_types/long/trial_license_complete_tier/long.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/exceptions/operators_data_types/long/trial_license_complete_tier/long.ts index c604789f802ad..4acbd385bde43 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/exceptions/operators_data_types/long/trial_license_complete_tier/long.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/exceptions/operators_data_types/long/trial_license_complete_tier/long.ts @@ -13,9 +13,9 @@ import { deleteListsIndex, importFile, } from '../../../../../../lists_and_exception_lists/utils'; +import { createRuleWithExceptionEntries } from '../../../../../utils'; import { createRule, - createRuleWithExceptionEntries, createAlertsIndex, deleteAllRules, deleteAllAlerts, @@ -23,7 +23,7 @@ import { getAlertsById, waitForRuleSuccess, waitForAlertsToBePresent, -} from '../../../../../utils'; +} from '../../../../../../../../common/utils/security_solution'; import { FtrProviderContext } from '../../../../../../../ftr_provider_context'; export default ({ getService }: FtrProviderContext) => { diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/exceptions/operators_data_types/text/trial_license_complete_tier/text.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/exceptions/operators_data_types/text/trial_license_complete_tier/text.ts index 537cfaf6ae991..d8c44b3fc5e91 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/exceptions/operators_data_types/text/trial_license_complete_tier/text.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/exceptions/operators_data_types/text/trial_license_complete_tier/text.ts @@ -14,9 +14,9 @@ import { importFile, importTextFile, } from '../../../../../../lists_and_exception_lists/utils'; +import { createRuleWithExceptionEntries } from '../../../../../utils'; import { createRule, - createRuleWithExceptionEntries, createAlertsIndex, deleteAllRules, deleteAllAlerts, @@ -24,7 +24,7 @@ import { getAlertsById, waitForRuleSuccess, waitForAlertsToBePresent, -} from '../../../../../utils'; +} from '../../../../../../../../common/utils/security_solution'; import { FtrProviderContext } from '../../../../../../../ftr_provider_context'; export default ({ getService }: FtrProviderContext) => { diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/exceptions/operators_data_types/text/trial_license_complete_tier/text_array.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/exceptions/operators_data_types/text/trial_license_complete_tier/text_array.ts index 34835f27bdcc4..674d24e6231f1 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/exceptions/operators_data_types/text/trial_license_complete_tier/text_array.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/exceptions/operators_data_types/text/trial_license_complete_tier/text_array.ts @@ -13,9 +13,9 @@ import { deleteListsIndex, importFile, } from '../../../../../../lists_and_exception_lists/utils'; +import { createRuleWithExceptionEntries } from '../../../../../utils'; import { createRule, - createRuleWithExceptionEntries, createAlertsIndex, deleteAllRules, deleteAllAlerts, @@ -23,7 +23,7 @@ import { getAlertsById, waitForRuleSuccess, waitForAlertsToBePresent, -} from '../../../../../utils'; +} from '../../../../../../../../common/utils/security_solution'; import { FtrProviderContext } from '../../../../../../../ftr_provider_context'; export default ({ getService }: FtrProviderContext) => { diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/exceptions/workflows/trial_license_complete_tier/create_endpoint_exceptions.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/exceptions/workflows/trial_license_complete_tier/create_endpoint_exceptions.ts index e686675d808e2..8850976d1cf53 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/exceptions/workflows/trial_license_complete_tier/create_endpoint_exceptions.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/exceptions/workflows/trial_license_complete_tier/create_endpoint_exceptions.ts @@ -9,9 +9,9 @@ import { ToolingLog } from '@kbn/tooling-log'; import expect from 'expect'; import type SuperTest from 'supertest'; +import { createRuleWithExceptionEntries } from '../../../../utils'; import { createRule, - createRuleWithExceptionEntries, createAlertsIndex, deleteAllRules, deleteAllAlerts, @@ -19,7 +19,7 @@ import { getAlertsById, waitForRuleSuccess, waitForAlertsToBePresent, -} from '../../../../utils'; +} from '../../../../../../../common/utils/security_solution'; import { createListsIndex, deleteAllExceptions, diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/exceptions/workflows/trial_license_complete_tier/create_rule_exceptions.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/exceptions/workflows/trial_license_complete_tier/create_rule_exceptions.ts index bebe8b5a64c1b..9a51cd8a1e130 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/exceptions/workflows/trial_license_complete_tier/create_rule_exceptions.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/exceptions/workflows/trial_license_complete_tier/create_rule_exceptions.ts @@ -18,17 +18,19 @@ import { import { getCreateExceptionListMinimalSchemaMock } from '@kbn/lists-plugin/common/schemas/request/create_exception_list_schema.mock'; import { fetchRule, - createRule, getSimpleRule, - createAlertsIndex, - deleteAllRules, createExceptionList, - deleteAllAlerts, getRuleSOById, createRuleThroughAlertingEndpoint, getRuleSavedObjectWithLegacyInvestigationFields, checkInvestigationFieldSoValue, } from '../../../../utils'; +import { + createRule, + createAlertsIndex, + deleteAllRules, + deleteAllAlerts, +} from '../../../../../../../common/utils/security_solution'; import { deleteAllExceptions, removeExceptionListItemServerGeneratedProperties, diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/exceptions/workflows/trial_license_complete_tier/find_rule_exception_references.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/exceptions/workflows/trial_license_complete_tier/find_rule_exception_references.ts index c6c25cd0d8a7c..de24472e94989 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/exceptions/workflows/trial_license_complete_tier/find_rule_exception_references.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/exceptions/workflows/trial_license_complete_tier/find_rule_exception_references.ts @@ -22,14 +22,13 @@ import { import { FtrProviderContext } from '../../../../../../ftr_provider_context'; +import { getSimpleRule, createExceptionList } from '../../../../utils'; import { createRule, - getSimpleRule, deleteAllRules, - createExceptionList, deleteAllAlerts, createAlertsIndex, -} from '../../../../utils'; +} from '../../../../../../../common/utils/security_solution'; import { deleteAllExceptions } from '../../../../../lists_and_exception_lists/utils'; export default ({ getService }: FtrProviderContext) => { diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/exceptions/workflows/trial_license_complete_tier/role_based_rule_exceptions_workflows.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/exceptions/workflows/trial_license_complete_tier/role_based_rule_exceptions_workflows.ts index e308732db3821..1472637d3b5f5 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/exceptions/workflows/trial_license_complete_tier/role_based_rule_exceptions_workflows.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/exceptions/workflows/trial_license_complete_tier/role_based_rule_exceptions_workflows.ts @@ -30,31 +30,32 @@ import { ELASTIC_SECURITY_RULE_ID } from '@kbn/security-solution-plugin/common'; import { EsArchivePathBuilder } from '../../../../../../es_archive_path_builder'; import { - createAlertsIndex, fetchRule, - createRule, getSimpleRule, - deleteAllRules, createExceptionList, createExceptionListItem, getThresholdRuleForAlertTesting, getSimpleRuleOutput, removeServerGeneratedProperties, downgradeImmutableRule, - waitForRuleSuccess, installMockPrebuiltRules, - waitForAlertsToBePresent, - getAlertsByIds, findImmutableRuleById, getPrebuiltRulesAndTimelinesStatus, getOpenAlerts, createRuleWithExceptionEntries, getEqlRuleForAlertTesting, SAMPLE_PREBUILT_RULES, - deleteAllAlerts, updateUsername, } from '../../../../utils'; - +import { + createAlertsIndex, + createRule, + deleteAllRules, + waitForRuleSuccess, + waitForAlertsToBePresent, + getAlertsByIds, + deleteAllAlerts, +} from '../../../../../../../common/utils/security_solution'; import { createListsIndex, deleteAllExceptions, diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/exceptions/workflows/trial_license_complete_tier/rule_exception_synchronizations.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/exceptions/workflows/trial_license_complete_tier/rule_exception_synchronizations.ts index 5a68270e1220a..f966098d4fa58 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/exceptions/workflows/trial_license_complete_tier/rule_exception_synchronizations.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/exceptions/workflows/trial_license_complete_tier/rule_exception_synchronizations.ts @@ -18,13 +18,12 @@ import type { RuleCreateProps, } from '@kbn/security-solution-plugin/common/api/detection_engine'; import { getCreateExceptionListDetectionSchemaMock } from '@kbn/lists-plugin/common/schemas/request/create_exception_list_schema.mock'; +import { createRuleWithExceptionEntries, getSimpleRule } from '../../../../utils'; import { deleteAllAlerts, - getSimpleRule, - createRuleWithExceptionEntries, deleteAllRules, createRule, -} from '../../../../utils'; +} from '../../../../../../../common/utils/security_solution'; import { createListsIndex, deleteAllExceptions, @@ -45,8 +44,8 @@ export default ({ getService }: FtrProviderContext) => { await deleteAllExceptions(supertest, log); }); /* - This test to mimic if we have two browser tabs, and the user tried to - edit an exception in a tab after deleting it in another + This test to mimic if we have two browser tabs, and the user tried to + edit an exception in a tab after deleting it in another */ it('should Not edit an exception after being deleted', async () => { const { list_id: skippedListId, ...newExceptionItem } = @@ -101,7 +100,7 @@ export default ({ getService }: FtrProviderContext) => { }); }); /* - This test to mimic if we have two browser tabs, and the user tried to + This test to mimic if we have two browser tabs, and the user tried to edit an exception with value-list was deleted in another tab */ it('should Not allow editing an Exception with deleted ValueList', async () => { diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/execution_logic/eql.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/execution_logic/eql.ts index 77d8b5ffb373d..801efa011cfd2 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/execution_logic/eql.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/execution_logic/eql.ts @@ -30,14 +30,16 @@ import { } from '@kbn/security-solution-plugin/common/field_maps/field_names'; import { getMaxSignalsWarning as getMaxAlertsWarning } from '@kbn/security-solution-plugin/server/lib/detection_engine/rule_types/utils/utils'; import { - createRule, - deleteAllRules, - deleteAllAlerts, getEqlRuleForAlertTesting, getOpenAlerts, getPreviewAlerts, previewRule, } from '../../../../utils'; +import { + createRule, + deleteAllRules, + deleteAllAlerts, +} from '../../../../../../../common/utils/security_solution'; import { FtrProviderContext } from '../../../../../../ftr_provider_context'; import { EsArchivePathBuilder } from '../../../../../../es_archive_path_builder'; diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/execution_logic/esql.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/execution_logic/esql.ts index c582b9d94f3ce..8f95d0c1c3770 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/execution_logic/esql.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/execution_logic/esql.ts @@ -15,17 +15,19 @@ import { RuleExecutionStatusEnum } from '@kbn/security-solution-plugin/common/ap import { getMaxSignalsWarning as getMaxAlertsWarning } from '@kbn/security-solution-plugin/server/lib/detection_engine/rule_types/utils/utils'; import { - deleteAllRules, - deleteAllAlerts, getPreviewAlerts, previewRule, - createRule, getOpenAlerts, dataGeneratorFactory, previewRuleWithExceptionEntries, removeRandomValuedPropertiesFromAlert, patchRule, } from '../../../../utils'; +import { + deleteAllRules, + deleteAllAlerts, + createRule, +} from '../../../../../../../common/utils/security_solution'; import { deleteAllExceptions } from '../../../../../lists_and_exception_lists/utils'; import { FtrProviderContext } from '../../../../../../ftr_provider_context'; diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/execution_logic/machine_learning.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/execution_logic/machine_learning.ts index 60ad53f94937f..cceea36fc7329 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/execution_logic/machine_learning.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/execution_logic/machine_learning.ts @@ -34,9 +34,6 @@ import { importFile, } from '../../../../../lists_and_exception_lists/utils'; import { - createRule, - deleteAllRules, - deleteAllAlerts, executeSetupModuleRequest, forceStartDatafeeds, getOpenAlerts, @@ -44,6 +41,11 @@ import { previewRule, previewRuleWithExceptionEntries, } from '../../../../utils'; +import { + createRule, + deleteAllRules, + deleteAllAlerts, +} from '../../../../../../../common/utils/security_solution'; import { FtrProviderContext } from '../../../../../../ftr_provider_context'; import { EsArchivePathBuilder } from '../../../../../../es_archive_path_builder'; diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/execution_logic/new_terms.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/execution_logic/new_terms.ts index 7cacab1066da4..d82e9a2b932ca 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/execution_logic/new_terms.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/execution_logic/new_terms.ts @@ -14,9 +14,6 @@ import { getCreateNewTermsRulesSchemaMock } from '@kbn/security-solution-plugin/ import { getMaxSignalsWarning as getMaxAlertsWarning } from '@kbn/security-solution-plugin/server/lib/detection_engine/rule_types/utils/utils'; import { - createRule, - deleteAllRules, - deleteAllAlerts, getOpenAlerts, getPreviewAlerts, previewRule, @@ -24,6 +21,11 @@ import { previewRuleWithExceptionEntries, removeRandomValuedPropertiesFromAlert, } from '../../../../utils'; +import { + createRule, + deleteAllRules, + deleteAllAlerts, +} from '../../../../../../../common/utils/security_solution'; import { deleteAllExceptions } from '../../../../../lists_and_exception_lists/utils'; import { FtrProviderContext } from '../../../../../../ftr_provider_context'; import { EsArchivePathBuilder } from '../../../../../../es_archive_path_builder'; diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/execution_logic/non_ecs_fields.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/execution_logic/non_ecs_fields.ts index 01eb52dbc4901..bdd3a53914fcf 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/execution_logic/non_ecs_fields.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/execution_logic/non_ecs_fields.ts @@ -7,14 +7,16 @@ import expect from 'expect'; import { - deleteAllRules, - deleteAllAlerts, getPreviewAlerts, - getRuleForAlertTesting, previewRule, dataGeneratorFactory, enhanceDocument, } from '../../../../utils'; +import { + deleteAllRules, + deleteAllAlerts, + getRuleForAlertTesting, +} from '../../../../../../../common/utils/security_solution'; import { FtrProviderContext } from '../../../../../../ftr_provider_context'; const getQueryRule = (docIdToQuery: string) => ({ diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/execution_logic/query.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/execution_logic/query.ts index ac7aa41223c9e..ebdd9a71f5215 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/execution_logic/query.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/execution_logic/query.ts @@ -50,12 +50,8 @@ import { deleteAllExceptions } from '../../../../../lists_and_exception_lists/ut import { createExceptionList, createExceptionListItem, - createRule, - deleteAllRules, - deleteAllAlerts, getOpenAlerts, getPreviewAlerts, - getRuleForAlertTesting, getSimpleRule, previewRule, setAlertStatus, @@ -65,6 +61,12 @@ import { getRuleSavedObjectWithLegacyInvestigationFields, dataGeneratorFactory, } from '../../../../utils'; +import { + createRule, + deleteAllRules, + deleteAllAlerts, + getRuleForAlertTesting, +} from '../../../../../../../common/utils/security_solution'; import { FtrProviderContext } from '../../../../../../ftr_provider_context'; import { EsArchivePathBuilder } from '../../../../../../es_archive_path_builder'; diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/execution_logic/saved_query.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/execution_logic/saved_query.ts index cc170182923f9..c44a5cb5293ae 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/execution_logic/saved_query.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/execution_logic/saved_query.ts @@ -16,13 +16,13 @@ import { ALERT_ORIGINAL_TIME, ALERT_ORIGINAL_EVENT, } from '@kbn/security-solution-plugin/common/field_maps/field_names'; +import { getOpenAlerts } from '../../../../utils'; import { createRule, deleteAllRules, deleteAllAlerts, - getOpenAlerts, getRuleForAlertTesting, -} from '../../../../utils'; +} from '../../../../../../../common/utils/security_solution'; import { FtrProviderContext } from '../../../../../../ftr_provider_context'; import { EsArchivePathBuilder } from '../../../../../../es_archive_path_builder'; diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/execution_logic/threat_match.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/execution_logic/threat_match.ts index e8cbeb2c1b4b3..41cce68818a0b 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/execution_logic/threat_match.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/execution_logic/threat_match.ts @@ -37,14 +37,12 @@ import { } from '@kbn/security-solution-plugin/common/field_maps/field_names'; import { RuleExecutionStatusEnum } from '@kbn/security-solution-plugin/common/api/detection_engine/rule_monitoring'; import { getMaxSignalsWarning as getMaxAlertsWarning } from '@kbn/security-solution-plugin/server/lib/detection_engine/rule_types/utils/utils'; +import { previewRule, getOpenAlerts, getPreviewAlerts } from '../../../../utils'; import { - previewRule, - getOpenAlerts, - getPreviewAlerts, deleteAllAlerts, deleteAllRules, createRule, -} from '../../../../utils'; +} from '../../../../../../../common/utils/security_solution'; import { FtrProviderContext } from '../../../../../../ftr_provider_context'; import { EsArchivePathBuilder } from '../../../../../../es_archive_path_builder'; diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/execution_logic/threat_match_alert_suppression.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/execution_logic/threat_match_alert_suppression.ts index 3f53e6d8a7100..194af2d2ea979 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/execution_logic/threat_match_alert_suppression.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/execution_logic/threat_match_alert_suppression.ts @@ -25,8 +25,8 @@ import { ThreatMatchRuleCreateProps } from '@kbn/security-solution-plugin/common import { RuleExecutionStatusEnum } from '@kbn/security-solution-plugin/common/api/detection_engine/rule_monitoring'; import { ALERT_ORIGINAL_TIME } from '@kbn/security-solution-plugin/common/field_maps/field_names'; +import { createRule } from '../../../../../../../common/utils/security_solution'; import { - createRule, getOpenAlerts, getPreviewAlerts, getThreatMatchRuleForAlertTesting, diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/execution_logic/threshold.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/execution_logic/threshold.ts index 9449750b38465..661fc43f74d20 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/execution_logic/threshold.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/execution_logic/threshold.ts @@ -24,8 +24,8 @@ import { ALERT_THRESHOLD_RESULT, } from '@kbn/security-solution-plugin/common/field_maps/field_names'; import { getMaxSignalsWarning as getMaxAlertsWarning } from '@kbn/security-solution-plugin/server/lib/detection_engine/rule_types/utils/utils'; +import { createRule } from '../../../../../../../common/utils/security_solution'; import { - createRule, getOpenAlerts, getPreviewAlerts, getThresholdRuleForAlertTesting, diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/execution_logic/threshold_alert_suppression.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/execution_logic/threshold_alert_suppression.ts index 436e869937e5e..3afba97724b5c 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/execution_logic/threshold_alert_suppression.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/execution_logic/threshold_alert_suppression.ts @@ -23,8 +23,8 @@ import { ThresholdRuleCreateProps } from '@kbn/security-solution-plugin/common/a import { RuleExecutionStatusEnum } from '@kbn/security-solution-plugin/common/api/detection_engine/rule_monitoring'; import { ALERT_ORIGINAL_TIME } from '@kbn/security-solution-plugin/common/field_maps/field_names'; +import { createRule } from '../../../../../../../common/utils/security_solution'; import { - createRule, getOpenAlerts, getPreviewAlerts, getThresholdRuleForAlertTesting, diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/ignore_fields.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/ignore_fields.ts index f4c49ba1434b1..3e3cf7eac685f 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/ignore_fields.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/ignore_fields.ts @@ -7,16 +7,16 @@ import expect from '@kbn/expect'; +import { getEqlRuleForAlertTesting } from '../../../utils'; import { createRule, createAlertsIndex, deleteAllRules, deleteAllAlerts, - getEqlRuleForAlertTesting, getAlertsById, waitForRuleSuccess, waitForAlertsToBePresent, -} from '../../../utils'; +} from '../../../../../../common/utils/security_solution'; interface Ignore { normal_constant?: string; diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/keyword_family/const_keyword.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/keyword_family/const_keyword.ts index bd19ea428a2d4..fca298744b69c 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/keyword_family/const_keyword.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/keyword_family/const_keyword.ts @@ -12,18 +12,17 @@ import { } from '@kbn/security-solution-plugin/common/api/detection_engine'; import { ALERT_THRESHOLD_RESULT } from '@kbn/security-solution-plugin/common/field_maps/field_names'; +import { getEqlRuleForAlertTesting, getThresholdRuleForAlertTesting } from '../../../../utils'; import { createRule, createAlertsIndex, deleteAllRules, deleteAllAlerts, - getEqlRuleForAlertTesting, getRuleForAlertTesting, getAlertsById, - getThresholdRuleForAlertTesting, waitForRuleSuccess, waitForAlertsToBePresent, -} from '../../../../utils'; +} from '../../../../../../../common/utils/security_solution'; import { FtrProviderContext } from '../../../../../../ftr_provider_context'; export default ({ getService }: FtrProviderContext) => { diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/keyword_family/keyword.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/keyword_family/keyword.ts index 0edfd13a2e264..ddab1e5a1a44b 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/keyword_family/keyword.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/keyword_family/keyword.ts @@ -13,18 +13,17 @@ import { ThresholdRuleCreateProps, } from '@kbn/security-solution-plugin/common/api/detection_engine'; import { ALERT_THRESHOLD_RESULT } from '@kbn/security-solution-plugin/common/field_maps/field_names'; +import { getEqlRuleForAlertTesting, getThresholdRuleForAlertTesting } from '../../../../utils'; import { createRule, createAlertsIndex, deleteAllRules, deleteAllAlerts, - getEqlRuleForAlertTesting, getRuleForAlertTesting, getAlertsById, - getThresholdRuleForAlertTesting, waitForRuleSuccess, waitForAlertsToBePresent, -} from '../../../../utils'; +} from '../../../../../../../common/utils/security_solution'; import { FtrProviderContext } from '../../../../../../ftr_provider_context'; export default ({ getService }: FtrProviderContext) => { diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/keyword_family/keyword_mixed_with_const.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/keyword_family/keyword_mixed_with_const.ts index 7f9c55a56c46b..4844aadb76a27 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/keyword_family/keyword_mixed_with_const.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/keyword_family/keyword_mixed_with_const.ts @@ -12,17 +12,17 @@ import { } from '@kbn/security-solution-plugin/common/api/detection_engine'; import { ALERT_THRESHOLD_RESULT } from '@kbn/security-solution-plugin/common/field_maps/field_names'; +import { getEqlRuleForAlertTesting } from '../../../../utils'; import { createRule, createAlertsIndex, deleteAllRules, deleteAllAlerts, - getEqlRuleForAlertTesting, getRuleForAlertTesting, getAlertsById, waitForRuleSuccess, waitForAlertsToBePresent, -} from '../../../../utils'; +} from '../../../../../../../common/utils/security_solution'; import { FtrProviderContext } from '../../../../../../ftr_provider_context'; export default ({ getService }: FtrProviderContext) => { diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/runtime.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/runtime.ts index 20f2d08045c34..47e0a0ab3ff9c 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/runtime.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/runtime.ts @@ -17,7 +17,7 @@ import { getAlertsById, waitForRuleSuccess, waitForAlertsToBePresent, -} from '../../../utils'; +} from '../../../../../../common/utils/security_solution'; import { FtrProviderContext } from '../../../../../ftr_provider_context'; export default ({ getService }: FtrProviderContext) => { diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/timestamps.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/timestamps.ts index ba0c206c2f305..6ae4964ece45b 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/timestamps.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/timestamps.ts @@ -14,6 +14,7 @@ import { } from '@kbn/security-solution-plugin/common/api/detection_engine'; import { ALERT_ORIGINAL_TIME } from '@kbn/security-solution-plugin/common/field_maps/field_names'; +import { getOpenAlerts, getEqlRuleForAlertTesting } from '../../../utils'; import { createAlertsIndex, deleteAllRules, @@ -21,12 +22,10 @@ import { createRule, waitForRuleSuccess, waitForAlertsToBePresent, - getOpenAlerts, getRuleForAlertTesting, getAlertsByIds, - getEqlRuleForAlertTesting, waitForRulePartialFailure, -} from '../../../utils'; +} from '../../../../../../common/utils/security_solution'; import { FtrProviderContext } from '../../../../../ftr_provider_context'; import { EsArchivePathBuilder } from '../../../../../es_archive_path_builder'; diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/bundled_prebuilt_rules_package/trial_license_complete_tier/install_latest_bundled_prebuilt_rules.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/bundled_prebuilt_rules_package/trial_license_complete_tier/install_latest_bundled_prebuilt_rules.ts index dcb2561b1f3e8..cba12ecf33764 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/bundled_prebuilt_rules_package/trial_license_complete_tier/install_latest_bundled_prebuilt_rules.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/bundled_prebuilt_rules_package/trial_license_complete_tier/install_latest_bundled_prebuilt_rules.ts @@ -13,11 +13,11 @@ import { PackageSpecManifest } from '@kbn/fleet-plugin/common'; import { ALL_SAVED_OBJECT_INDICES } from '@kbn/core-saved-objects-server'; import { FtrProviderContext } from '../../../../../../ftr_provider_context'; import { - deleteAllRules, deleteAllPrebuiltRuleAssets, getPrebuiltRulesStatus, installPrebuiltRulesPackageByVersion, } from '../../../../utils'; +import { deleteAllRules } from '../../../../../../../common/utils/security_solution'; export default ({ getService }: FtrProviderContext): void => { const es = getService('es'); const supertest = getService('supertest'); diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/bundled_prebuilt_rules_package/trial_license_complete_tier/prerelease_packages.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/bundled_prebuilt_rules_package/trial_license_complete_tier/prerelease_packages.ts index 3ed663f7ecc66..fa3ffe093b5b4 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/bundled_prebuilt_rules_package/trial_license_complete_tier/prerelease_packages.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/bundled_prebuilt_rules_package/trial_license_complete_tier/prerelease_packages.ts @@ -9,7 +9,6 @@ import expect from 'expect'; import { FtrProviderContext } from '../../../../../../ftr_provider_context'; import { deleteAllPrebuiltRuleAssets, - deleteAllRules, deletePrebuiltRulesFleetPackage, getInstalledRules, getPrebuiltRulesFleetPackage, @@ -17,6 +16,7 @@ import { installPrebuiltRules, installPrebuiltRulesPackageViaFleetAPI, } from '../../../../utils'; +import { deleteAllRules } from '../../../../../../../common/utils/security_solution'; export default ({ getService }: FtrProviderContext): void => { const es = getService('es'); diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/large_prebuilt_rules_package/trial_license_complete_tier/install_large_prebuilt_rules_package.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/large_prebuilt_rules_package/trial_license_complete_tier/install_large_prebuilt_rules_package.ts index 0b625e7bbb82e..b34aae0b97a56 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/large_prebuilt_rules_package/trial_license_complete_tier/install_large_prebuilt_rules_package.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/large_prebuilt_rules_package/trial_license_complete_tier/install_large_prebuilt_rules_package.ts @@ -8,10 +8,10 @@ import expect from 'expect'; import { FtrProviderContext } from '../../../../../../ftr_provider_context'; import { deleteAllPrebuiltRuleAssets, - deleteAllRules, getPrebuiltRulesAndTimelinesStatus, installPrebuiltRulesAndTimelines, } from '../../../../utils'; +import { deleteAllRules } from '../../../../../../../common/utils/security_solution'; export default ({ getService }: FtrProviderContext): void => { const es = getService('es'); diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/management/trial_license_complete_tier/fleet_integration.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/management/trial_license_complete_tier/fleet_integration.ts index 1a8394a3b5144..19dcd4ba1aa37 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/management/trial_license_complete_tier/fleet_integration.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/management/trial_license_complete_tier/fleet_integration.ts @@ -7,10 +7,10 @@ import expect from 'expect'; import { FtrProviderContext } from '../../../../../../ftr_provider_context'; import { - deleteAllRules, getPrebuiltRulesAndTimelinesStatus, installPrebuiltRulesAndTimelines, } from '../../../../utils'; +import { deleteAllRules } from '../../../../../../../common/utils/security_solution'; import { deleteAllPrebuiltRuleAssets } from '../../../../utils/rules/prebuilt_rules/delete_all_prebuilt_rule_assets'; import { deleteAllTimelines } from '../../../../utils/rules/prebuilt_rules/delete_all_timelines'; import { deletePrebuiltRulesFleetPackage } from '../../../../utils/rules/prebuilt_rules/delete_prebuilt_rules_fleet_package'; diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/management/trial_license_complete_tier/get_prebuilt_rules_status.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/management/trial_license_complete_tier/get_prebuilt_rules_status.ts index 5f39065263afb..5ae15cd6fc3a1 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/management/trial_license_complete_tier/get_prebuilt_rules_status.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/management/trial_license_complete_tier/get_prebuilt_rules_status.ts @@ -9,19 +9,21 @@ import expect from 'expect'; import { FtrProviderContext } from '../../../../../../ftr_provider_context'; import { deleteAllPrebuiltRuleAssets, - deleteAllRules, getPrebuiltRulesStatus, - createRule, getSimpleRule, createRuleAssetSavedObject, createPrebuiltRuleAssetSavedObjects, installPrebuiltRules, - deleteRule, upgradePrebuiltRules, createHistoricalPrebuiltRuleAssetSavedObjects, getPrebuiltRulesAndTimelinesStatus, installPrebuiltRulesAndTimelines, } from '../../../../utils'; +import { + deleteAllRules, + createRule, + deleteRule, +} from '../../../../../../../common/utils/security_solution'; export default ({ getService }: FtrProviderContext): void => { const supertest = getService('supertest'); diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/management/trial_license_complete_tier/install_prebuilt_rules.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/management/trial_license_complete_tier/install_prebuilt_rules.ts index e9b8bbed84d1e..d9a99d50228d5 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/management/trial_license_complete_tier/install_prebuilt_rules.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/management/trial_license_complete_tier/install_prebuilt_rules.ts @@ -7,18 +7,17 @@ import expect from 'expect'; import { FtrProviderContext } from '../../../../../../ftr_provider_context'; import { - deleteAllRules, deleteAllTimelines, deleteAllPrebuiltRuleAssets, createRuleAssetSavedObject, createPrebuiltRuleAssetSavedObjects, installPrebuiltRulesAndTimelines, - deleteRule, getPrebuiltRulesAndTimelinesStatus, getPrebuiltRulesStatus, installPrebuiltRules, getInstalledRules, } from '../../../../utils'; +import { deleteAllRules, deleteRule } from '../../../../../../../common/utils/security_solution'; export default ({ getService }: FtrProviderContext): void => { const es = getService('es'); diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/management/trial_license_complete_tier/install_prebuilt_rules_with_historical_versions.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/management/trial_license_complete_tier/install_prebuilt_rules_with_historical_versions.ts index 6120caa8eda22..8b8b06f6b1519 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/management/trial_license_complete_tier/install_prebuilt_rules_with_historical_versions.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/management/trial_license_complete_tier/install_prebuilt_rules_with_historical_versions.ts @@ -7,18 +7,17 @@ import expect from 'expect'; import { FtrProviderContext } from '../../../../../../ftr_provider_context'; import { - deleteAllRules, deleteAllTimelines, deleteAllPrebuiltRuleAssets, createRuleAssetSavedObject, installPrebuiltRulesAndTimelines, - deleteRule, getPrebuiltRulesAndTimelinesStatus, createHistoricalPrebuiltRuleAssetSavedObjects, getPrebuiltRulesStatus, installPrebuiltRules, getInstalledRules, } from '../../../../utils'; +import { deleteAllRules, deleteRule } from '../../../../../../../common/utils/security_solution'; export default ({ getService }: FtrProviderContext): void => { const es = getService('es'); diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/management/trial_license_complete_tier/upgrade_prebuilt_rules.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/management/trial_license_complete_tier/upgrade_prebuilt_rules.ts index 5d1f9662e118a..73204855bcbf8 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/management/trial_license_complete_tier/upgrade_prebuilt_rules.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/management/trial_license_complete_tier/upgrade_prebuilt_rules.ts @@ -8,7 +8,6 @@ import expect from 'expect'; import { PRECONFIGURED_EMAIL_ACTION_CONNECTOR_ID } from '../../../../../../config/shared'; import { FtrProviderContext } from '../../../../../../ftr_provider_context'; import { - deleteAllRules, deleteAllTimelines, deleteAllPrebuiltRuleAssets, createRuleAssetSavedObject, @@ -21,6 +20,7 @@ import { fetchRule, patchRule, } from '../../../../utils'; +import { deleteAllRules } from '../../../../../../../common/utils/security_solution'; export default ({ getService }: FtrProviderContext): void => { const es = getService('es'); diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/management/trial_license_complete_tier/upgrade_prebuilt_rules_with_historical_versions.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/management/trial_license_complete_tier/upgrade_prebuilt_rules_with_historical_versions.ts index cd6ff46ecabb1..4137e2a9a194f 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/management/trial_license_complete_tier/upgrade_prebuilt_rules_with_historical_versions.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/management/trial_license_complete_tier/upgrade_prebuilt_rules_with_historical_versions.ts @@ -7,7 +7,6 @@ import expect from 'expect'; import { FtrProviderContext } from '../../../../../../ftr_provider_context'; import { - deleteAllRules, deleteAllTimelines, deleteAllPrebuiltRuleAssets, createRuleAssetSavedObject, @@ -18,6 +17,7 @@ import { installPrebuiltRules, upgradePrebuiltRules, } from '../../../../utils'; +import { deleteAllRules } from '../../../../../../../common/utils/security_solution'; export default ({ getService }: FtrProviderContext): void => { const es = getService('es'); diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/update_prebuilt_rules_package/trial_license_complete_tier/update_prebuilt_rules_package.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/update_prebuilt_rules_package/trial_license_complete_tier/update_prebuilt_rules_package.ts index ffba2bd01d988..21535741597d6 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/update_prebuilt_rules_package/trial_license_complete_tier/update_prebuilt_rules_package.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/update_prebuilt_rules_package/trial_license_complete_tier/update_prebuilt_rules_package.ts @@ -15,7 +15,6 @@ import { PackageSpecManifest } from '@kbn/fleet-plugin/common'; import { FtrProviderContext } from '../../../../../../ftr_provider_context'; import { deleteAllPrebuiltRuleAssets, - deleteAllRules, getInstalledRules, getPrebuiltRulesStatus, installPrebuiltRules, @@ -24,6 +23,7 @@ import { reviewPrebuiltRulesToInstall, reviewPrebuiltRulesToUpgrade, } from '../../../../utils'; +import { deleteAllRules } from '../../../../../../../common/utils/security_solution'; export default ({ getService }: FtrProviderContext): void => { const es = getService('es'); diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_bulk_actions/trial_license_complete_tier/perform_bulk_action.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_bulk_actions/trial_license_complete_tier/perform_bulk_action.ts index 04e895290c555..747426e0c0587 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_bulk_actions/trial_license_complete_tier/perform_bulk_action.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_bulk_actions/trial_license_complete_tier/perform_bulk_action.ts @@ -21,10 +21,6 @@ import { getCreateExceptionListItemMinimalSchemaMock } from '@kbn/lists-plugin/c import { WebhookAuthType } from '@kbn/stack-connectors-plugin/common/webhook/constants'; import { binaryToString, - createRule, - createAlertsIndex, - deleteAllRules, - deleteAllAlerts, getSimpleMlRule, getSimpleRule, getSimpleRuleOutput, @@ -34,6 +30,12 @@ import { removeServerGeneratedProperties, updateUsername, } from '../../../utils'; +import { + createRule, + createAlertsIndex, + deleteAllRules, + deleteAllAlerts, +} from '../../../../../../common/utils/security_solution'; import { deleteAllExceptions } from '../../../../lists_and_exception_lists/utils'; import { FtrProviderContext } from '../../../../../ftr_provider_context'; diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_bulk_actions/trial_license_complete_tier/perform_bulk_action_dry_run.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_bulk_actions/trial_license_complete_tier/perform_bulk_action_dry_run.ts index ad88064d1d2fe..5120c5f606c17 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_bulk_actions/trial_license_complete_tier/perform_bulk_action_dry_run.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_bulk_actions/trial_license_complete_tier/perform_bulk_action_dry_run.ts @@ -14,15 +14,13 @@ import { BulkActionTypeEnum, BulkActionEditTypeEnum, } from '@kbn/security-solution-plugin/common/api/detection_engine/rule_management'; +import { getSimpleMlRule, getSimpleRule, installMockPrebuiltRules } from '../../../utils'; import { createRule, createAlertsIndex, deleteAllRules, deleteAllAlerts, - getSimpleMlRule, - getSimpleRule, - installMockPrebuiltRules, -} from '../../../utils'; +} from '../../../../../../common/utils/security_solution'; import { FtrProviderContext } from '../../../../../ftr_provider_context'; export default ({ getService }: FtrProviderContext): void => { diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_bulk_actions/trial_license_complete_tier/perform_bulk_action_dry_run_ess.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_bulk_actions/trial_license_complete_tier/perform_bulk_action_dry_run_ess.ts index c44bc5acd58e5..50cd8cf6afc91 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_bulk_actions/trial_license_complete_tier/perform_bulk_action_dry_run_ess.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_bulk_actions/trial_license_complete_tier/perform_bulk_action_dry_run_ess.ts @@ -12,7 +12,12 @@ import { BulkActionTypeEnum, BulkActionEditTypeEnum, } from '@kbn/security-solution-plugin/common/api/detection_engine/rule_management'; -import { createRule, createAlertsIndex, deleteAllRules, deleteAllAlerts } from '../../../utils'; +import { + createRule, + createAlertsIndex, + deleteAllRules, + deleteAllAlerts, +} from '../../../../../../common/utils/security_solution'; import { FtrProviderContext } from '../../../../../ftr_provider_context'; export default ({ getService }: FtrProviderContext): void => { diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_bulk_actions/trial_license_complete_tier/perform_bulk_action_ess.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_bulk_actions/trial_license_complete_tier/perform_bulk_action_ess.ts index f260d3ed2683a..83a7db27fe3fe 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_bulk_actions/trial_license_complete_tier/perform_bulk_action_ess.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_bulk_actions/trial_license_complete_tier/perform_bulk_action_ess.ts @@ -21,20 +21,22 @@ import { import { binaryToString, createLegacyRuleAction, - createRule, - createAlertsIndex, - deleteAllRules, - deleteAllAlerts, getLegacyActionSO, getSimpleRule, getWebHookAction, - waitForRuleSuccess, - getRuleSOById, createRuleThroughAlertingEndpoint, getRuleSavedObjectWithLegacyInvestigationFields, getRuleSavedObjectWithLegacyInvestigationFieldsEmptyArray, checkInvestigationFieldSoValue, + getRuleSOById, } from '../../../utils'; +import { + createRule, + createAlertsIndex, + deleteAllRules, + deleteAllAlerts, + waitForRuleSuccess, +} from '../../../../../../common/utils/security_solution'; import { FtrProviderContext } from '../../../../../ftr_provider_context'; @@ -801,7 +803,7 @@ export default ({ getService }: FtrProviderContext): void => { /* It's duplicate of a rule with properly formatted "investigation fields". So we just check that "investigation fields" are in intended format. - No migration needs to happen. + No migration needs to happen. */ const isInvestigationFieldForRuleWithIntendedTypeInSo = await checkInvestigationFieldSoValue( @@ -839,7 +841,7 @@ export default ({ getService }: FtrProviderContext): void => { /* Since this rule was created with intended "investigation fields" format, - it shouldn't change - no need to migrate. + it shouldn't change - no need to migrate. */ const isInvestigationFieldForOriginalRuleWithIntendedTypeInSo = await checkInvestigationFieldSoValue( diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_creation/basic_license_essentials_tier/create_ml_rules_privileges.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_creation/basic_license_essentials_tier/create_ml_rules_privileges.ts index a9537d0426c01..fe89747eaa375 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_creation/basic_license_essentials_tier/create_ml_rules_privileges.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_creation/basic_license_essentials_tier/create_ml_rules_privileges.ts @@ -9,14 +9,12 @@ import expect from 'expect'; import { ELASTIC_HTTP_VERSION_HEADER } from '@kbn/core-http-common'; import { DETECTION_ENGINE_RULES_URL } from '@kbn/security-solution-plugin/common/constants'; +import { removeServerGeneratedProperties, getSimpleMlRule, updateUsername } from '../../../utils'; import { createAlertsIndex, deleteAllRules, - removeServerGeneratedProperties, - getSimpleMlRule, deleteAllAlerts, - updateUsername, -} from '../../../utils'; +} from '../../../../../../common/utils/security_solution'; import { FtrProviderContext } from '../../../../../ftr_provider_context'; import { EsArchivePathBuilder } from '../../../../../es_archive_path_builder'; diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_creation/basic_license_essentials_tier/create_rules.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_creation/basic_license_essentials_tier/create_rules.ts index 281fa37bb2d5d..7b9cc9e0f5d20 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_creation/basic_license_essentials_tier/create_rules.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_creation/basic_license_essentials_tier/create_rules.ts @@ -12,16 +12,19 @@ import { RuleCreateProps } from '@kbn/security-solution-plugin/common/api/detect import { DETECTION_ENGINE_RULES_URL } from '@kbn/security-solution-plugin/common/constants'; import { - createAlertsIndex, - deleteAllRules, getSimpleRule, getSimpleRuleOutputWithoutRuleId, getSimpleRuleWithoutRuleId, removeServerGeneratedProperties, removeServerGeneratedPropertiesIncludingRuleId, - deleteAllAlerts, updateUsername, + getSimpleRuleOutput, } from '../../../utils'; +import { + createAlertsIndex, + deleteAllRules, + deleteAllAlerts, +} from '../../../../../../common/utils/security_solution'; import { FtrProviderContext } from '../../../../../ftr_provider_context'; import { EsArchivePathBuilder } from '../../../../../es_archive_path_builder'; @@ -65,7 +68,7 @@ export default ({ getService }: FtrProviderContext) => { .expect(200); const bodyToCompare = removeServerGeneratedProperties(body); - const expectedRule = updateUsername(bodyToCompare, ELASTICSEARCH_USERNAME); + const expectedRule = updateUsername(getSimpleRuleOutput(), ELASTICSEARCH_USERNAME); expect(bodyToCompare).to.eql(expectedRule); }); @@ -90,7 +93,42 @@ export default ({ getService }: FtrProviderContext) => { .expect(200); const bodyToCompare = removeServerGeneratedProperties(body); - const expectedRule = updateUsername(bodyToCompare, ELASTICSEARCH_USERNAME); + const expectedRule = updateUsername( + { + actions: [], + author: [], + created_by: 'elastic', + description: 'Simple Rule Query', + enabled: true, + false_positives: [], + from: 'now-6m', + immutable: false, + interval: '5m', + rule_id: 'rule-1', + language: 'kuery', + output_index: '', + max_signals: 100, + risk_score: 1, + risk_score_mapping: [], + name: 'Simple Rule Query', + query: 'user.name: root or user.name: admin', + references: [], + related_integrations: [], + required_fields: [], + setup: '', + severity: 'high', + severity_mapping: [], + updated_by: 'elastic', + tags: [], + to: 'now', + type: 'query', + threat: [], + exceptions_list: [], + version: 1, + revision: 0, + }, + ELASTICSEARCH_USERNAME + ); expect(bodyToCompare).to.eql(expectedRule); }); diff --git a/x-pack/test/detection_engine_api_integration/basic/tests/create_rules_bulk.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_creation/basic_license_essentials_tier/create_rules_bulk.ts similarity index 74% rename from x-pack/test/detection_engine_api_integration/basic/tests/create_rules_bulk.ts rename to x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_creation/basic_license_essentials_tier/create_rules_bulk.ts index d23d92e2887a2..a36b14bab5667 100644 --- a/x-pack/test/detection_engine_api_integration/basic/tests/create_rules_bulk.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_creation/basic_license_essentials_tier/create_rules_bulk.ts @@ -8,38 +8,47 @@ import expect from '@kbn/expect'; import { DETECTION_ENGINE_RULES_BULK_CREATE } from '@kbn/security-solution-plugin/common/constants'; -import { FtrProviderContext } from '../../common/ftr_provider_context'; +import { EsArchivePathBuilder } from '../../../../../es_archive_path_builder'; +import { FtrProviderContext } from '../../../../../ftr_provider_context'; import { - createSignalsIndex, - deleteAllRules, - deleteAllAlerts, getSimpleRule, getSimpleRuleOutput, getSimpleRuleOutputWithoutRuleId, getSimpleRuleWithoutRuleId, removeServerGeneratedProperties, removeServerGeneratedPropertiesIncludingRuleId, -} from '../../utils'; + updateUsername, +} from '../../../utils'; +import { + createAlertsIndex, + deleteAllRules, + deleteAllAlerts, +} from '../../../../../../common/utils/security_solution'; -// eslint-disable-next-line import/no-default-export export default ({ getService }: FtrProviderContext): void => { const esArchiver = getService('esArchiver'); const supertest = getService('supertest'); const log = getService('log'); const es = getService('es'); - - describe('create_rules_bulk', () => { + // TODO: add a new service for loading archiver files similar to "getService('es')" + const config = getService('config'); + const ELASTICSEARCH_USERNAME = config.get('servers.kibana.username'); + const isServerless = config.get('serverless'); + const dataPathBuilder = new EsArchivePathBuilder(isServerless); + const auditbeatPath = dataPathBuilder.getPath('auditbeat/hosts'); + + describe('@ess @serverless create_rules_bulk', () => { describe('creating rules in bulk', () => { before(async () => { - await esArchiver.load('x-pack/test/functional/es_archives/auditbeat/hosts'); + await esArchiver.load(auditbeatPath); }); after(async () => { - await esArchiver.unload('x-pack/test/functional/es_archives/auditbeat/hosts'); + await esArchiver.unload(auditbeatPath); }); beforeEach(async () => { - await createSignalsIndex(supertest, log); + await createAlertsIndex(supertest, log); }); afterEach(async () => { @@ -56,7 +65,9 @@ export default ({ getService }: FtrProviderContext): void => { .expect(200); const bodyToCompare = removeServerGeneratedProperties(body[0]); - expect(bodyToCompare).to.eql(getSimpleRuleOutput()); + const expectedRule = updateUsername(getSimpleRuleOutput(), ELASTICSEARCH_USERNAME); + + expect(bodyToCompare).to.eql(expectedRule); }); it('should create a single rule without a rule_id', async () => { @@ -68,7 +79,12 @@ export default ({ getService }: FtrProviderContext): void => { .expect(200); const bodyToCompare = removeServerGeneratedPropertiesIncludingRuleId(body[0]); - expect(bodyToCompare).to.eql(getSimpleRuleOutputWithoutRuleId()); + const expectedRule = updateUsername( + getSimpleRuleOutputWithoutRuleId(), + ELASTICSEARCH_USERNAME + ); + + expect(bodyToCompare).to.eql(expectedRule); }); it('should return a 200 ok but have a 409 conflict if we attempt to create the same rule_id twice', async () => { diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_creation/basic_license_essentials_tier/index.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_creation/basic_license_essentials_tier/index.ts index aa21da6d74cc7..9a2d3b8256c2f 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_creation/basic_license_essentials_tier/index.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_creation/basic_license_essentials_tier/index.ts @@ -9,6 +9,7 @@ import { FtrProviderContext } from '../../../../../ftr_provider_context'; export default function ({ loadTestFile }: FtrProviderContext) { describe('Rules Management - Rule Creation APIs', function () { loadTestFile(require.resolve('./create_rules')); + loadTestFile(require.resolve('./create_rules_bulk')); loadTestFile(require.resolve('./create_ml_rules_privileges')); }); } diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_creation/trial_license_complete_tier/create_new_terms.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_creation/trial_license_complete_tier/create_new_terms.ts index 78c8467356612..9b8c41c9ef841 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_creation/trial_license_complete_tier/create_new_terms.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_creation/trial_license_complete_tier/create_new_terms.ts @@ -10,7 +10,7 @@ import expect from '@kbn/expect'; import { DETECTION_ENGINE_RULES_URL } from '@kbn/security-solution-plugin/common/constants'; import { getCreateNewTermsRulesSchemaMock } from '@kbn/security-solution-plugin/common/api/detection_engine/model/rule_schema/mocks'; -import { deleteAllRules } from '../../../utils'; +import { deleteAllRules } from '../../../../../../common/utils/security_solution'; import { FtrProviderContext } from '../../../../../ftr_provider_context'; export default ({ getService }: FtrProviderContext) => { diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_creation/trial_license_complete_tier/create_rules.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_creation/trial_license_complete_tier/create_rules.ts index e1bbdbe26e3dd..91fcf8e4f1a94 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_creation/trial_license_complete_tier/create_rules.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_creation/trial_license_complete_tier/create_rules.ts @@ -19,12 +19,6 @@ import { ROLES } from '@kbn/security-solution-plugin/common/test'; import { FtrProviderContext } from '../../../../../ftr_provider_context'; import { - deleteAllRules, - waitForRuleSuccess, - waitForAlertToComplete, - waitForAlertsToBePresent, - waitForRulePartialFailure, - deleteAllAlerts, getActionsWithFrequencies, getActionsWithoutFrequencies, getSomeActionsWithFrequencies, @@ -34,7 +28,15 @@ import { getThresholdRuleParams, generateEvent, fetchRule, + waitForAlertToComplete, } from '../../../utils'; +import { + deleteAllRules, + waitForRuleSuccess, + waitForAlertsToBePresent, + waitForRulePartialFailure, + deleteAllAlerts, +} from '../../../../../../common/utils/security_solution'; import { createUserAndRole, deleteUserAndRole, diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_creation/trial_license_complete_tier/create_rules_bulk.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_creation/trial_license_complete_tier/create_rules_bulk.ts index 2319c3d0ddf82..c762487b8f278 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_creation/trial_license_complete_tier/create_rules_bulk.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_creation/trial_license_complete_tier/create_rules_bulk.ts @@ -18,22 +18,24 @@ import { RuleCreateProps } from '@kbn/security-solution-plugin/common/api/detect import { ExceptionListTypeEnum } from '@kbn/securitysolution-io-ts-list-types'; import { - createAlertsIndex, - deleteAllRules, - deleteAllAlerts, - getRuleForAlertTesting, getSimpleRule, getSimpleRuleOutput, getSimpleRuleOutputWithoutRuleId, getSimpleRuleWithoutRuleId, removeServerGeneratedProperties, removeServerGeneratedPropertiesIncludingRuleId, - waitForRuleSuccess, getActionsWithFrequencies, getActionsWithoutFrequencies, getSomeActionsWithFrequencies, removeUUIDFromActions, } from '../../../utils'; +import { + createAlertsIndex, + deleteAllRules, + deleteAllAlerts, + getRuleForAlertTesting, + waitForRuleSuccess, +} from '../../../../../../common/utils/security_solution'; import { FtrProviderContext } from '../../../../../ftr_provider_context'; export default ({ getService }: FtrProviderContext): void => { diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_creation/trial_license_complete_tier/preview_rules.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_creation/trial_license_complete_tier/preview_rules.ts index cf723f5cf2779..43a04eef3cb69 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_creation/trial_license_complete_tier/preview_rules.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_creation/trial_license_complete_tier/preview_rules.ts @@ -9,7 +9,8 @@ import expect from '@kbn/expect'; import { DETECTION_ENGINE_RULES_PREVIEW } from '@kbn/security-solution-plugin/common/constants'; import { ROLES } from '@kbn/security-solution-plugin/common/test'; -import { deleteAllRules, getSimplePreviewRule, getSimpleRulePreviewOutput } from '../../../utils'; +import { getSimplePreviewRule, getSimpleRulePreviewOutput } from '../../../utils'; +import { deleteAllRules } from '../../../../../../common/utils/security_solution'; import { createUserAndRole, diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_delete/basic_license_essentials_tier/configs/ess.config.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_delete/basic_license_essentials_tier/configs/ess.config.ts new file mode 100644 index 0000000000000..05eaaabfd740e --- /dev/null +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_delete/basic_license_essentials_tier/configs/ess.config.ts @@ -0,0 +1,22 @@ +/* + * 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 { FtrConfigProviderContext } from '@kbn/test'; + +export default async function ({ readConfigFile }: FtrConfigProviderContext) { + const functionalConfig = await readConfigFile( + require.resolve('../../../../../../config/ess/config.base.basic') + ); + + return { + ...functionalConfig.getAll(), + testFiles: [require.resolve('..')], + junit: { + reportName: 'Rules Management - Rule Delete Integration Tests - ESS Env - Basic License', + }, + }; +} diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_delete/basic_license_essentials_tier/configs/serverless.config.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_delete/basic_license_essentials_tier/configs/serverless.config.ts new file mode 100644 index 0000000000000..430b838dc76a8 --- /dev/null +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_delete/basic_license_essentials_tier/configs/serverless.config.ts @@ -0,0 +1,16 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { createTestConfig } from '../../../../../../config/serverless/config.base.essentials'; + +export default createTestConfig({ + testFiles: [require.resolve('..')], + junit: { + reportName: + 'Rules Management - Rule Delete Integration Tests - Serverless Env - Essentials Tier ', + }, +}); diff --git a/x-pack/test/detection_engine_api_integration/basic/tests/delete_rules.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_delete/basic_license_essentials_tier/delete_rules.ts similarity index 79% rename from x-pack/test/detection_engine_api_integration/basic/tests/delete_rules.ts rename to x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_delete/basic_license_essentials_tier/delete_rules.ts index b649b16d7ef02..944a33dc1a75d 100644 --- a/x-pack/test/detection_engine_api_integration/basic/tests/delete_rules.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_delete/basic_license_essentials_tier/delete_rules.ts @@ -8,30 +8,34 @@ import expect from '@kbn/expect'; import { DETECTION_ENGINE_RULES_URL } from '@kbn/security-solution-plugin/common/constants'; -import { FtrProviderContext } from '../../common/ftr_provider_context'; +import { FtrProviderContext } from '../../../../../ftr_provider_context'; import { - createRule, - createSignalsIndex, - deleteAllRules, - deleteAllAlerts, getSimpleRule, getSimpleRuleOutput, getSimpleRuleOutputWithoutRuleId, getSimpleRuleWithoutRuleId, removeServerGeneratedProperties, removeServerGeneratedPropertiesIncludingRuleId, -} from '../../utils'; + updateUsername, +} from '../../../utils'; +import { + createRule, + createAlertsIndex, + deleteAllRules, + deleteAllAlerts, +} from '../../../../../../common/utils/security_solution'; -// eslint-disable-next-line import/no-default-export export default ({ getService }: FtrProviderContext): void => { const supertest = getService('supertest'); const log = getService('log'); const es = getService('es'); + const config = getService('config'); + const ELASTICSEARCH_USERNAME = config.get('servers.kibana.username'); - describe('delete_rules', () => { + describe('@ess @serverless delete_rules', () => { describe('deleting rules', () => { beforeEach(async () => { - await createSignalsIndex(supertest, log); + await createAlertsIndex(supertest, log); }); afterEach(async () => { @@ -50,7 +54,9 @@ export default ({ getService }: FtrProviderContext): void => { .expect(200); const bodyToCompare = removeServerGeneratedProperties(body); - expect(bodyToCompare).to.eql(getSimpleRuleOutput()); + const expectedRule = updateUsername(getSimpleRuleOutput(), ELASTICSEARCH_USERNAME); + + expect(bodyToCompare).to.eql(expectedRule); }); it('should delete a single rule using an auto generated rule_id', async () => { @@ -64,7 +70,12 @@ export default ({ getService }: FtrProviderContext): void => { .expect(200); const bodyToCompare = removeServerGeneratedPropertiesIncludingRuleId(body); - expect(bodyToCompare).to.eql(getSimpleRuleOutputWithoutRuleId()); + const expectedRule = updateUsername( + getSimpleRuleOutputWithoutRuleId(), + ELASTICSEARCH_USERNAME + ); + + expect(bodyToCompare).to.eql(expectedRule); }); it('should delete a single rule using an auto generated id', async () => { @@ -78,7 +89,12 @@ export default ({ getService }: FtrProviderContext): void => { .expect(200); const bodyToCompare = removeServerGeneratedPropertiesIncludingRuleId(body); - expect(bodyToCompare).to.eql(getSimpleRuleOutputWithoutRuleId()); + const expectedRule = updateUsername( + getSimpleRuleOutputWithoutRuleId(), + ELASTICSEARCH_USERNAME + ); + + expect(bodyToCompare).to.eql(expectedRule); }); it('should return an error if the id does not exist when trying to delete it', async () => { diff --git a/x-pack/test/detection_engine_api_integration/basic/tests/delete_rules_bulk.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_delete/basic_license_essentials_tier/delete_rules_bulk.ts similarity index 83% rename from x-pack/test/detection_engine_api_integration/basic/tests/delete_rules_bulk.ts rename to x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_delete/basic_license_essentials_tier/delete_rules_bulk.ts index c2017aead47d8..771573b3e7c70 100644 --- a/x-pack/test/detection_engine_api_integration/basic/tests/delete_rules_bulk.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_delete/basic_license_essentials_tier/delete_rules_bulk.ts @@ -8,30 +8,34 @@ import expect from '@kbn/expect'; import { DETECTION_ENGINE_RULES_BULK_DELETE } from '@kbn/security-solution-plugin/common/constants'; -import { FtrProviderContext } from '../../common/ftr_provider_context'; +import { FtrProviderContext } from '../../../../../ftr_provider_context'; import { - createRule, - createSignalsIndex, - deleteAllRules, - deleteAllAlerts, getSimpleRule, getSimpleRuleOutput, getSimpleRuleOutputWithoutRuleId, getSimpleRuleWithoutRuleId, removeServerGeneratedProperties, removeServerGeneratedPropertiesIncludingRuleId, -} from '../../utils'; + updateUsername, +} from '../../../utils'; +import { + createRule, + createAlertsIndex, + deleteAllRules, + deleteAllAlerts, +} from '../../../../../../common/utils/security_solution'; -// eslint-disable-next-line import/no-default-export export default ({ getService }: FtrProviderContext): void => { const supertest = getService('supertest'); const log = getService('log'); const es = getService('es'); + const config = getService('config'); + const ELASTICSEARCH_USERNAME = config.get('servers.kibana.username'); - describe('delete_rules_bulk', () => { + describe('@ess @serverless delete_rules_bulk', () => { describe('deleting rules bulk using DELETE', () => { beforeEach(async () => { - await createSignalsIndex(supertest, log); + await createAlertsIndex(supertest, log); }); afterEach(async () => { @@ -51,7 +55,9 @@ export default ({ getService }: FtrProviderContext): void => { .expect(200); const bodyToCompare = removeServerGeneratedProperties(body[0]); - expect(bodyToCompare).to.eql(getSimpleRuleOutput()); + const expectedRule = updateUsername(getSimpleRuleOutput(), ELASTICSEARCH_USERNAME); + + expect(bodyToCompare).to.eql(expectedRule); }); it('should delete a single rule using an auto generated rule_id', async () => { @@ -66,7 +72,12 @@ export default ({ getService }: FtrProviderContext): void => { .expect(200); const bodyToCompare = removeServerGeneratedPropertiesIncludingRuleId(body[0]); - expect(bodyToCompare).to.eql(getSimpleRuleOutputWithoutRuleId()); + const expectedRule = updateUsername( + getSimpleRuleOutputWithoutRuleId(), + ELASTICSEARCH_USERNAME + ); + + expect(bodyToCompare).to.eql(expectedRule); }); it('should delete a single rule using an auto generated id', async () => { @@ -81,7 +92,12 @@ export default ({ getService }: FtrProviderContext): void => { .expect(200); const bodyToCompare = removeServerGeneratedPropertiesIncludingRuleId(body[0]); - expect(bodyToCompare).to.eql(getSimpleRuleOutputWithoutRuleId()); + const expectedRule = updateUsername( + getSimpleRuleOutputWithoutRuleId(), + ELASTICSEARCH_USERNAME + ); + + expect(bodyToCompare).to.eql(expectedRule); }); it('should return an error if the ruled_id does not exist when trying to delete a rule_id', async () => { @@ -133,8 +149,13 @@ export default ({ getService }: FtrProviderContext): void => { .expect(200); const bodyToCompare = removeServerGeneratedPropertiesIncludingRuleId(body[0]); - expect([bodyToCompare, body[1]]).to.eql([ + const expectedRule = updateUsername( getSimpleRuleOutputWithoutRuleId(), + ELASTICSEARCH_USERNAME + ); + + expect([bodyToCompare, body[1]]).to.eql([ + expectedRule, { id: 'c4e80a0d-e20f-4efc-84c1-08112da5a612', error: { @@ -149,7 +170,7 @@ export default ({ getService }: FtrProviderContext): void => { // This is a repeat of the tests above but just using POST instead of DELETE describe('deleting rules bulk using POST', () => { beforeEach(async () => { - await createSignalsIndex(supertest, log); + await createAlertsIndex(supertest, log); }); afterEach(async () => { @@ -169,7 +190,9 @@ export default ({ getService }: FtrProviderContext): void => { .expect(200); const bodyToCompare = removeServerGeneratedProperties(body[0]); - expect(bodyToCompare).to.eql(getSimpleRuleOutput()); + const expectedRule = updateUsername(getSimpleRuleOutput(), ELASTICSEARCH_USERNAME); + + expect(bodyToCompare).to.eql(expectedRule); }); it('should delete a single rule using an auto generated rule_id', async () => { @@ -184,7 +207,12 @@ export default ({ getService }: FtrProviderContext): void => { .expect(200); const bodyToCompare = removeServerGeneratedPropertiesIncludingRuleId(body[0]); - expect(bodyToCompare).to.eql(getSimpleRuleOutputWithoutRuleId()); + const expectedRule = updateUsername( + getSimpleRuleOutputWithoutRuleId(), + ELASTICSEARCH_USERNAME + ); + + expect(bodyToCompare).to.eql(expectedRule); }); it('should delete a single rule using an auto generated id', async () => { @@ -199,7 +227,12 @@ export default ({ getService }: FtrProviderContext): void => { .expect(200); const bodyToCompare = removeServerGeneratedPropertiesIncludingRuleId(body[0]); - expect(bodyToCompare).to.eql(getSimpleRuleOutputWithoutRuleId()); + const expectedRule = updateUsername( + getSimpleRuleOutputWithoutRuleId(), + ELASTICSEARCH_USERNAME + ); + + expect(bodyToCompare).to.eql(expectedRule); }); it('should return an error if the ruled_id does not exist when trying to delete a rule_id', async () => { @@ -251,8 +284,13 @@ export default ({ getService }: FtrProviderContext): void => { .expect(200); const bodyToCompare = removeServerGeneratedPropertiesIncludingRuleId(body[0]); - expect([bodyToCompare, body[1]]).to.eql([ + const expectedRule = updateUsername( getSimpleRuleOutputWithoutRuleId(), + ELASTICSEARCH_USERNAME + ); + + expect([bodyToCompare, body[1]]).to.eql([ + expectedRule, { id: 'c4e80a0d-e20f-4efc-84c1-08112da5a612', error: { diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_delete/basic_license_essentials_tier/index.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_delete/basic_license_essentials_tier/index.ts new file mode 100644 index 0000000000000..db32a616e20de --- /dev/null +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_delete/basic_license_essentials_tier/index.ts @@ -0,0 +1,15 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { FtrProviderContext } from '../../../../../ftr_provider_context'; + +export default function ({ loadTestFile }: FtrProviderContext) { + describe('Rules Management - Rule Delete API', function () { + loadTestFile(require.resolve('./delete_rules')); + loadTestFile(require.resolve('./delete_rules_bulk')); + }); +} diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_delete/trial_license_complete_tier/delete_rules.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_delete/trial_license_complete_tier/delete_rules.ts index 09ed13dc483b1..350cb550f6075 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_delete/trial_license_complete_tier/delete_rules.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_delete/trial_license_complete_tier/delete_rules.ts @@ -8,10 +8,6 @@ import expect from '@kbn/expect'; import { DETECTION_ENGINE_RULES_URL } from '@kbn/security-solution-plugin/common/constants'; import { - createRule, - createAlertsIndex, - deleteAllRules, - deleteAllAlerts, getSimpleRule, getSimpleRuleOutput, getSimpleRuleOutputWithoutRuleId, @@ -20,6 +16,12 @@ import { removeServerGeneratedPropertiesIncludingRuleId, updateUsername, } from '../../../utils'; +import { + createRule, + createAlertsIndex, + deleteAllRules, + deleteAllAlerts, +} from '../../../../../../common/utils/security_solution'; import { FtrProviderContext } from '../../../../../ftr_provider_context'; diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_delete/trial_license_complete_tier/delete_rules_bulk.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_delete/trial_license_complete_tier/delete_rules_bulk.ts index 7a4d4e4df9c56..c02a11f6ec5a4 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_delete/trial_license_complete_tier/delete_rules_bulk.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_delete/trial_license_complete_tier/delete_rules_bulk.ts @@ -11,10 +11,6 @@ import { BaseRuleParams } from '@kbn/security-solution-plugin/server/lib/detecti import { DETECTION_ENGINE_RULES_BULK_DELETE } from '@kbn/security-solution-plugin/common/constants'; import { RuleResponse } from '@kbn/security-solution-plugin/common/api/detection_engine'; import { - createRule, - createAlertsIndex, - deleteAllRules, - deleteAllAlerts, getSimpleRule, getSimpleRuleOutput, updateUsername, @@ -26,6 +22,12 @@ import { getRuleSavedObjectWithLegacyInvestigationFields, getRuleSavedObjectWithLegacyInvestigationFieldsEmptyArray, } from '../../../utils'; +import { + createRule, + createAlertsIndex, + deleteAllRules, + deleteAllAlerts, +} from '../../../../../../common/utils/security_solution'; import { FtrProviderContext } from '../../../../../ftr_provider_context'; export default ({ getService }: FtrProviderContext): void => { diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_delete/trial_license_complete_tier/delete_rules_bulk_legacy.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_delete/trial_license_complete_tier/delete_rules_bulk_legacy.ts index 7af966e8a0dfe..c64eeeb402f61 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_delete/trial_license_complete_tier/delete_rules_bulk_legacy.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_delete/trial_license_complete_tier/delete_rules_bulk_legacy.ts @@ -10,15 +10,17 @@ import { BASE_ALERTING_API_PATH } from '@kbn/alerting-plugin/common'; import { DETECTION_ENGINE_RULES_BULK_DELETE } from '@kbn/security-solution-plugin/common/constants'; import { createLegacyRuleAction, - createRule, - createAlertsIndex, - deleteAllRules, - deleteAllAlerts, getSimpleRule, getSlackAction, getWebHookAction, getLegacyActionSO, } from '../../../utils'; +import { + createRule, + createAlertsIndex, + deleteAllRules, + deleteAllAlerts, +} from '../../../../../../common/utils/security_solution'; import { FtrProviderContext } from '../../../../../ftr_provider_context'; export default ({ getService }: FtrProviderContext): void => { diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_delete/trial_license_complete_tier/delete_rules_ess.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_delete/trial_license_complete_tier/delete_rules_ess.ts index 784f083cbb75b..05a516df7e163 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_delete/trial_license_complete_tier/delete_rules_ess.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_delete/trial_license_complete_tier/delete_rules_ess.ts @@ -10,16 +10,18 @@ import { Rule } from '@kbn/alerting-plugin/common'; import { BaseRuleParams } from '@kbn/security-solution-plugin/server/lib/detection_engine/rule_schema'; import { DETECTION_ENGINE_RULES_URL } from '@kbn/security-solution-plugin/common/constants'; import { - createRule, - createAlertsIndex, - deleteAllRules, - deleteAllAlerts, getSimpleRule, removeServerGeneratedProperties, createRuleThroughAlertingEndpoint, getRuleSavedObjectWithLegacyInvestigationFields, getRuleSavedObjectWithLegacyInvestigationFieldsEmptyArray, } from '../../../utils'; +import { + createRule, + createAlertsIndex, + deleteAllRules, + deleteAllAlerts, +} from '../../../../../../common/utils/security_solution'; import { FtrProviderContext } from '../../../../../ftr_provider_context'; diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_delete/trial_license_complete_tier/delete_rules_legacy.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_delete/trial_license_complete_tier/delete_rules_legacy.ts index 42c3d633a003d..ba17cbd1254b6 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_delete/trial_license_complete_tier/delete_rules_legacy.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_delete/trial_license_complete_tier/delete_rules_legacy.ts @@ -10,16 +10,17 @@ import { BASE_ALERTING_API_PATH } from '@kbn/alerting-plugin/common'; import { DETECTION_ENGINE_RULES_URL } from '@kbn/security-solution-plugin/common/constants'; import { createLegacyRuleAction, - createRule, - createAlertsIndex, - deleteAllRules, - deleteAllAlerts, getSimpleRule, getSlackAction, getWebHookAction, getLegacyActionSO, } from '../../../utils'; - +import { + createRule, + createAlertsIndex, + deleteAllRules, + deleteAllAlerts, +} from '../../../../../../common/utils/security_solution'; import { FtrProviderContext } from '../../../../../ftr_provider_context'; export default ({ getService }: FtrProviderContext): void => { diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_import_export/basic_license_essentials_tier/configs/ess.config.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_import_export/basic_license_essentials_tier/configs/ess.config.ts new file mode 100644 index 0000000000000..c71cfab3cc9fd --- /dev/null +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_import_export/basic_license_essentials_tier/configs/ess.config.ts @@ -0,0 +1,22 @@ +/* + * 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 { FtrConfigProviderContext } from '@kbn/test'; + +export default async function ({ readConfigFile }: FtrConfigProviderContext) { + const functionalConfig = await readConfigFile( + require.resolve('../../../../../../config/ess/config.base.basic') + ); + + return { + ...functionalConfig.getAll(), + testFiles: [require.resolve('..')], + junit: { + reportName: 'Rules Management - Rule Patch Integration Tests - ESS Env - Basic License', + }, + }; +} diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_import_export/basic_license_essentials_tier/configs/serverless.config.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_import_export/basic_license_essentials_tier/configs/serverless.config.ts new file mode 100644 index 0000000000000..de3421e190305 --- /dev/null +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_import_export/basic_license_essentials_tier/configs/serverless.config.ts @@ -0,0 +1,16 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { createTestConfig } from '../../../../../../config/serverless/config.base.essentials'; + +export default createTestConfig({ + testFiles: [require.resolve('..')], + junit: { + reportName: + 'Rules Management - Rule Patch Integration Tests - Serverless Env - Essentials Tier ', + }, +}); diff --git a/x-pack/test/detection_engine_api_integration/basic/tests/export_rules.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_import_export/basic_license_essentials_tier/export_rules.ts similarity index 82% rename from x-pack/test/detection_engine_api_integration/basic/tests/export_rules.ts rename to x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_import_export/basic_license_essentials_tier/export_rules.ts index 883c9adcc7ad0..67d48610d4e8f 100644 --- a/x-pack/test/detection_engine_api_integration/basic/tests/export_rules.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_import_export/basic_license_essentials_tier/export_rules.ts @@ -8,28 +8,31 @@ import expect from '@kbn/expect'; import { DETECTION_ENGINE_RULES_URL } from '@kbn/security-solution-plugin/common/constants'; -import { FtrProviderContext } from '../../common/ftr_provider_context'; +import { FtrProviderContext } from '../../../../../ftr_provider_context'; import { binaryToString, - createRule, - createSignalsIndex, - deleteAllRules, - deleteAllAlerts, getSimpleRule, getSimpleRuleOutput, removeServerGeneratedProperties, -} from '../../utils'; - -// eslint-disable-next-line import/no-default-export + updateUsername, +} from '../../../utils'; +import { + createRule, + createAlertsIndex, + deleteAllRules, + deleteAllAlerts, +} from '../../../../../../common/utils/security_solution'; export default ({ getService }: FtrProviderContext): void => { const supertest = getService('supertest'); const log = getService('log'); const es = getService('es'); + const config = getService('config'); + const ELASTICSEARCH_USERNAME = config.get('servers.kibana.username'); - describe('export_rules', () => { + describe('@ess @serverless export_rules', () => { describe('exporting rules', () => { beforeEach(async () => { - await createSignalsIndex(supertest, log); + await createAlertsIndex(supertest, log); }); afterEach(async () => { @@ -63,8 +66,9 @@ export default ({ getService }: FtrProviderContext): void => { const bodySplitAndParsed = JSON.parse(body.toString().split(/\n/)[0]); const bodyToTest = removeServerGeneratedProperties(bodySplitAndParsed); + const expectedRule = updateUsername(getSimpleRuleOutput(), ELASTICSEARCH_USERNAME); - expect(bodyToTest).to.eql(getSimpleRuleOutput()); + expect(bodyToTest).to.eql(expectedRule); }); it('should export a exported count with a single rule_id', async () => { @@ -115,11 +119,10 @@ export default ({ getService }: FtrProviderContext): void => { const secondRuleParsed = JSON.parse(body.toString().split(/\n/)[1]); const firstRule = removeServerGeneratedProperties(firstRuleParsed); const secondRule = removeServerGeneratedProperties(secondRuleParsed); + const expectedRule = updateUsername(getSimpleRuleOutput('rule-2'), ELASTICSEARCH_USERNAME); + const expectedRule2 = updateUsername(getSimpleRuleOutput('rule-1'), ELASTICSEARCH_USERNAME); - expect([firstRule, secondRule]).to.eql([ - getSimpleRuleOutput('rule-2'), - getSimpleRuleOutput('rule-1'), - ]); + expect([firstRule, secondRule]).to.eql([expectedRule, expectedRule2]); }); }); }); diff --git a/x-pack/test/detection_engine_api_integration/basic/tests/import_rules.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_import_export/basic_license_essentials_tier/import_rules.ts similarity index 94% rename from x-pack/test/detection_engine_api_integration/basic/tests/import_rules.ts rename to x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_import_export/basic_license_essentials_tier/import_rules.ts index bf22875e23712..5bb8fd306bd04 100644 --- a/x-pack/test/detection_engine_api_integration/basic/tests/import_rules.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_import_export/basic_license_essentials_tier/import_rules.ts @@ -8,28 +8,32 @@ import expect from '@kbn/expect'; import { DETECTION_ENGINE_RULES_URL } from '@kbn/security-solution-plugin/common/constants'; -import { FtrProviderContext } from '../../common/ftr_provider_context'; +import { FtrProviderContext } from '../../../../../ftr_provider_context'; import { - createSignalsIndex, - deleteAllRules, - deleteAllAlerts, getSimpleRule, getSimpleRuleAsNdjson, getSimpleRuleOutput, removeServerGeneratedProperties, ruleToNdjson, -} from '../../utils'; + updateUsername, +} from '../../../utils'; +import { + createAlertsIndex, + deleteAllRules, + deleteAllAlerts, +} from '../../../../../../common/utils/security_solution'; -// eslint-disable-next-line import/no-default-export export default ({ getService }: FtrProviderContext): void => { const supertest = getService('supertest'); const log = getService('log'); const es = getService('es'); + const config = getService('config'); + const ELASTICSEARCH_USERNAME = config.get('servers.kibana.username'); - describe('import_rules', () => { + describe('@ess @serverless import_rules', () => { describe('importing rules with an index', () => { beforeEach(async () => { - await createSignalsIndex(supertest, log); + await createAlertsIndex(supertest, log); }); afterEach(async () => { @@ -99,10 +103,15 @@ export default ({ getService }: FtrProviderContext): void => { .expect(200); const bodyToCompare = removeServerGeneratedProperties(body); - expect(bodyToCompare).to.eql({ - ...getSimpleRuleOutput('rule-1', false), - output_index: '', - }); + const expectedRule = updateUsername( + { + ...getSimpleRuleOutput('rule-1', false), + output_index: '', + }, + ELASTICSEARCH_USERNAME + ); + + expect(bodyToCompare).to.eql(expectedRule); }); it('should fail validation when importing a rule with malformed "from" params on the rules', async () => { @@ -381,7 +390,9 @@ export default ({ getService }: FtrProviderContext): void => { }; ruleOutput.name = 'some other name'; ruleOutput.revision = 0; - expect(bodyToCompare).to.eql(ruleOutput); + const expectedRule = updateUsername(ruleOutput, ELASTICSEARCH_USERNAME); + + expect(bodyToCompare).to.eql(expectedRule); }); it('should report a conflict if there is an attempt to import a rule with a rule_id that already exists, but still have some successes with other rules', async () => { @@ -507,11 +518,14 @@ export default ({ getService }: FtrProviderContext): void => { const bodyToCompareOfRule1 = removeServerGeneratedProperties(bodyOfRule1); const bodyToCompareOfRule2 = removeServerGeneratedProperties(bodyOfRule2); const bodyToCompareOfRule3 = removeServerGeneratedProperties(bodyOfRule3); + const expectedRule = updateUsername(getRuleOutput('rule-1'), ELASTICSEARCH_USERNAME); + const expectedRule2 = updateUsername(getRuleOutput('rule-2'), ELASTICSEARCH_USERNAME); + const expectedRule3 = updateUsername(getRuleOutput('rule-3'), ELASTICSEARCH_USERNAME); expect([bodyToCompareOfRule1, bodyToCompareOfRule2, bodyToCompareOfRule3]).to.eql([ - getRuleOutput('rule-1'), - getRuleOutput('rule-2'), - getRuleOutput('rule-3'), + expectedRule, + expectedRule2, + expectedRule3, ]); }); }); diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_import_export/basic_license_essentials_tier/index.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_import_export/basic_license_essentials_tier/index.ts new file mode 100644 index 0000000000000..7d91e7c455b58 --- /dev/null +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_import_export/basic_license_essentials_tier/index.ts @@ -0,0 +1,15 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { FtrProviderContext } from '../../../../../ftr_provider_context'; + +export default function ({ loadTestFile }: FtrProviderContext) { + describe('Rules Management - Rule Import & Export APIs', function () { + loadTestFile(require.resolve('./export_rules')); + loadTestFile(require.resolve('./import_rules')); + }); +} diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_import_export/trial_license_complete_tier/export_rules.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_import_export/trial_license_complete_tier/export_rules.ts index 555e845ec7b4f..08ef7c6d7f28a 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_import_export/trial_license_complete_tier/export_rules.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_import_export/trial_license_complete_tier/export_rules.ts @@ -12,17 +12,19 @@ import { RuleResponse } from '@kbn/security-solution-plugin/common/api/detection import { PRECONFIGURED_EMAIL_ACTION_CONNECTOR_ID } from '../../../../../config/shared'; import { binaryToString, - createRule, - createAlertsIndex, - deleteAllRules, - deleteAllAlerts, getSimpleRule, getSimpleRuleOutput, getWebHookAction, removeServerGeneratedProperties, - waitForRulePartialFailure, updateUsername, } from '../../../utils'; +import { + createRule, + createAlertsIndex, + deleteAllRules, + deleteAllAlerts, + waitForRulePartialFailure, +} from '../../../../../../common/utils/security_solution'; import { FtrProviderContext } from '../../../../../ftr_provider_context'; export default ({ getService }: FtrProviderContext): void => { diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_import_export/trial_license_complete_tier/export_rules_ess.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_import_export/trial_license_complete_tier/export_rules_ess.ts index a0c7ab520da3c..fd2d2374c1d56 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_import_export/trial_license_complete_tier/export_rules_ess.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_import_export/trial_license_complete_tier/export_rules_ess.ts @@ -16,10 +16,6 @@ import { } from '@kbn/security-solution-plugin/common/constants'; import { binaryToString, - createRule, - createAlertsIndex, - deleteAllRules, - deleteAllAlerts, getSimpleRule, getSimpleRuleOutput, getWebHookAction, @@ -30,6 +26,12 @@ import { createRuleThroughAlertingEndpoint, checkInvestigationFieldSoValue, } from '../../../utils'; +import { + createRule, + createAlertsIndex, + deleteAllRules, + deleteAllAlerts, +} from '../../../../../../common/utils/security_solution'; import { FtrProviderContext } from '../../../../../ftr_provider_context'; export default ({ getService }: FtrProviderContext): void => { diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_import_export/trial_license_complete_tier/import_export_rules.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_import_export/trial_license_complete_tier/import_export_rules.ts index 3d4d37205ae7d..3be3740dda80a 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_import_export/trial_license_complete_tier/import_export_rules.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_import_export/trial_license_complete_tier/import_export_rules.ts @@ -18,14 +18,13 @@ import { } from '@kbn/lists-plugin/common/schemas/request/create_exception_list_schema.mock'; import { DETECTION_ENGINE_RULES_URL } from '@kbn/security-solution-plugin/common/constants'; import { ROLES } from '@kbn/security-solution-plugin/common/test'; +import { binaryToString, getSimpleRule } from '../../../utils'; import { - binaryToString, createRule, createAlertsIndex, deleteAllRules, deleteAllAlerts, - getSimpleRule, -} from '../../../utils'; +} from '../../../../../../common/utils/security_solution'; import { createUserAndRole, deleteUserAndRole, @@ -57,12 +56,12 @@ export default ({ getService }: FtrProviderContext): void => { }); describe('Endpoint Exception', () => { - /* - Following the release of version 8.7, this test can be considered as an evaluation of exporting + /* + Following the release of version 8.7, this test can be considered as an evaluation of exporting an outdated List Item. A notable distinction lies in the absence of the "expire_time" property within the getCreateExceptionListMinimalSchemaMock, which allows for differentiation between older - and newer versions. The rationale behind this approach is the lack of version tracking for both List and Rule, - thereby enabling simulation of migration scenarios. + and newer versions. The rationale behind this approach is the lack of version tracking for both List and Rule, + thereby enabling simulation of migration scenarios. */ it('should be able to reimport a rule referencing an old version of endpoint exception list with existing comments', async () => { // create an exception list @@ -224,12 +223,12 @@ export default ({ getService }: FtrProviderContext): void => { }); describe('Detection Exception', () => { - /* - Following the release of version 8.7, this test can be considered as an evaluation of exporting + /* + Following the release of version 8.7, this test can be considered as an evaluation of exporting an outdated List Item. A notable distinction lies in the absence of the "expire_time" property within the getCreateExceptionListMinimalSchemaMock, which allows for differentiation between older - and newer versions. The rationale behind this approach is the lack of version tracking for both List and Rule, - thereby enabling simulation of migration scenarios. + and newer versions. The rationale behind this approach is the lack of version tracking for both List and Rule, + thereby enabling simulation of migration scenarios. */ it('should be able to reimport a rule referencing an old version of detection exception list with existing comments', async () => { // create an exception list diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_import_export/trial_license_complete_tier/import_rules.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_import_export/trial_license_complete_tier/import_rules.ts index f9f5f5ce4388c..58720b601fd37 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_import_export/trial_license_complete_tier/import_rules.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_import_export/trial_license_complete_tier/import_rules.ts @@ -21,7 +21,6 @@ import { getImportExceptionsListItemNewerVersionSchemaMock, } from '@kbn/lists-plugin/common/schemas/request/import_exceptions_schema.mock'; import { - deleteAllRules, getSimpleRule, getSimpleRuleAsNdjson, getSimpleRuleOutput, @@ -31,6 +30,7 @@ import { removeServerGeneratedProperties, ruleToNdjson, } from '../../../utils'; +import { deleteAllRules } from '../../../../../../common/utils/security_solution'; import { deleteAllExceptions } from '../../../../lists_and_exception_lists/utils'; import { FtrProviderContext } from '../../../../../ftr_provider_context'; @@ -1291,12 +1291,12 @@ export default ({ getService }: FtrProviderContext): void => { await deleteAllExceptions(supertest, log); }); - /* - Following the release of version 8.7, this test can be considered as an evaluation of exporting + /* + Following the release of version 8.7, this test can be considered as an evaluation of exporting an outdated List Item. A notable distinction lies in the absence of the "expire_time" property within the getCreateExceptionListMinimalSchemaMock, which allows for differentiation between older - and newer versions. The rationale behind this approach is the lack of version tracking for both List and Rule, - thereby enabling simulation of migration scenarios. + and newer versions. The rationale behind this approach is the lack of version tracking for both List and Rule, + thereby enabling simulation of migration scenarios. */ it('should be able to import a rule and an old version exception list, then delete it successfully', async () => { const simpleRule = getSimpleRule('rule-1'); diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_import_export/trial_license_complete_tier/import_rules_ess.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_import_export/trial_license_complete_tier/import_rules_ess.ts index 536aa25300de4..69466edf7a1be 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_import_export/trial_license_complete_tier/import_rules_ess.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_import_export/trial_license_complete_tier/import_rules_ess.ts @@ -15,17 +15,16 @@ import { DETECTION_ENGINE_RULES_URL } from '@kbn/security-solution-plugin/common import { ROLES } from '@kbn/security-solution-plugin/common/test'; import { toNdJsonString } from '@kbn/lists-plugin/common/schemas/request/import_exceptions_schema.mock'; import { - deleteAllRules, getSimpleRule, ruleToNdjson, createLegacyRuleAction, getLegacyActionSO, - createRule, fetchRule, getWebHookAction, getSimpleRuleAsNdjson, checkInvestigationFieldSoValue, } from '../../../utils'; +import { deleteAllRules, createRule } from '../../../../../../common/utils/security_solution'; import { createUserAndRole, deleteUserAndRole, diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_management/basic_license_essentials_tier/coverage_overview.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_management/basic_license_essentials_tier/coverage_overview.ts index f5185ac31d316..7a6b5716f8ff7 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_management/basic_license_essentials_tier/coverage_overview.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_management/basic_license_essentials_tier/coverage_overview.ts @@ -17,13 +17,12 @@ import { FtrProviderContext } from '../../../../../ftr_provider_context'; import { createPrebuiltRuleAssetSavedObjects, createRuleAssetSavedObject, - createRule, - deleteAllRules, installPrebuiltRulesAndTimelines, installPrebuiltRules, getCustomQueryRuleParams, createNonSecurityRule, } from '../../../utils'; +import { createRule, deleteAllRules } from '../../../../../../common/utils/security_solution'; import { getCoverageOverview } from '../../../utils/rules/get_coverage_overview'; export default ({ getService }: FtrProviderContext): void => { diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_management/trial_license_complete_tier/get_rule_execution_results.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_management/trial_license_complete_tier/get_rule_execution_results.ts index fbd4836b19605..6509b4810903e 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_management/trial_license_complete_tier/get_rule_execution_results.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_management/trial_license_complete_tier/get_rule_execution_results.ts @@ -16,18 +16,20 @@ import { ELASTIC_HTTP_VERSION_HEADER, X_ELASTIC_INTERNAL_ORIGIN_REQUEST, } from '@kbn/core-http-common'; +import { + deleteAllEventLogExecutionEvents, + indexEventLogExecutionEvents, + waitForEventLogExecuteComplete, +} from '../../../utils'; import { createRule, createAlertsIndex, deleteAllRules, - deleteAllEventLogExecutionEvents, deleteAllAlerts, getRuleForAlertTesting, - indexEventLogExecutionEvents, - waitForEventLogExecuteComplete, waitForRulePartialFailure, waitForRuleSuccess, -} from '../../../utils'; +} from '../../../../../../common/utils/security_solution'; import { failedGapExecution, failedRanAfterDisabled, diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_management/trial_license_complete_tier/get_rule_management_filters.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_management/trial_license_complete_tier/get_rule_management_filters.ts index 834542a0de0e9..e8da4a62593e3 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_management/trial_license_complete_tier/get_rule_management_filters.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_management/trial_license_complete_tier/get_rule_management_filters.ts @@ -11,11 +11,11 @@ import { DETECTION_ENGINE_RULES_URL } from '@kbn/security-solution-plugin/common import { RULE_MANAGEMENT_FILTERS_URL } from '@kbn/security-solution-plugin/common/api/detection_engine/rule_management'; import { - deleteAllRules, getSimpleRule, installMockPrebuiltRules, deleteAllPrebuiltRuleAssets, } from '../../../utils'; +import { deleteAllRules } from '../../../../../../common/utils/security_solution'; import { FtrProviderContext } from '../../../../../ftr_provider_context'; export default ({ getService }: FtrProviderContext): void => { diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_patch/basic_license_essentials_tier/configs/ess.config.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_patch/basic_license_essentials_tier/configs/ess.config.ts new file mode 100644 index 0000000000000..c71cfab3cc9fd --- /dev/null +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_patch/basic_license_essentials_tier/configs/ess.config.ts @@ -0,0 +1,22 @@ +/* + * 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 { FtrConfigProviderContext } from '@kbn/test'; + +export default async function ({ readConfigFile }: FtrConfigProviderContext) { + const functionalConfig = await readConfigFile( + require.resolve('../../../../../../config/ess/config.base.basic') + ); + + return { + ...functionalConfig.getAll(), + testFiles: [require.resolve('..')], + junit: { + reportName: 'Rules Management - Rule Patch Integration Tests - ESS Env - Basic License', + }, + }; +} diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_patch/basic_license_essentials_tier/configs/serverless.config.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_patch/basic_license_essentials_tier/configs/serverless.config.ts new file mode 100644 index 0000000000000..de3421e190305 --- /dev/null +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_patch/basic_license_essentials_tier/configs/serverless.config.ts @@ -0,0 +1,16 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { createTestConfig } from '../../../../../../config/serverless/config.base.essentials'; + +export default createTestConfig({ + testFiles: [require.resolve('..')], + junit: { + reportName: + 'Rules Management - Rule Patch Integration Tests - Serverless Env - Essentials Tier ', + }, +}); diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_patch/basic_license_essentials_tier/index.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_patch/basic_license_essentials_tier/index.ts new file mode 100644 index 0000000000000..ccaa2d297a8de --- /dev/null +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_patch/basic_license_essentials_tier/index.ts @@ -0,0 +1,15 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { FtrProviderContext } from '../../../../../ftr_provider_context'; + +export default function ({ loadTestFile }: FtrProviderContext) { + describe('Rules Management - Rule Patch APIs', function () { + loadTestFile(require.resolve('./patch_rules_bulk')); + loadTestFile(require.resolve('./patch_rules')); + }); +} diff --git a/x-pack/test/detection_engine_api_integration/basic/tests/patch_rules.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_patch/basic_license_essentials_tier/patch_rules.ts similarity index 84% rename from x-pack/test/detection_engine_api_integration/basic/tests/patch_rules.ts rename to x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_patch/basic_license_essentials_tier/patch_rules.ts index 040a504ee282c..7d53883276f9d 100644 --- a/x-pack/test/detection_engine_api_integration/basic/tests/patch_rules.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_patch/basic_license_essentials_tier/patch_rules.ts @@ -8,29 +8,33 @@ import expect from '@kbn/expect'; import { DETECTION_ENGINE_RULES_URL } from '@kbn/security-solution-plugin/common/constants'; -import { FtrProviderContext } from '../../common/ftr_provider_context'; +import { FtrProviderContext } from '../../../../../ftr_provider_context'; import { - createSignalsIndex, - deleteAllRules, getSimpleRule, getSimpleRuleOutput, removeServerGeneratedProperties, removeServerGeneratedPropertiesIncludingRuleId, getSimpleRuleOutputWithoutRuleId, + updateUsername, +} from '../../../utils'; +import { + createAlertsIndex, + deleteAllRules, createRule, deleteAllAlerts, -} from '../../utils'; +} from '../../../../../../common/utils/security_solution'; -// eslint-disable-next-line import/no-default-export export default ({ getService }: FtrProviderContext) => { const supertest = getService('supertest'); const log = getService('log'); const es = getService('es'); + const config = getService('config'); + const ELASTICSEARCH_USERNAME = config.get('servers.kibana.username'); - describe('patch_rules', () => { + describe('@ess @serverless patch_rules', () => { describe('patch rules', () => { beforeEach(async () => { - await createSignalsIndex(supertest, log); + await createAlertsIndex(supertest, log); }); afterEach(async () => { @@ -52,11 +56,13 @@ export default ({ getService }: FtrProviderContext) => { const outputRule = getSimpleRuleOutput(); outputRule.name = 'some other name'; outputRule.revision = 1; + const expectedRule = updateUsername(outputRule, ELASTICSEARCH_USERNAME); + const bodyToCompare = removeServerGeneratedProperties(body); - expect(bodyToCompare).to.eql(outputRule); + expect(bodyToCompare).to.eql(expectedRule); }); - it('should return a "403 forbidden" using a rule_id of type "machine learning"', async () => { + it('@brokenInServerless should return a "403 forbidden" using a rule_id of type "machine learning"', async () => { await createRule(supertest, log, getSimpleRule('rule-1')); // patch a simple rule's type to machine learning @@ -90,8 +96,10 @@ export default ({ getService }: FtrProviderContext) => { const outputRule = getSimpleRuleOutputWithoutRuleId(); outputRule.name = 'some other name'; outputRule.revision = 1; + const expectedRule = updateUsername(outputRule, ELASTICSEARCH_USERNAME); + const bodyToCompare = removeServerGeneratedPropertiesIncludingRuleId(body); - expect(bodyToCompare).to.eql(outputRule); + expect(bodyToCompare).to.eql(expectedRule); }); it('should patch a single rule property of name using the auto-generated id', async () => { @@ -108,8 +116,10 @@ export default ({ getService }: FtrProviderContext) => { const outputRule = getSimpleRuleOutput(); outputRule.name = 'some other name'; outputRule.revision = 1; + const expectedRule = updateUsername(outputRule, ELASTICSEARCH_USERNAME); + const bodyToCompare = removeServerGeneratedProperties(body); - expect(bodyToCompare).to.eql(outputRule); + expect(bodyToCompare).to.eql(expectedRule); }); it('should not change the revision of a rule when it patches only enabled', async () => { @@ -125,9 +135,10 @@ export default ({ getService }: FtrProviderContext) => { const outputRule = getSimpleRuleOutput(); outputRule.enabled = false; + const expectedRule = updateUsername(outputRule, ELASTICSEARCH_USERNAME); const bodyToCompare = removeServerGeneratedProperties(body); - expect(bodyToCompare).to.eql(outputRule); + expect(bodyToCompare).to.eql(expectedRule); }); it('should change the revision of a rule when it patches enabled and another property', async () => { @@ -145,9 +156,10 @@ export default ({ getService }: FtrProviderContext) => { outputRule.enabled = false; outputRule.severity = 'low'; outputRule.revision = 1; + const expectedRule = updateUsername(outputRule, ELASTICSEARCH_USERNAME); const bodyToCompare = removeServerGeneratedProperties(body); - expect(bodyToCompare).to.eql(outputRule); + expect(bodyToCompare).to.eql(expectedRule); }); it('should not change other properties when it does patches', async () => { @@ -174,9 +186,10 @@ export default ({ getService }: FtrProviderContext) => { outputRule.timeline_title = 'some title'; outputRule.timeline_id = 'some id'; outputRule.revision = 2; + const expectedRule = updateUsername(outputRule, ELASTICSEARCH_USERNAME); const bodyToCompare = removeServerGeneratedProperties(body); - expect(bodyToCompare).to.eql(outputRule); + expect(bodyToCompare).to.eql(expectedRule); }); it('should give a 404 if it is given a fake id', async () => { diff --git a/x-pack/test/detection_engine_api_integration/basic/tests/patch_rules_bulk.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_patch/basic_license_essentials_tier/patch_rules_bulk.ts similarity index 85% rename from x-pack/test/detection_engine_api_integration/basic/tests/patch_rules_bulk.ts rename to x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_patch/basic_license_essentials_tier/patch_rules_bulk.ts index 48407ba7df94b..cee298e8fca4e 100644 --- a/x-pack/test/detection_engine_api_integration/basic/tests/patch_rules_bulk.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_patch/basic_license_essentials_tier/patch_rules_bulk.ts @@ -8,29 +8,33 @@ import expect from '@kbn/expect'; import { DETECTION_ENGINE_RULES_BULK_UPDATE } from '@kbn/security-solution-plugin/common/constants'; -import { FtrProviderContext } from '../../common/ftr_provider_context'; +import { FtrProviderContext } from '../../../../../ftr_provider_context'; import { - createSignalsIndex, - deleteAllRules, getSimpleRule, getSimpleRuleOutput, removeServerGeneratedProperties, getSimpleRuleOutputWithoutRuleId, removeServerGeneratedPropertiesIncludingRuleId, + updateUsername, +} from '../../../utils'; +import { + createAlertsIndex, + deleteAllRules, createRule, deleteAllAlerts, -} from '../../utils'; +} from '../../../../../../common/utils/security_solution'; -// eslint-disable-next-line import/no-default-export export default ({ getService }: FtrProviderContext) => { const supertest = getService('supertest'); const log = getService('log'); const es = getService('es'); + const config = getService('config'); + const ELASTICSEARCH_USERNAME = config.get('servers.kibana.username'); - describe('patch_rules_bulk', () => { + describe('@ess @serverless patch_rules_bulk', () => { describe('patch rules bulk', () => { beforeEach(async () => { - await createSignalsIndex(supertest, log); + await createAlertsIndex(supertest, log); }); afterEach(async () => { @@ -53,7 +57,8 @@ export default ({ getService }: FtrProviderContext) => { outputRule.name = 'some other name'; outputRule.revision = 1; const bodyToCompare = removeServerGeneratedProperties(body[0]); - expect(bodyToCompare).to.eql(outputRule); + const expectedRule = updateUsername(outputRule, ELASTICSEARCH_USERNAME); + expect(bodyToCompare).to.eql(expectedRule); }); it('should patch two rule properties of name using the two rules rule_id', async () => { @@ -74,15 +79,17 @@ export default ({ getService }: FtrProviderContext) => { const outputRule1 = getSimpleRuleOutput(); outputRule1.name = 'some other name'; outputRule1.revision = 1; + const expectedRule1 = updateUsername(outputRule1, ELASTICSEARCH_USERNAME); const outputRule2 = getSimpleRuleOutput('rule-2'); outputRule2.name = 'some other name'; outputRule2.revision = 1; + const expectedRule2 = updateUsername(outputRule2, ELASTICSEARCH_USERNAME); const bodyToCompare1 = removeServerGeneratedProperties(body[0]); const bodyToCompare2 = removeServerGeneratedProperties(body[1]); - expect(bodyToCompare1).to.eql(outputRule1); - expect(bodyToCompare2).to.eql(outputRule2); + expect(bodyToCompare1).to.eql(expectedRule1); + expect(bodyToCompare2).to.eql(expectedRule2); }); it('should patch a single rule property of name using an id', async () => { @@ -99,8 +106,9 @@ export default ({ getService }: FtrProviderContext) => { const outputRule = getSimpleRuleOutput(); outputRule.name = 'some other name'; outputRule.revision = 1; + const expectedRule = updateUsername(outputRule, ELASTICSEARCH_USERNAME); const bodyToCompare = removeServerGeneratedProperties(body[0]); - expect(bodyToCompare).to.eql(outputRule); + expect(bodyToCompare).to.eql(expectedRule); }); it('should patch two rule properties of name using the two rules id', async () => { @@ -121,15 +129,17 @@ export default ({ getService }: FtrProviderContext) => { const outputRule1 = getSimpleRuleOutputWithoutRuleId('rule-1'); outputRule1.name = 'some other name'; outputRule1.revision = 1; + const expectedRule = updateUsername(outputRule1, ELASTICSEARCH_USERNAME); const outputRule2 = getSimpleRuleOutputWithoutRuleId('rule-2'); outputRule2.name = 'some other name'; outputRule2.revision = 1; + const expectedRule2 = updateUsername(outputRule2, ELASTICSEARCH_USERNAME); const bodyToCompare1 = removeServerGeneratedPropertiesIncludingRuleId(body[0]); const bodyToCompare2 = removeServerGeneratedPropertiesIncludingRuleId(body[1]); - expect(bodyToCompare1).to.eql(outputRule1); - expect(bodyToCompare2).to.eql(outputRule2); + expect(bodyToCompare1).to.eql(expectedRule); + expect(bodyToCompare2).to.eql(expectedRule2); }); it('should patch a single rule property of name using the auto-generated id', async () => { @@ -146,8 +156,10 @@ export default ({ getService }: FtrProviderContext) => { const outputRule = getSimpleRuleOutput(); outputRule.name = 'some other name'; outputRule.revision = 1; + const expectedRule = updateUsername(outputRule, ELASTICSEARCH_USERNAME); + const bodyToCompare = removeServerGeneratedProperties(body[0]); - expect(bodyToCompare).to.eql(outputRule); + expect(bodyToCompare).to.eql(expectedRule); }); it('should not change the revision of a rule when it patches only enabled', async () => { @@ -163,9 +175,10 @@ export default ({ getService }: FtrProviderContext) => { const outputRule = getSimpleRuleOutput(); outputRule.enabled = false; + const expectedRule = updateUsername(outputRule, ELASTICSEARCH_USERNAME); const bodyToCompare = removeServerGeneratedProperties(body[0]); - expect(bodyToCompare).to.eql(outputRule); + expect(bodyToCompare).to.eql(expectedRule); }); it('should change the revision of a rule when it patches enabled and another property', async () => { @@ -183,9 +196,10 @@ export default ({ getService }: FtrProviderContext) => { outputRule.enabled = false; outputRule.severity = 'low'; outputRule.revision = 1; + const expectedRule = updateUsername(outputRule, ELASTICSEARCH_USERNAME); const bodyToCompare = removeServerGeneratedProperties(body[0]); - expect(bodyToCompare).to.eql(outputRule); + expect(bodyToCompare).to.eql(expectedRule); }); it('should not change other properties when it does patches', async () => { @@ -212,9 +226,10 @@ export default ({ getService }: FtrProviderContext) => { outputRule.timeline_title = 'some title'; outputRule.timeline_id = 'some id'; outputRule.revision = 2; + const expectedRule = updateUsername(outputRule, ELASTICSEARCH_USERNAME); const bodyToCompare = removeServerGeneratedProperties(body[0]); - expect(bodyToCompare).to.eql(outputRule); + expect(bodyToCompare).to.eql(expectedRule); }); it('should return a 200 but give a 404 in the message if it is given a fake id', async () => { @@ -269,10 +284,11 @@ export default ({ getService }: FtrProviderContext) => { const outputRule = getSimpleRuleOutput(); outputRule.name = 'some other name'; outputRule.revision = 1; + const expectedRule = updateUsername(outputRule, ELASTICSEARCH_USERNAME); const bodyToCompare = removeServerGeneratedProperties(body[0]); expect([bodyToCompare, body[1]]).to.eql([ - outputRule, + expectedRule, { error: { message: 'rule_id: "fake_id" not found', @@ -300,10 +316,11 @@ export default ({ getService }: FtrProviderContext) => { const outputRule = getSimpleRuleOutput(); outputRule.name = 'some other name'; outputRule.revision = 1; + const expectedRule = updateUsername(outputRule, ELASTICSEARCH_USERNAME); const bodyToCompare = removeServerGeneratedProperties(body[0]); expect([bodyToCompare, body[1]]).to.eql([ - outputRule, + expectedRule, { error: { message: 'id: "5096dec6-b6b9-4d8d-8f93-6c2602079d9d" not found', diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_patch/trial_license_complete_tier/patch_rules.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_patch/trial_license_complete_tier/patch_rules.ts index 015175209fe23..edd84f6c86650 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_patch/trial_license_complete_tier/patch_rules.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_patch/trial_license_complete_tier/patch_rules.ts @@ -17,16 +17,12 @@ import { RuleActionArray, RuleActionThrottle } from '@kbn/securitysolution-io-ts import { ExceptionListTypeEnum } from '@kbn/securitysolution-io-ts-list-types'; import { - createAlertsIndex, - deleteAllRules, - deleteAllAlerts, getSimpleRule, getSimpleRuleOutput, removeServerGeneratedProperties, removeServerGeneratedPropertiesIncludingRuleId, getSimpleRuleOutputWithoutRuleId, getSimpleMlRuleOutput, - createRule, getSimpleMlRule, getSimpleRuleWithoutRuleId, removeUUIDFromActions, @@ -35,6 +31,12 @@ import { getSomeActionsWithFrequencies, updateUsername, } from '../../../utils'; +import { + createAlertsIndex, + deleteAllRules, + deleteAllAlerts, + createRule, +} from '../../../../../../common/utils/security_solution'; import { FtrProviderContext } from '../../../../../ftr_provider_context'; export default ({ getService }: FtrProviderContext) => { diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_patch/trial_license_complete_tier/patch_rules_bulk.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_patch/trial_license_complete_tier/patch_rules_bulk.ts index 6b8bc2a7c24bf..e48a59391287c 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_patch/trial_license_complete_tier/patch_rules_bulk.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_patch/trial_license_complete_tier/patch_rules_bulk.ts @@ -13,15 +13,11 @@ import { Rule } from '@kbn/alerting-plugin/common'; import { BaseRuleParams } from '@kbn/security-solution-plugin/server/lib/detection_engine/rule_schema'; import { - createAlertsIndex, - deleteAllRules, - deleteAllAlerts, getSimpleRule, getSimpleRuleOutput, removeServerGeneratedProperties, getSimpleRuleOutputWithoutRuleId, removeServerGeneratedPropertiesIncludingRuleId, - createRule, createLegacyRuleAction, getLegacyActionSO, getRuleSOById, @@ -31,6 +27,12 @@ import { getRuleSavedObjectWithLegacyInvestigationFields, checkInvestigationFieldSoValue, } from '../../../utils'; +import { + createAlertsIndex, + deleteAllRules, + deleteAllAlerts, + createRule, +} from '../../../../../../common/utils/security_solution'; import { FtrProviderContext } from '../../../../../ftr_provider_context'; export default ({ getService }: FtrProviderContext) => { diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_patch/trial_license_complete_tier/patch_rules_ess.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_patch/trial_license_complete_tier/patch_rules_ess.ts index be413c1a5ef9c..5efab4bb9e533 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_patch/trial_license_complete_tier/patch_rules_ess.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_patch/trial_license_complete_tier/patch_rules_ess.ts @@ -12,14 +12,10 @@ import { BaseRuleParams } from '@kbn/security-solution-plugin/server/lib/detecti import { DETECTION_ENGINE_RULES_URL } from '@kbn/security-solution-plugin/common/constants'; import { - createAlertsIndex, - deleteAllRules, - deleteAllAlerts, removeServerGeneratedProperties, createRuleThroughAlertingEndpoint, getRuleSavedObjectWithLegacyInvestigationFields, getRuleSavedObjectWithLegacyInvestigationFieldsEmptyArray, - createRule, getLegacyActionSO, getSimpleRuleOutput, updateUsername, @@ -27,6 +23,12 @@ import { getSimpleRule, checkInvestigationFieldSoValue, } from '../../../utils'; +import { + createAlertsIndex, + deleteAllRules, + deleteAllAlerts, + createRule, +} from '../../../../../../common/utils/security_solution'; import { FtrProviderContext } from '../../../../../ftr_provider_context'; export default ({ getService }: FtrProviderContext) => { diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_read/basic_license_essentials_tier/configs/ess.config.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_read/basic_license_essentials_tier/configs/ess.config.ts new file mode 100644 index 0000000000000..e7421eb362996 --- /dev/null +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_read/basic_license_essentials_tier/configs/ess.config.ts @@ -0,0 +1,22 @@ +/* + * 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 { FtrConfigProviderContext } from '@kbn/test'; + +export default async function ({ readConfigFile }: FtrConfigProviderContext) { + const functionalConfig = await readConfigFile( + require.resolve('../../../../../../config/ess/config.base.basic') + ); + + return { + ...functionalConfig.getAll(), + testFiles: [require.resolve('..')], + junit: { + reportName: 'Rules Management - Rule Read Integration Tests - ESS Env - Basic License', + }, + }; +} diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_read/basic_license_essentials_tier/configs/serverless.config.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_read/basic_license_essentials_tier/configs/serverless.config.ts new file mode 100644 index 0000000000000..a8827a68facac --- /dev/null +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_read/basic_license_essentials_tier/configs/serverless.config.ts @@ -0,0 +1,16 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { createTestConfig } from '../../../../../../config/serverless/config.base.essentials'; + +export default createTestConfig({ + testFiles: [require.resolve('..')], + junit: { + reportName: + 'Rules Management - Rule Read Integration Tests - Serverless Env - Essentials Tier ', + }, +}); diff --git a/x-pack/test/detection_engine_api_integration/basic/tests/find_rules.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_read/basic_license_essentials_tier/find_rules.ts similarity index 80% rename from x-pack/test/detection_engine_api_integration/basic/tests/find_rules.ts rename to x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_read/basic_license_essentials_tier/find_rules.ts index 01ee932f60b1b..1810328dca64b 100644 --- a/x-pack/test/detection_engine_api_integration/basic/tests/find_rules.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_read/basic_license_essentials_tier/find_rules.ts @@ -8,23 +8,24 @@ import expect from '@kbn/expect'; import { DETECTION_ENGINE_RULES_URL } from '@kbn/security-solution-plugin/common/constants'; -import { FtrProviderContext } from '../../common/ftr_provider_context'; +import { FtrProviderContext } from '../../../../../ftr_provider_context'; import { - createRule, - deleteAllRules, getComplexRule, getComplexRuleOutput, getSimpleRule, getSimpleRuleOutput, removeServerGeneratedProperties, -} from '../../utils'; + updateUsername, +} from '../../../utils'; +import { createRule, deleteAllRules } from '../../../../../../common/utils/security_solution'; -// eslint-disable-next-line import/no-default-export export default ({ getService }: FtrProviderContext): void => { const supertest = getService('supertest'); const log = getService('log'); + const config = getService('config'); + const ELASTICSEARCH_USERNAME = config.get('servers.kibana.username'); - describe('find_rules', () => { + describe('@ess @serverless find_rules', () => { beforeEach(async () => { await deleteAllRules(supertest, log); }); @@ -57,8 +58,10 @@ export default ({ getService }: FtrProviderContext): void => { .expect(200); body.data = [removeServerGeneratedProperties(body.data[0])]; + const expectedRule = updateUsername(getSimpleRuleOutput(), ELASTICSEARCH_USERNAME); + expect(body).to.eql({ - data: [getSimpleRuleOutput()], + data: [expectedRule], page: 1, perPage: 20, total: 1, @@ -83,8 +86,9 @@ export default ({ getService }: FtrProviderContext): void => { .expect(200); body.data = [removeServerGeneratedProperties(body.data[0])]; + const expectedRule = updateUsername(getComplexRuleOutput(), ELASTICSEARCH_USERNAME); expect(body).to.eql({ - data: [getComplexRuleOutput()], + data: [expectedRule], page: 1, perPage: 20, total: 1, diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_read/basic_license_essentials_tier/index.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_read/basic_license_essentials_tier/index.ts new file mode 100644 index 0000000000000..510d07b612492 --- /dev/null +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_read/basic_license_essentials_tier/index.ts @@ -0,0 +1,15 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { FtrProviderContext } from '../../../../../ftr_provider_context'; + +export default function ({ loadTestFile }: FtrProviderContext) { + describe('Rules Management - Rule Read APIs', function () { + loadTestFile(require.resolve('./read_rules')); + loadTestFile(require.resolve('./find_rules')); + }); +} diff --git a/x-pack/test/detection_engine_api_integration/basic/tests/read_rules.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_read/basic_license_essentials_tier/read_rules.ts similarity index 79% rename from x-pack/test/detection_engine_api_integration/basic/tests/read_rules.ts rename to x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_read/basic_license_essentials_tier/read_rules.ts index 839f5a15fe161..845c1ee4196aa 100644 --- a/x-pack/test/detection_engine_api_integration/basic/tests/read_rules.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_read/basic_license_essentials_tier/read_rules.ts @@ -8,30 +8,34 @@ import expect from '@kbn/expect'; import { DETECTION_ENGINE_RULES_URL } from '@kbn/security-solution-plugin/common/constants'; -import { FtrProviderContext } from '../../common/ftr_provider_context'; +import { FtrProviderContext } from '../../../../../ftr_provider_context'; import { - createRule, - createSignalsIndex, - deleteAllRules, - deleteAllAlerts, getSimpleRule, getSimpleRuleOutput, getSimpleRuleOutputWithoutRuleId, getSimpleRuleWithoutRuleId, removeServerGeneratedProperties, removeServerGeneratedPropertiesIncludingRuleId, -} from '../../utils'; + updateUsername, +} from '../../../utils'; +import { + createRule, + createAlertsIndex, + deleteAllRules, + deleteAllAlerts, +} from '../../../../../../common/utils/security_solution'; -// eslint-disable-next-line import/no-default-export export default ({ getService }: FtrProviderContext) => { const supertest = getService('supertest'); const log = getService('log'); const es = getService('es'); + const config = getService('config'); + const ELASTICSEARCH_USERNAME = config.get('servers.kibana.username'); - describe('read_rules', () => { + describe('@ess @serverless read_rules', () => { describe('reading rules', () => { beforeEach(async () => { - await createSignalsIndex(supertest, log); + await createAlertsIndex(supertest, log); }); afterEach(async () => { @@ -50,7 +54,9 @@ export default ({ getService }: FtrProviderContext) => { .expect(200); const bodyToCompare = removeServerGeneratedProperties(body); - expect(bodyToCompare).to.eql(getSimpleRuleOutput()); + const expectedRule = updateUsername(getSimpleRuleOutput(), ELASTICSEARCH_USERNAME); + + expect(bodyToCompare).to.eql(expectedRule); }); it('should be able to read a single rule using id', async () => { @@ -64,7 +70,9 @@ export default ({ getService }: FtrProviderContext) => { .expect(200); const bodyToCompare = removeServerGeneratedProperties(body); - expect(bodyToCompare).to.eql(getSimpleRuleOutput()); + const expectedRule = updateUsername(getSimpleRuleOutput(), ELASTICSEARCH_USERNAME); + + expect(bodyToCompare).to.eql(expectedRule); }); it('should be able to read a single rule with an auto-generated rule_id', async () => { @@ -78,7 +86,12 @@ export default ({ getService }: FtrProviderContext) => { .expect(200); const bodyToCompare = removeServerGeneratedPropertiesIncludingRuleId(body); - expect(bodyToCompare).to.eql(getSimpleRuleOutputWithoutRuleId()); + const expectedRule = updateUsername( + getSimpleRuleOutputWithoutRuleId(), + ELASTICSEARCH_USERNAME + ); + + expect(bodyToCompare).to.eql(expectedRule); }); it('should return 404 if given a fake id', async () => { diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_read/trial_license_complete_tier/find_rules.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_read/trial_license_complete_tier/find_rules.ts index 96e3b90013112..56b90a088f18a 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_read/trial_license_complete_tier/find_rules.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_read/trial_license_complete_tier/find_rules.ts @@ -12,8 +12,6 @@ import { } from '@kbn/core-http-common'; import { DETECTION_ENGINE_RULES_URL } from '@kbn/security-solution-plugin/common/constants'; import { - createRule, - deleteAllRules, getComplexRule, getComplexRuleOutput, getSimpleRule, @@ -22,6 +20,7 @@ import { updateUsername, removeServerGeneratedProperties, } from '../../../utils'; +import { createRule, deleteAllRules } from '../../../../../../common/utils/security_solution'; import { FtrProviderContext } from '../../../../../ftr_provider_context'; export default ({ getService }: FtrProviderContext): void => { diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_read/trial_license_complete_tier/find_rules_ess.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_read/trial_license_complete_tier/find_rules_ess.ts index 1155c99e9455d..ccebf86486879 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_read/trial_license_complete_tier/find_rules_ess.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_read/trial_license_complete_tier/find_rules_ess.ts @@ -15,9 +15,7 @@ import { UPDATE_OR_CREATE_LEGACY_ACTIONS, } from '@kbn/security-solution-plugin/common/constants'; import { - createRule, createRuleThroughAlertingEndpoint, - deleteAllRules, getSimpleRule, getSimpleRuleOutput, getWebHookAction, @@ -27,6 +25,7 @@ import { getRuleSavedObjectWithLegacyInvestigationFieldsEmptyArray, checkInvestigationFieldSoValue, } from '../../../utils'; +import { createRule, deleteAllRules } from '../../../../../../common/utils/security_solution'; import { FtrProviderContext } from '../../../../../ftr_provider_context'; export default ({ getService }: FtrProviderContext): void => { diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_read/trial_license_complete_tier/read_rules.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_read/trial_license_complete_tier/read_rules.ts index 8ac8fcec3744b..c76a4c5ef1dd7 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_read/trial_license_complete_tier/read_rules.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_read/trial_license_complete_tier/read_rules.ts @@ -8,10 +8,6 @@ import expect from '@kbn/expect'; import { DETECTION_ENGINE_RULES_URL } from '@kbn/security-solution-plugin/common/constants'; import { - createRule, - createAlertsIndex, - deleteAllRules, - deleteAllAlerts, getSimpleRule, getSimpleRuleOutput, getSimpleRuleOutputWithoutRuleId, @@ -21,6 +17,12 @@ import { removeServerGeneratedPropertiesIncludingRuleId, updateUsername, } from '../../../utils'; +import { + createRule, + createAlertsIndex, + deleteAllRules, + deleteAllAlerts, +} from '../../../../../../common/utils/security_solution'; import { FtrProviderContext } from '../../../../../ftr_provider_context'; export default ({ getService }: FtrProviderContext) => { diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_read/trial_license_complete_tier/read_rules_ess.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_read/trial_license_complete_tier/read_rules_ess.ts index 5c6362d10eb60..78bbcadac71eb 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_read/trial_license_complete_tier/read_rules_ess.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_read/trial_license_complete_tier/read_rules_ess.ts @@ -13,10 +13,6 @@ import { UPDATE_OR_CREATE_LEGACY_ACTIONS, } from '@kbn/security-solution-plugin/common/constants'; import { - createRule, - createAlertsIndex, - deleteAllRules, - deleteAllAlerts, getSimpleRule, getSimpleRuleOutput, getWebHookAction, @@ -27,6 +23,12 @@ import { getRuleSavedObjectWithLegacyInvestigationFieldsEmptyArray, checkInvestigationFieldSoValue, } from '../../../utils'; +import { + createRule, + createAlertsIndex, + deleteAllRules, + deleteAllAlerts, +} from '../../../../../../common/utils/security_solution'; import { FtrProviderContext } from '../../../../../ftr_provider_context'; export default ({ getService }: FtrProviderContext) => { diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_read/trial_license_complete_tier/resolve_read_rules.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_read/trial_license_complete_tier/resolve_read_rules.ts index d5c167b6a9d15..437517c7545a2 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_read/trial_license_complete_tier/resolve_read_rules.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_read/trial_license_complete_tier/resolve_read_rules.ts @@ -9,7 +9,11 @@ import expect from '@kbn/expect'; import { ALERTING_CASES_SAVED_OBJECT_INDEX } from '@kbn/core-saved-objects-server'; import { DETECTION_ENGINE_RULES_URL } from '@kbn/security-solution-plugin/common/constants'; -import { createAlertsIndex, deleteAllRules, deleteAllAlerts } from '../../../utils'; +import { + createAlertsIndex, + deleteAllRules, + deleteAllAlerts, +} from '../../../../../../common/utils/security_solution'; import { FtrProviderContext } from '../../../../../ftr_provider_context'; const spaceId = '714-space'; diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_update/basic_license_essentials_tier/configs/ess.config.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_update/basic_license_essentials_tier/configs/ess.config.ts new file mode 100644 index 0000000000000..917ad5bbf6fa5 --- /dev/null +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_update/basic_license_essentials_tier/configs/ess.config.ts @@ -0,0 +1,22 @@ +/* + * 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 { FtrConfigProviderContext } from '@kbn/test'; + +export default async function ({ readConfigFile }: FtrConfigProviderContext) { + const functionalConfig = await readConfigFile( + require.resolve('../../../../../../config/ess/config.base.basic') + ); + + return { + ...functionalConfig.getAll(), + testFiles: [require.resolve('..')], + junit: { + reportName: 'Rules Management - Rule Update Integration Tests - ESS Env - Basic License', + }, + }; +} diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_update/basic_license_essentials_tier/configs/serverless.config.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_update/basic_license_essentials_tier/configs/serverless.config.ts new file mode 100644 index 0000000000000..4984e833902ab --- /dev/null +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_update/basic_license_essentials_tier/configs/serverless.config.ts @@ -0,0 +1,16 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { createTestConfig } from '../../../../../../config/serverless/config.base.essentials'; + +export default createTestConfig({ + testFiles: [require.resolve('..')], + junit: { + reportName: + 'Rules Management - Rule Update Integration Tests - Serverless Env - Essentials Tier ', + }, +}); diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_update/basic_license_essentials_tier/index.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_update/basic_license_essentials_tier/index.ts new file mode 100644 index 0000000000000..fe58f672777ba --- /dev/null +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_update/basic_license_essentials_tier/index.ts @@ -0,0 +1,15 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { FtrProviderContext } from '../../../../../ftr_provider_context'; + +export default function ({ loadTestFile }: FtrProviderContext) { + describe('Rules Management - Rule Update APIs', function () { + loadTestFile(require.resolve('./update_rules_bulk')); + loadTestFile(require.resolve('./update_rules')); + }); +} diff --git a/x-pack/test/detection_engine_api_integration/basic/tests/update_rules.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_update/basic_license_essentials_tier/update_rules.ts similarity index 86% rename from x-pack/test/detection_engine_api_integration/basic/tests/update_rules.ts rename to x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_update/basic_license_essentials_tier/update_rules.ts index c9332cd41b4fb..38905d5176127 100644 --- a/x-pack/test/detection_engine_api_integration/basic/tests/update_rules.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_update/basic_license_essentials_tier/update_rules.ts @@ -8,31 +8,35 @@ import expect from '@kbn/expect'; import { DETECTION_ENGINE_RULES_URL } from '@kbn/security-solution-plugin/common/constants'; -import { FtrProviderContext } from '../../common/ftr_provider_context'; +import { FtrProviderContext } from '../../../../../ftr_provider_context'; import { - createSignalsIndex, - deleteAllRules, getSimpleRuleOutput, removeServerGeneratedProperties, removeServerGeneratedPropertiesIncludingRuleId, getSimpleRuleOutputWithoutRuleId, getSimpleRuleUpdate, getSimpleMlRuleUpdate, - createRule, getSimpleRule, + updateUsername, +} from '../../../utils'; +import { + createAlertsIndex, + deleteAllRules, + createRule, deleteAllAlerts, -} from '../../utils'; +} from '../../../../../../common/utils/security_solution'; -// eslint-disable-next-line import/no-default-export export default ({ getService }: FtrProviderContext) => { const supertest = getService('supertest'); const log = getService('log'); const es = getService('es'); + const config = getService('config'); + const ELASTICSEARCH_USERNAME = config.get('servers.kibana.username'); - describe('update_rules', () => { + describe('@ess @serverless update_rules', () => { describe('update rules', () => { beforeEach(async () => { - await createSignalsIndex(supertest, log); + await createAlertsIndex(supertest, log); }); afterEach(async () => { @@ -59,11 +63,13 @@ export default ({ getService }: FtrProviderContext) => { const outputRule = getSimpleRuleOutput(); outputRule.name = 'some other name'; outputRule.revision = 1; + const expectedRule = updateUsername(outputRule, ELASTICSEARCH_USERNAME); + const bodyToCompare = removeServerGeneratedProperties(body); - expect(bodyToCompare).to.eql(outputRule); + expect(bodyToCompare).to.eql(expectedRule); }); - it('should return a 403 forbidden if it is a machine learning job', async () => { + it('@brokenInServerless should return a 403 forbidden if it is a machine learning job', async () => { await createRule(supertest, log, getSimpleRule('rule-1')); // update a simple rule's type to try to be a machine learning job type @@ -106,8 +112,10 @@ export default ({ getService }: FtrProviderContext) => { const outputRule = getSimpleRuleOutputWithoutRuleId(); outputRule.name = 'some other name'; outputRule.revision = 1; + const expectedRule = updateUsername(outputRule, ELASTICSEARCH_USERNAME); + const bodyToCompare = removeServerGeneratedPropertiesIncludingRuleId(body); - expect(bodyToCompare).to.eql(outputRule); + expect(bodyToCompare).to.eql(expectedRule); }); it('should update a single rule property of name using the auto-generated id', async () => { @@ -129,8 +137,10 @@ export default ({ getService }: FtrProviderContext) => { const outputRule = getSimpleRuleOutput(); outputRule.name = 'some other name'; outputRule.revision = 1; + const expectedRule = updateUsername(outputRule, ELASTICSEARCH_USERNAME); + const bodyToCompare = removeServerGeneratedProperties(body); - expect(bodyToCompare).to.eql(outputRule); + expect(bodyToCompare).to.eql(expectedRule); }); it('should change the revision of a rule when it updates enabled and another property', async () => { @@ -152,9 +162,10 @@ export default ({ getService }: FtrProviderContext) => { outputRule.enabled = false; outputRule.severity = 'low'; outputRule.revision = 1; + const expectedRule = updateUsername(outputRule, ELASTICSEARCH_USERNAME); const bodyToCompare = removeServerGeneratedProperties(body); - expect(bodyToCompare).to.eql(outputRule); + expect(bodyToCompare).to.eql(expectedRule); }); it('should change other properties when it does updates and effectively delete them such as timeline_title', async () => { @@ -186,9 +197,10 @@ export default ({ getService }: FtrProviderContext) => { const outputRule = getSimpleRuleOutput(); outputRule.name = 'some other name'; outputRule.revision = 2; + const expectedRule = updateUsername(outputRule, ELASTICSEARCH_USERNAME); const bodyToCompare = removeServerGeneratedProperties(body); - expect(bodyToCompare).to.eql(outputRule); + expect(bodyToCompare).to.eql(expectedRule); }); it('should give a 404 if it is given a fake id', async () => { diff --git a/x-pack/test/detection_engine_api_integration/basic/tests/update_rules_bulk.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_update/basic_license_essentials_tier/update_rules_bulk.ts similarity index 86% rename from x-pack/test/detection_engine_api_integration/basic/tests/update_rules_bulk.ts rename to x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_update/basic_license_essentials_tier/update_rules_bulk.ts index ca4df7a4cd648..b92b62c37d721 100644 --- a/x-pack/test/detection_engine_api_integration/basic/tests/update_rules_bulk.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_update/basic_license_essentials_tier/update_rules_bulk.ts @@ -11,30 +11,34 @@ import { DETECTION_ENGINE_RULES_BULK_UPDATE, DETECTION_ENGINE_RULES_URL, } from '@kbn/security-solution-plugin/common/constants'; -import { FtrProviderContext } from '../../common/ftr_provider_context'; +import { FtrProviderContext } from '../../../../../ftr_provider_context'; import { - createSignalsIndex, - deleteAllRules, getSimpleRuleOutput, removeServerGeneratedProperties, getSimpleRuleOutputWithoutRuleId, removeServerGeneratedPropertiesIncludingRuleId, getSimpleRuleUpdate, - createRule, getSimpleRule, + updateUsername, +} from '../../../utils'; +import { + createAlertsIndex, + deleteAllRules, + createRule, deleteAllAlerts, -} from '../../utils'; +} from '../../../../../../common/utils/security_solution'; -// eslint-disable-next-line import/no-default-export export default ({ getService }: FtrProviderContext) => { const supertest = getService('supertest'); const log = getService('log'); const es = getService('es'); + const config = getService('config'); + const ELASTICSEARCH_USERNAME = config.get('servers.kibana.username'); - describe('update_rules_bulk', () => { + describe('@ess @serverless update_rules_bulk', () => { describe('update rules bulk', () => { beforeEach(async () => { - await createSignalsIndex(supertest, log); + await createAlertsIndex(supertest, log); }); afterEach(async () => { @@ -59,8 +63,10 @@ export default ({ getService }: FtrProviderContext) => { const outputRule = getSimpleRuleOutput(); outputRule.name = 'some other name'; outputRule.revision = 1; + const expectedRule = updateUsername(outputRule, ELASTICSEARCH_USERNAME); + const bodyToCompare = removeServerGeneratedProperties(body[0]); - expect(bodyToCompare).to.eql(outputRule); + expect(bodyToCompare).to.eql(expectedRule); }); it('should update two rule properties of name using the two rules rule_id', async () => { @@ -91,15 +97,17 @@ export default ({ getService }: FtrProviderContext) => { const outputRule1 = getSimpleRuleOutput(); outputRule1.name = 'some other name'; outputRule1.revision = 1; + const expectedRule = updateUsername(outputRule1, ELASTICSEARCH_USERNAME); const outputRule2 = getSimpleRuleOutput('rule-2'); outputRule2.name = 'some other name'; outputRule2.revision = 1; + const expectedRule2 = updateUsername(outputRule2, ELASTICSEARCH_USERNAME); const bodyToCompare1 = removeServerGeneratedProperties(body[0]); const bodyToCompare2 = removeServerGeneratedProperties(body[1]); - expect(bodyToCompare1).to.eql(outputRule1); - expect(bodyToCompare2).to.eql(outputRule2); + expect(bodyToCompare1).to.eql(expectedRule); + expect(bodyToCompare2).to.eql(expectedRule2); }); it('should update a single rule property of name using an id', async () => { @@ -121,8 +129,10 @@ export default ({ getService }: FtrProviderContext) => { const outputRule = getSimpleRuleOutput(); outputRule.name = 'some other name'; outputRule.revision = 1; + const expectedRule = updateUsername(outputRule, ELASTICSEARCH_USERNAME); + const bodyToCompare = removeServerGeneratedProperties(body[0]); - expect(bodyToCompare).to.eql(outputRule); + expect(bodyToCompare).to.eql(expectedRule); }); it('should update two rule properties of name using the two rules id', async () => { @@ -150,15 +160,17 @@ export default ({ getService }: FtrProviderContext) => { const outputRule1 = getSimpleRuleOutputWithoutRuleId('rule-1'); outputRule1.name = 'some other name'; outputRule1.revision = 1; + const expectedRule = updateUsername(outputRule1, ELASTICSEARCH_USERNAME); const outputRule2 = getSimpleRuleOutputWithoutRuleId('rule-2'); outputRule2.name = 'some other name'; outputRule2.revision = 1; + const expectedRule2 = updateUsername(outputRule2, ELASTICSEARCH_USERNAME); const bodyToCompare1 = removeServerGeneratedPropertiesIncludingRuleId(body[0]); const bodyToCompare2 = removeServerGeneratedPropertiesIncludingRuleId(body[1]); - expect(bodyToCompare1).to.eql(outputRule1); - expect(bodyToCompare2).to.eql(outputRule2); + expect(bodyToCompare1).to.eql(expectedRule); + expect(bodyToCompare2).to.eql(expectedRule2); }); it('should update a single rule property of name using the auto-generated id', async () => { @@ -180,8 +192,10 @@ export default ({ getService }: FtrProviderContext) => { const outputRule = getSimpleRuleOutput(); outputRule.name = 'some other name'; outputRule.revision = 1; + const expectedRule = updateUsername(outputRule, ELASTICSEARCH_USERNAME); + const bodyToCompare = removeServerGeneratedProperties(body[0]); - expect(bodyToCompare).to.eql(outputRule); + expect(bodyToCompare).to.eql(expectedRule); }); it('should change the revision of a rule when it updates enabled and another property', async () => { @@ -203,9 +217,10 @@ export default ({ getService }: FtrProviderContext) => { outputRule.enabled = false; outputRule.severity = 'low'; outputRule.revision = 1; + const expectedRule = updateUsername(outputRule, ELASTICSEARCH_USERNAME); const bodyToCompare = removeServerGeneratedProperties(body[0]); - expect(bodyToCompare).to.eql(outputRule); + expect(bodyToCompare).to.eql(expectedRule); }); it('should change other properties when it does updates and effectively delete them such as timeline_title', async () => { @@ -237,9 +252,10 @@ export default ({ getService }: FtrProviderContext) => { const outputRule = getSimpleRuleOutput(); outputRule.name = 'some other name'; outputRule.revision = 2; + const expectedRule = updateUsername(outputRule, ELASTICSEARCH_USERNAME); const bodyToCompare = removeServerGeneratedProperties(body[0]); - expect(bodyToCompare).to.eql(outputRule); + expect(bodyToCompare).to.eql(expectedRule); }); it('should return a 200 but give a 404 in the message if it is given a fake id', async () => { @@ -307,10 +323,11 @@ export default ({ getService }: FtrProviderContext) => { const outputRule = getSimpleRuleOutput(); outputRule.name = 'some other name'; outputRule.revision = 1; + const expectedRule = updateUsername(outputRule, ELASTICSEARCH_USERNAME); const bodyToCompare = removeServerGeneratedProperties(body[0]); expect([bodyToCompare, body[1]]).to.eql([ - outputRule, + expectedRule, { error: { message: 'rule_id: "fake_id" not found', @@ -345,10 +362,11 @@ export default ({ getService }: FtrProviderContext) => { const outputRule = getSimpleRuleOutput(); outputRule.name = 'some other name'; outputRule.revision = 1; + const expectedRule = updateUsername(outputRule, ELASTICSEARCH_USERNAME); const bodyToCompare = removeServerGeneratedProperties(body[0]); expect([bodyToCompare, body[1]]).to.eql([ - outputRule, + expectedRule, { error: { message: 'id: "b3aa019a-656c-4311-b13b-4d9852e24347" not found', diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_update/trial_license_complete_tier/configs/serverless.config.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_update/trial_license_complete_tier/configs/serverless.config.ts index 4fe157e55efb9..a0e033e1b7a35 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_update/trial_license_complete_tier/configs/serverless.config.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_update/trial_license_complete_tier/configs/serverless.config.ts @@ -9,7 +9,6 @@ import { createTestConfig } from '../../../../../../config/serverless/config.bas export default createTestConfig({ testFiles: [require.resolve('..')], junit: { - reportName: - 'Rules Management - Rule Update Integration Tests - Serverless Env - Complete License', + reportName: 'Rules Management - Rule Update Integration Tests - Serverless Env - Complete Tier', }, }); diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_update/trial_license_complete_tier/update_rules.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_update/trial_license_complete_tier/update_rules.ts index 94d2742ba3587..b341be9379914 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_update/trial_license_complete_tier/update_rules.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_update/trial_license_complete_tier/update_rules.ts @@ -16,9 +16,6 @@ import { RuleActionArray, RuleActionThrottle } from '@kbn/securitysolution-io-ts import { ExceptionListTypeEnum } from '@kbn/securitysolution-io-ts-list-types'; import { - createAlertsIndex, - deleteAllRules, - deleteAllAlerts, getSimpleRuleOutput, removeServerGeneratedProperties, removeServerGeneratedPropertiesIncludingRuleId, @@ -28,7 +25,6 @@ import { getSimpleRuleUpdate, getSimpleMlRuleUpdate, getSimpleSavedQueryRule, - createRule, getSimpleRule, getThresholdRuleForAlertTesting, getSimpleRuleWithoutRuleId, @@ -38,6 +34,12 @@ import { getActionsWithoutFrequencies, getSomeActionsWithFrequencies, } from '../../../utils'; +import { + createAlertsIndex, + deleteAllRules, + deleteAllAlerts, + createRule, +} from '../../../../../../common/utils/security_solution'; import { FtrProviderContext } from '../../../../../ftr_provider_context'; export default ({ getService }: FtrProviderContext) => { diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_update/trial_license_complete_tier/update_rules_bulk.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_update/trial_license_complete_tier/update_rules_bulk.ts index 85a0daabfe861..d0f957149efc2 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_update/trial_license_complete_tier/update_rules_bulk.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_update/trial_license_complete_tier/update_rules_bulk.ts @@ -19,13 +19,9 @@ import { import { ExceptionListTypeEnum } from '@kbn/securitysolution-io-ts-list-types'; import { RuleActionArray, RuleActionThrottle } from '@kbn/securitysolution-io-ts-alerting-types'; import { - createAlertsIndex, - deleteAllRules, - deleteAllAlerts, getSimpleRuleOutput, removeServerGeneratedProperties, getSimpleRuleUpdate, - createRule, getSimpleRule, createLegacyRuleAction, getLegacyActionSO, @@ -42,6 +38,12 @@ import { getSomeActionsWithFrequencies, updateUsername, } from '../../../utils'; +import { + createAlertsIndex, + deleteAllRules, + deleteAllAlerts, + createRule, +} from '../../../../../../common/utils/security_solution'; import { FtrProviderContext } from '../../../../../ftr_provider_context'; export default ({ getService }: FtrProviderContext) => { diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_update/trial_license_complete_tier/update_rules_ess.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_update/trial_license_complete_tier/update_rules_ess.ts index 8f01ea1d01105..12303e53f8cc2 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_update/trial_license_complete_tier/update_rules_ess.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_update/trial_license_complete_tier/update_rules_ess.ts @@ -11,14 +11,10 @@ import { BaseRuleParams } from '@kbn/security-solution-plugin/server/lib/detecti import { DETECTION_ENGINE_RULES_URL } from '@kbn/security-solution-plugin/common/constants'; import { - createAlertsIndex, - deleteAllRules, - deleteAllAlerts, removeServerGeneratedProperties, removeServerGeneratedPropertiesIncludingRuleId, getSimpleRuleOutputWithoutRuleId, getSimpleRuleUpdate, - createRule, getSimpleRule, createLegacyRuleAction, getLegacyActionSO, @@ -28,6 +24,12 @@ import { getRuleSavedObjectWithLegacyInvestigationFieldsEmptyArray, updateUsername, } from '../../../utils'; +import { + createAlertsIndex, + deleteAllRules, + deleteAllAlerts, + createRule, +} from '../../../../../../common/utils/security_solution'; import { FtrProviderContext } from '../../../../../ftr_provider_context'; export default ({ getService }: FtrProviderContext) => { diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/telemetry/trial_license_complete_tier/task_based/all_types.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/telemetry/trial_license_complete_tier/task_based/all_types.ts index 64b4897dbc8a3..adaed08158794 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/telemetry/trial_license_complete_tier/task_based/all_types.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/telemetry/trial_license_complete_tier/task_based/all_types.ts @@ -7,13 +7,12 @@ import expect from '@kbn/expect'; +import { getSecurityTelemetryStats, removeTimeFieldsFromTelemetryStats } from '../../../utils'; import { createAlertsIndex, deleteAllRules, deleteAllAlerts, - getSecurityTelemetryStats, - removeTimeFieldsFromTelemetryStats, -} from '../../../utils'; +} from '../../../../../../common/utils/security_solution'; import { deleteAllExceptions } from '../../../../lists_and_exception_lists/utils'; import { FtrProviderContext } from '../../../../../ftr_provider_context'; diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/telemetry/trial_license_complete_tier/task_based/detection_rules.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/telemetry/trial_license_complete_tier/task_based/detection_rules.ts index 069484a338b3b..48f8be9a3d2c5 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/telemetry/trial_license_complete_tier/task_based/detection_rules.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/telemetry/trial_license_complete_tier/task_based/detection_rules.ts @@ -11,18 +11,20 @@ import expect from '@kbn/expect'; import { DETECTION_ENGINE_RULES_URL } from '@kbn/security-solution-plugin/common/constants'; import { ELASTIC_SECURITY_RULE_ID } from '@kbn/security-solution-plugin/common'; import { - createRule, - createAlertsIndex, - deleteAllRules, - deleteAllAlerts, fetchRule, - getRuleForAlertTesting, installMockPrebuiltRules, getSecurityTelemetryStats, createExceptionList, createExceptionListItem, removeTimeFieldsFromTelemetryStats, } from '../../../utils'; +import { + createRule, + createAlertsIndex, + deleteAllRules, + deleteAllAlerts, + getRuleForAlertTesting, +} from '../../../../../../common/utils/security_solution'; import { deleteAllExceptions } from '../../../../lists_and_exception_lists/utils'; import { FtrProviderContext } from '../../../../../ftr_provider_context'; diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/telemetry/trial_license_complete_tier/task_based/security_lists.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/telemetry/trial_license_complete_tier/task_based/security_lists.ts index 3a4e8cb4c3ea3..088016d868caa 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/telemetry/trial_license_complete_tier/task_based/security_lists.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/telemetry/trial_license_complete_tier/task_based/security_lists.ts @@ -12,14 +12,16 @@ import { ENDPOINT_TRUSTED_APPS_LIST_ID, } from '@kbn/securitysolution-list-constants'; import { - createAlertsIndex, - deleteAllRules, - deleteAllAlerts, getSecurityTelemetryStats, createExceptionListItem, createExceptionList, removeTimeFieldsFromTelemetryStats, } from '../../../utils'; +import { + createAlertsIndex, + deleteAllRules, + deleteAllAlerts, +} from '../../../../../../common/utils/security_solution'; import { deleteAllExceptions } from '../../../../lists_and_exception_lists/utils'; import { FtrProviderContext } from '../../../../../ftr_provider_context'; diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/telemetry/trial_license_complete_tier/usage_collector/all_types.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/telemetry/trial_license_complete_tier/usage_collector/all_types.ts index 93e16cefa19ff..a3f893fff8aa9 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/telemetry/trial_license_complete_tier/usage_collector/all_types.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/telemetry/trial_license_complete_tier/usage_collector/all_types.ts @@ -8,7 +8,13 @@ import expect from '@kbn/expect'; import { getInitialDetectionMetrics } from '@kbn/security-solution-plugin/server/usage/detections/get_initial_usage'; -import { createAlertsIndex, deleteAllRules, deleteAllAlerts, getStats } from '../../../utils'; +import { getStats } from '../../../utils'; +import { + createAlertsIndex, + deleteAllRules, + deleteAllAlerts, +} from '../../../../../../common/utils/security_solution'; + import { FtrProviderContext } from '../../../../../ftr_provider_context'; export default ({ getService }: FtrProviderContext) => { diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/telemetry/trial_license_complete_tier/usage_collector/detection_rule_status.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/telemetry/trial_license_complete_tier/usage_collector/detection_rule_status.ts index 6007c3d37c6dc..317956d5b7dcd 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/telemetry/trial_license_complete_tier/usage_collector/detection_rule_status.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/telemetry/trial_license_complete_tier/usage_collector/detection_rule_status.ts @@ -20,20 +20,22 @@ import { getInitialSingleEventLogUsage, getInitialSingleEventMetric, } from '@kbn/security-solution-plugin/server/usage/detections/rules/get_initial_usage'; +import { + getEqlRuleForAlertTesting, + getSimpleThreatMatch, + getStats, + getThresholdRuleForAlertTesting, + deleteAllEventLogExecutionEvents, +} from '../../../utils'; import { createRule, createAlertsIndex, deleteAllRules, deleteAllAlerts, - getEqlRuleForAlertTesting, getRuleForAlertTesting, - getSimpleThreatMatch, - getStats, - getThresholdRuleForAlertTesting, waitForRuleSuccess, waitForAlertsToBePresent, - deleteAllEventLogExecutionEvents, -} from '../../../utils'; +} from '../../../../../../common/utils/security_solution'; import { FtrProviderContext } from '../../../../../ftr_provider_context'; export default ({ getService }: FtrProviderContext) => { diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/telemetry/trial_license_complete_tier/usage_collector/detection_rules.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/telemetry/trial_license_complete_tier/usage_collector/detection_rules.ts index b81ced41b2511..2772b3ebde34e 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/telemetry/trial_license_complete_tier/usage_collector/detection_rules.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/telemetry/trial_license_complete_tier/usage_collector/detection_rules.ts @@ -17,13 +17,8 @@ import { RulesTypeUsage } from '@kbn/security-solution-plugin/server/usage/detec import { createLegacyRuleAction, createWebHookRuleAction, - createRule, - createAlertsIndex, - deleteAllRules, - deleteAllAlerts, getEqlRuleForAlertTesting, fetchRule, - getRuleForAlertTesting, getRuleWithWebHookAction, getSimpleMlRule, getSimpleRule, @@ -31,14 +26,21 @@ import { getStats, getThresholdRuleForAlertTesting, installMockPrebuiltRules, - waitForRuleSuccess, - waitForAlertsToBePresent, updateRule, deleteAllEventLogExecutionEvents, getRuleSavedObjectWithLegacyInvestigationFields, getRuleSavedObjectWithLegacyInvestigationFieldsEmptyArray, createRuleThroughAlertingEndpoint, } from '../../../utils'; +import { + createRule, + createAlertsIndex, + deleteAllRules, + deleteAllAlerts, + waitForRuleSuccess, + waitForAlertsToBePresent, + getRuleForAlertTesting, +} from '../../../../../../common/utils/security_solution'; import { FtrProviderContext } from '../../../../../ftr_provider_context'; diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/telemetry/trial_license_complete_tier/usage_collector/detection_rules_legacy_action.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/telemetry/trial_license_complete_tier/usage_collector/detection_rules_legacy_action.ts index b0e40253bb8c2..e34ee9c2005ef 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/telemetry/trial_license_complete_tier/usage_collector/detection_rules_legacy_action.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/telemetry/trial_license_complete_tier/usage_collector/detection_rules_legacy_action.ts @@ -17,13 +17,8 @@ import { RulesTypeUsage } from '@kbn/security-solution-plugin/server/usage/detec import { createLegacyRuleAction, createWebHookRuleAction, - createRule, - createAlertsIndex, - deleteAllRules, - deleteAllAlerts, getEqlRuleForAlertTesting, fetchRule, - getRuleForAlertTesting, getRuleWithWebHookAction, getSimpleMlRule, getSimpleRule, @@ -31,11 +26,18 @@ import { getStats, getThresholdRuleForAlertTesting, installMockPrebuiltRules, - waitForRuleSuccess, - waitForAlertsToBePresent, updateRule, deleteAllEventLogExecutionEvents, } from '../../../utils'; +import { + createRule, + createAlertsIndex, + deleteAllRules, + deleteAllAlerts, + getRuleForAlertTesting, + waitForRuleSuccess, + waitForAlertsToBePresent, +} from '../../../../../../common/utils/security_solution'; import { FtrProviderContext } from '../../../../../ftr_provider_context'; diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/alerts/get_open_alerts.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/alerts/get_open_alerts.ts index 26e2459d7e2a3..66370e6236bf9 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/alerts/get_open_alerts.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/alerts/get_open_alerts.ts @@ -14,9 +14,8 @@ import { } from '@kbn/security-solution-plugin/common/api/detection_engine/rule_monitoring'; import type { RuleResponse } from '@kbn/security-solution-plugin/common/api/detection_engine'; -import { waitForRuleStatus } from '../rules'; import { refreshIndex } from '..'; -import { getAlertsByIds } from './get_alerts_by_ids'; +import { getAlertsByIds, waitForRuleStatus } from '../../../../../common/utils/security_solution'; export const getOpenAlerts = async ( supertest: SuperTest.SuperTest, diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/alerts/index.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/alerts/index.ts index 867f85653ef4f..2dba313d1bbcf 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/alerts/index.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/alerts/index.ts @@ -5,19 +5,12 @@ * 2.0. */ -export * from './create_alerts_index'; -export * from './delete_all_alerts'; export * from './wait_for_alert_to_complete'; -export * from './wait_for_alerts_to_be_present'; export * from './wait_for_alert_to_complete'; export * from './get_open_alerts'; -export * from './get_alerts_by_ids'; -export * from './get_query_alerts_ids'; -export * from './get_alerts_by_id'; export * from './remove_random_valued_properties_from_alert'; export * from './set_alert_status'; export * from './get_alert_status_empty_response'; -export * from './get_query_alert_ids'; export * from './set_alert_tags'; export * from './get_preview_alerts'; export * from './get_alert_status'; diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/alerts/wait_for_alert_to_complete.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/alerts/wait_for_alert_to_complete.ts index fc71c7fa50aaf..7d942198bc0d5 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/alerts/wait_for_alert_to_complete.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/alerts/wait_for_alert_to_complete.ts @@ -8,7 +8,7 @@ import type { ToolingLog } from '@kbn/tooling-log'; import type SuperTest from 'supertest'; -import { waitFor } from '../wait_for'; +import { waitFor } from '../../../../../common/utils/security_solution'; export const waitForAlertToComplete = async ( supertest: SuperTest.SuperTest, diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/count_down_es.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/count_down_es.ts index cfbcafbc06cb6..a5dfc937e4c35 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/count_down_es.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/count_down_es.ts @@ -7,7 +7,7 @@ import type { TransportResult } from '@elastic/elasticsearch'; import type { ToolingLog } from '@kbn/tooling-log'; -import { countDownTest } from './count_down_test'; +import { countDownTest } from '../../../../common/utils/security_solution'; /** * Does a plain countdown and checks against es queries for either conflicts in the error diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/count_down_test.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/count_down_test.ts deleted file mode 100644 index 39292a9cbbbb7..0000000000000 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/count_down_test.ts +++ /dev/null @@ -1,78 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import type { ToolingLog } from '@kbn/tooling-log'; - -/** - * Does a plain countdown and checks against a boolean to determine if to wait and try again. - * This is useful for over the wire things that can cause issues such as conflict or timeouts - * for testing resiliency. - * @param functionToTest The function to test against - * @param name The name of the function to print if we encounter errors - * @param log The tooling logger - * @param retryCount The number of times to retry before giving up (has default) - * @param timeoutWait Time to wait before trying again (has default) - */ -export const countDownTest = async ( - functionToTest: () => Promise<{ - passed: boolean; - returnValue?: T | undefined; - errorMessage?: string; - }>, - name: string, - log: ToolingLog, - retryCount: number = 50, - timeoutWait = 250, - ignoreThrow: boolean = false -): Promise => { - if (retryCount > 0) { - try { - const testReturn = await functionToTest(); - if (!testReturn.passed) { - const error = testReturn.errorMessage != null ? ` error: ${testReturn.errorMessage},` : ''; - log.error(`Failure trying to ${name},${error} retries left are: ${retryCount - 1}`); - // retry, counting down, and delay a bit before - await new Promise((resolve) => setTimeout(resolve, timeoutWait)); - const returnValue = await countDownTest( - functionToTest, - name, - log, - retryCount - 1, - timeoutWait, - ignoreThrow - ); - return returnValue; - } else { - return testReturn.returnValue; - } - } catch (err) { - if (ignoreThrow) { - throw err; - } else { - log.error( - `Failure trying to ${name}, with exception message of: ${ - err.message - }, retries left are: ${retryCount - 1}` - ); - // retry, counting down, and delay a bit before - await new Promise((resolve) => setTimeout(resolve, timeoutWait)); - const returnValue = await countDownTest( - functionToTest, - name, - log, - retryCount - 1, - timeoutWait, - ignoreThrow - ); - return returnValue; - } - } - } else { - log.error(`Could not ${name}, no retries are left`); - return undefined; - } -}; diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/event_log/wait_for_event_log_execute_complete.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/event_log/wait_for_event_log_execute_complete.ts index 58ac05c9ee3df..584fddced9db6 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/event_log/wait_for_event_log_execute_complete.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/event_log/wait_for_event_log_execute_complete.ts @@ -8,7 +8,7 @@ import type { ToolingLog } from '@kbn/tooling-log'; import type { Client } from '@elastic/elasticsearch'; -import { waitFor } from '../wait_for'; +import { waitFor } from '../../../../../common/utils/security_solution'; import { getEventLogExecuteCompleteById } from './get_event_log_execute_complete_by_id'; /** diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/exception_list_and_item/list/create_container_with_endpoint_entries.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/exception_list_and_item/list/create_container_with_endpoint_entries.ts index 9c48f80019c4e..7541514448b5c 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/exception_list_and_item/list/create_container_with_endpoint_entries.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/exception_list_and_item/list/create_container_with_endpoint_entries.ts @@ -16,7 +16,7 @@ import type { import { EXCEPTION_LIST_ITEM_URL } from '@kbn/securitysolution-list-constants'; import { createExceptionListItem } from '../item/create_exception_list_item'; -import { waitFor } from '../../wait_for'; +import { waitFor } from '../../../../../../common/utils/security_solution'; import { createExceptionList } from './create_exception_list'; /** diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/exception_list_and_item/list/create_container_with_entries.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/exception_list_and_item/list/create_container_with_entries.ts index dba2a1e1e3276..973e0d1962a75 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/exception_list_and_item/list/create_container_with_entries.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/exception_list_and_item/list/create_container_with_entries.ts @@ -13,7 +13,7 @@ import type { ListArray, NonEmptyEntriesArray } from '@kbn/securitysolution-io-t import { EXCEPTION_LIST_ITEM_URL } from '@kbn/securitysolution-list-constants'; import { createExceptionList } from './create_exception_list'; import { createExceptionListItem } from '../item/create_exception_list_item'; -import { waitFor } from '../../wait_for'; +import { waitFor } from '../../../../../../common/utils/security_solution'; /** * Convenience testing function where you can pass in just the endpoint entries and you will diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/index.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/index.ts index 415569827b85d..d51fce39e3410 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/index.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/index.ts @@ -15,12 +15,9 @@ export * from './machine_learning'; export * from './binary_to_string'; export * from './get_index_name_from_load'; -export * from './count_down_test'; export * from './count_down_es'; export * from './update_username'; export * from './refresh_index'; -export * from './wait_for'; -export * from './route_with_namespace'; export * from './wait_for_index_to_populate'; export * from './get_stats'; export * from './get_detection_metrics_from_body'; diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/route_with_namespace.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/route_with_namespace.ts deleted file mode 100644 index 07e5c4a8049e2..0000000000000 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/route_with_namespace.ts +++ /dev/null @@ -1,14 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -/** - * Generates a route string with an optional namespace. - * @param route the route string - * @param namespace [optional] the namespace to account for in the route - */ -export const routeWithNamespace = (route: string, namespace?: string) => - namespace ? `/s/${namespace}${route}` : route; diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/create_rule_with_exception_entries.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/create_rule_with_exception_entries.ts index ea608c48e7b8b..ca2a8129b7713 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/create_rule_with_exception_entries.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/create_rule_with_exception_entries.ts @@ -18,7 +18,7 @@ import { createContainerWithEntries, createContainerWithEndpointEntries, } from '../exception_list_and_item'; -import { createRule } from './create_rule'; +import { createRule } from '../../../../../common/utils/security_solution'; /** * Convenience testing function where you can pass in just the entries and you will diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/delete_rule.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/delete_rule.ts deleted file mode 100644 index f4eff397aba0b..0000000000000 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/delete_rule.ts +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import type SuperTest from 'supertest'; -import type { RuleResponse } from '@kbn/security-solution-plugin/common/api/detection_engine'; - -import { DETECTION_ENGINE_RULES_URL } from '@kbn/security-solution-plugin/common/constants'; - -/** - * Helper to cut down on the noise in some of the tests. Does a delete of a rule. - * It does not check for a 200 "ok" on this. - * @param supertest The supertest deps - * @param ruleId The rule id to delete - */ -export const deleteRule = async ( - supertest: SuperTest.SuperTest, - ruleId: string -): Promise => { - const response = await supertest - .delete(`${DETECTION_ENGINE_RULES_URL}?rule_id=${ruleId}`) - .set('kbn-xsrf', 'true') - .set('elastic-api-version', '2023-10-31') - .expect(200); - - return response.body; -}; diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/get_eql_rule_for_alert_testing.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/get_eql_rule_for_alert_testing.ts index b8253e0f9afec..a371250dfc36b 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/get_eql_rule_for_alert_testing.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/get_eql_rule_for_alert_testing.ts @@ -6,7 +6,7 @@ */ import type { EqlRuleCreateProps } from '@kbn/security-solution-plugin/common/api/detection_engine'; -import { getRuleForAlertTesting } from './get_rule_for_alert_testing'; +import { getRuleForAlertTesting } from '../../../../../common/utils/security_solution'; /** * This is a typical alert testing rule that is easy for most basic testing of output of EQL alerts. diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/get_saved_query_rule_for_alert_testing.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/get_saved_query_rule_for_alert_testing.ts index 01feea137efb6..b83f728d980e1 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/get_saved_query_rule_for_alert_testing.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/get_saved_query_rule_for_alert_testing.ts @@ -6,7 +6,7 @@ */ import type { SavedQueryRuleCreateProps } from '@kbn/security-solution-plugin/common/api/detection_engine'; -import { getRuleForAlertTesting } from './get_rule_for_alert_testing'; +import { getRuleForAlertTesting } from '../../../../../common/utils/security_solution'; /** * This is a typical alert testing rule that is easy for most basic testing of output of Saved Query alerts. diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/get_threat_match_rule_for_alert_testing.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/get_threat_match_rule_for_alert_testing.ts index 5537929033dfd..638b951e539d2 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/get_threat_match_rule_for_alert_testing.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/get_threat_match_rule_for_alert_testing.ts @@ -6,7 +6,7 @@ */ import type { ThreatMatchRuleCreateProps } from '@kbn/security-solution-plugin/common/api/detection_engine'; -import { getRuleForAlertTesting } from './get_rule_for_alert_testing'; +import { getRuleForAlertTesting } from '../../../../../common/utils/security_solution'; /** * This is a typical alert testing rule that is easy for most basic testing of output of Threat Match alerts. diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/get_threshold_rule_for_alert_testing.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/get_threshold_rule_for_alert_testing.ts index a64aa04981c3a..b834f48d8b006 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/get_threshold_rule_for_alert_testing.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/get_threshold_rule_for_alert_testing.ts @@ -6,7 +6,7 @@ */ import type { ThresholdRuleCreateProps } from '@kbn/security-solution-plugin/common/api/detection_engine'; -import { getRuleForAlertTesting } from './get_rule_for_alert_testing'; +import { getRuleForAlertTesting } from '../../../../../common/utils/security_solution'; /** * This is a typical signal testing rule that is easy for most basic testing of output of Threshold alerts. diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/index.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/index.ts index 501a5579fbfde..7888dd6e9ef33 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/index.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/index.ts @@ -5,22 +5,18 @@ * 2.0. */ export * from './create_legacy_rule_action'; -export * from './create_rule'; export * from './create_rule_with_exception_entries'; export * from './create_rule_saved_object'; export * from './create_rule_with_auth'; export * from './create_non_security_rule'; export * from './check_investigation_field_in_so'; export * from './downgrade_immutable_rule'; -export * from './delete_all_rules'; -export * from './delete_rule'; export * from './fetch_rule'; export * from './find_immutable_rule_by_id'; export * from './get_simple_rule'; export * from './get_rule_params'; export * from './get_simple_rule_output'; export * from './get_simple_rule_update'; -export * from './get_rule_for_alert_testing'; export * from './get_threshold_rule_for_alert_testing'; export * from './get_rule_actions'; export * from './get_eql_rule_for_alert_testing'; @@ -52,7 +48,6 @@ export * from './remove_server_generated_properties'; export * from './remove_server_generated_properties_including_rule_id'; export * from './rule_to_update_schema'; export * from './update_rule'; -export * from './wait_for_rule_status'; export * from './get_rules_as_ndjson'; export * from './get_simple_rule_as_ndjson'; diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/wait_for_index_to_populate.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/wait_for_index_to_populate.ts index ceba42efd1793..41d78f9986a26 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/wait_for_index_to_populate.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/wait_for_index_to_populate.ts @@ -7,7 +7,7 @@ import type { ToolingLog } from '@kbn/tooling-log'; import type { Client } from '@elastic/elasticsearch'; -import { waitFor } from './wait_for'; +import { waitFor } from '../../../../common/utils/security_solution'; /** * Waits for the given index to contain documents diff --git a/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/risk_engine/trial_license_complete_tier/risk_score_calculation.ts b/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/risk_engine/trial_license_complete_tier/risk_score_calculation.ts index fe1c3df4af77c..6bc6230ad3a1f 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/risk_engine/trial_license_complete_tier/risk_score_calculation.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/risk_engine/trial_license_complete_tier/risk_score_calculation.ts @@ -11,11 +11,8 @@ import { X_ELASTIC_INTERNAL_ORIGIN_REQUEST } from '@kbn/core-http-common'; import { RISK_SCORE_CALCULATION_URL } from '@kbn/security-solution-plugin/common/constants'; import type { RiskScore } from '@kbn/security-solution-plugin/common/entity_analytics/risk_engine'; import { v4 as uuidv4 } from 'uuid'; -import { - deleteAllAlerts, - deleteAllRules, - dataGeneratorFactory, -} from '../../../detections_response/utils'; +import { dataGeneratorFactory } from '../../../detections_response/utils'; +import { deleteAllAlerts, deleteAllRules } from '../../../../../common/utils/security_solution'; import { buildDocument, createAndSyncRuleAndAlertsFactory, diff --git a/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/risk_engine/trial_license_complete_tier/risk_score_preview.ts b/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/risk_engine/trial_license_complete_tier/risk_score_preview.ts index bfb415bac02a8..0577a1ac6d365 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/risk_engine/trial_license_complete_tier/risk_score_preview.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/risk_engine/trial_license_complete_tier/risk_score_preview.ts @@ -11,12 +11,12 @@ import { RISK_SCORE_PREVIEW_URL } from '@kbn/security-solution-plugin/common/con import type { RiskScore } from '@kbn/security-solution-plugin/common/entity_analytics/risk_engine'; import { v4 as uuidv4 } from 'uuid'; import { X_ELASTIC_INTERNAL_ORIGIN_REQUEST } from '@kbn/core-http-common'; +import { dataGeneratorFactory } from '../../../detections_response/utils'; import { createAlertsIndex, deleteAllAlerts, deleteAllRules, - dataGeneratorFactory, -} from '../../../detections_response/utils'; +} from '../../../../../common/utils/security_solution'; import { assetCriticalityRouteHelpersFactory, buildDocument, diff --git a/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/risk_engine/trial_license_complete_tier/risk_scoring_task/task_execution.ts b/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/risk_engine/trial_license_complete_tier/risk_scoring_task/task_execution.ts index b735852886a7d..795cd8fd067dc 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/risk_engine/trial_license_complete_tier/risk_scoring_task/task_execution.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/risk_engine/trial_license_complete_tier/risk_scoring_task/task_execution.ts @@ -7,11 +7,8 @@ import expect from '@kbn/expect'; import { v4 as uuidv4 } from 'uuid'; -import { - deleteAllAlerts, - deleteAllRules, - dataGeneratorFactory, -} from '../../../../detections_response/utils'; +import { dataGeneratorFactory } from '../../../../detections_response/utils'; +import { deleteAllRules, deleteAllAlerts } from '../../../../../../common/utils/security_solution'; import { buildDocument, createAndSyncRuleAndAlertsFactory, diff --git a/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/risk_engine/trial_license_complete_tier/risk_scoring_task/task_execution_nondefault_spaces.ts b/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/risk_engine/trial_license_complete_tier/risk_scoring_task/task_execution_nondefault_spaces.ts index d869fae2f3f95..f61e64ba89bbf 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/risk_engine/trial_license_complete_tier/risk_scoring_task/task_execution_nondefault_spaces.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/risk_engine/trial_license_complete_tier/risk_scoring_task/task_execution_nondefault_spaces.ts @@ -7,11 +7,8 @@ import expect from '@kbn/expect'; import { v4 as uuidv4 } from 'uuid'; -import { - deleteAllAlerts, - deleteAllRules, - dataGeneratorFactory, -} from '../../../../detections_response/utils'; +import { dataGeneratorFactory } from '../../../../detections_response/utils'; +import { deleteAllRules, deleteAllAlerts } from '../../../../../../common/utils/security_solution'; import { buildDocument, createAndSyncRuleAndAlertsFactory, diff --git a/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/risk_engine/trial_license_complete_tier/telemetry_usage.ts b/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/risk_engine/trial_license_complete_tier/telemetry_usage.ts index f68a8c1dd3e60..78fabff6d22f1 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/risk_engine/trial_license_complete_tier/telemetry_usage.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/risk_engine/trial_license_complete_tier/telemetry_usage.ts @@ -7,11 +7,8 @@ import expect from '@kbn/expect'; import { v4 as uuidv4 } from 'uuid'; -import { - deleteAllRules, - deleteAllAlerts, - dataGeneratorFactory, -} from '../../../detections_response/utils'; +import { dataGeneratorFactory } from '../../../detections_response/utils'; +import { deleteAllRules, deleteAllAlerts } from '../../../../../common/utils/security_solution'; import { buildDocument, createAndSyncRuleAndAlertsFactory, diff --git a/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/utils/asset_criticality.ts b/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/utils/asset_criticality.ts index 6abcb908f6083..acbccafee967f 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/utils/asset_criticality.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/utils/asset_criticality.ts @@ -19,7 +19,7 @@ import type { AssetCriticalityRecord } from '@kbn/security-solution-plugin/commo import type { Client } from '@elastic/elasticsearch'; import type { ToolingLog } from '@kbn/tooling-log'; import querystring from 'querystring'; -import { routeWithNamespace, waitFor } from '../../detections_response/utils'; +import { routeWithNamespace, waitFor } from '../../../../common/utils/security_solution'; export const getAssetCriticalityIndex = (namespace?: string) => `.asset-criticality.asset-criticality-${namespace ?? 'default'}`; diff --git a/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/utils/risk_engine.ts b/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/utils/risk_engine.ts index e8ff2d4e10240..4583d9d6c6772 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/utils/risk_engine.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/utils/risk_engine.ts @@ -35,7 +35,7 @@ import { countDownTest, waitFor, routeWithNamespace, -} from '../../detections_response/utils'; +} from '../../../../common/utils/security_solution'; const sanitizeScore = (score: Partial): Partial => { const { diff --git a/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/utils.ts b/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/utils.ts index be05bb5a47518..d45a77be0840b 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/utils.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/utils.ts @@ -33,7 +33,7 @@ import { ToolingLog } from '@kbn/tooling-log'; import { getImportListItemAsBuffer } from '@kbn/lists-plugin/common/schemas/request/import_list_item_schema.mock'; import { encodeHitVersion } from '@kbn/securitysolution-es-utils'; -import { countDownTest } from '../detections_response/utils'; +import { countDownTest } from '../../../common/utils/security_solution'; /** * Creates the lists and lists items index for use inside of beforeEach blocks of tests diff --git a/x-pack/test/detection_engine_api_integration/basic/tests/import_timelines.ts b/x-pack/test/timeline/security_and_spaces/tests/basic/import_timelines.ts similarity index 99% rename from x-pack/test/detection_engine_api_integration/basic/tests/import_timelines.ts rename to x-pack/test/timeline/security_and_spaces/tests/basic/import_timelines.ts index a1c34b51e12d5..4c9b82f0825cc 100644 --- a/x-pack/test/detection_engine_api_integration/basic/tests/import_timelines.ts +++ b/x-pack/test/timeline/security_and_spaces/tests/basic/import_timelines.ts @@ -9,8 +9,8 @@ import expect from '@kbn/expect'; import { TIMELINE_IMPORT_URL } from '@kbn/security-solution-plugin/common/constants'; -import { FtrProviderContext } from '../../common/ftr_provider_context'; -import { deleteAllTimelines } from '../../utils'; +import { FtrProviderContext } from '../../../common/ftr_provider_context'; +import { deleteAllTimelines } from '../../../utils'; // eslint-disable-next-line import/no-default-export export default ({ getService }: FtrProviderContext): void => { diff --git a/x-pack/test/detection_engine_api_integration/basic/tests/install_prepackaged_timelines.ts b/x-pack/test/timeline/security_and_spaces/tests/basic/install_prepackaged_timelines.ts similarity index 87% rename from x-pack/test/detection_engine_api_integration/basic/tests/install_prepackaged_timelines.ts rename to x-pack/test/timeline/security_and_spaces/tests/basic/install_prepackaged_timelines.ts index 6047bc21d0c39..fddc9317dcff9 100644 --- a/x-pack/test/detection_engine_api_integration/basic/tests/install_prepackaged_timelines.ts +++ b/x-pack/test/timeline/security_and_spaces/tests/basic/install_prepackaged_timelines.ts @@ -8,14 +8,8 @@ import expect from '@kbn/expect'; import { TIMELINE_PREPACKAGED_URL } from '@kbn/security-solution-plugin/common/constants'; -import { FtrProviderContext } from '../../common/ftr_provider_context'; -import { - createSignalsIndex, - deleteAllRules, - deleteAllTimelines, - deleteAllAlerts, - waitFor, -} from '../../utils'; +import { FtrProviderContext } from '../../../common/ftr_provider_context'; +import { deleteAllTimelines, waitFor } from '../../../utils'; // eslint-disable-next-line import/no-default-export export default ({ getService }: FtrProviderContext): void => { @@ -25,13 +19,7 @@ export default ({ getService }: FtrProviderContext): void => { describe('install_prepackaged_timelines', () => { describe('creating prepackaged rules', () => { - beforeEach(async () => { - await createSignalsIndex(supertest, log); - }); - afterEach(async () => { - await deleteAllAlerts(supertest, log, es); - await deleteAllRules(supertest, log); await deleteAllTimelines(es); }); diff --git a/x-pack/test/detection_engine_api_integration/utils/delete_all_timelines.ts b/x-pack/test/timeline/utils/delete_all_timelines.ts similarity index 100% rename from x-pack/test/detection_engine_api_integration/utils/delete_all_timelines.ts rename to x-pack/test/timeline/utils/delete_all_timelines.ts diff --git a/x-pack/test/detection_engine_api_integration/basic/config.ts b/x-pack/test/timeline/utils/index.ts similarity index 53% rename from x-pack/test/detection_engine_api_integration/basic/config.ts rename to x-pack/test/timeline/utils/index.ts index 26fdc62e0ec52..d43b824d2b428 100644 --- a/x-pack/test/detection_engine_api_integration/basic/config.ts +++ b/x-pack/test/timeline/utils/index.ts @@ -5,13 +5,5 @@ * 2.0. */ -import { createTestConfig } from '../common/config'; - -// eslint-disable-next-line import/no-default-export -export default createTestConfig( - { - license: 'basic', - ssl: true, - }, - [require.resolve('./tests')] -); +export * from './delete_all_timelines'; +export * from './wait_for'; diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/wait_for.ts b/x-pack/test/timeline/utils/wait_for.ts similarity index 100% rename from x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/wait_for.ts rename to x-pack/test/timeline/utils/wait_for.ts From 82c48aedbe3928df29703019b7dec03141252e12 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=B8ren=20Louv-Jansen?= Date: Tue, 13 Feb 2024 10:01:34 +0100 Subject: [PATCH 45/83] [Obs AI Assistant] Add link for AI Assistant in Observability left hand nav (#176144) ![image](https://github.com/elastic/kibana/assets/209966/63854f4d-6e4e-4e22-a431-840cea269a3f) --- x-pack/plugins/observability/public/plugin.ts | 102 +++++++++++------- 1 file changed, 63 insertions(+), 39 deletions(-) diff --git a/x-pack/plugins/observability/public/plugin.ts b/x-pack/plugins/observability/public/plugin.ts index 5d6e825d83c43..2eb60290dbb97 100644 --- a/x-pack/plugins/observability/public/plugin.ts +++ b/x-pack/plugins/observability/public/plugin.ts @@ -45,7 +45,7 @@ import { TriggersAndActionsUIPublicPluginStart, } from '@kbn/triggers-actions-ui-plugin/public'; import { BehaviorSubject, from } from 'rxjs'; -import { map } from 'rxjs/operators'; +import { map, mergeMap } from 'rxjs/operators'; import { AiopsPluginStart } from '@kbn/aiops-plugin/public/types'; import type { EmbeddableSetup } from '@kbn/embeddable-plugin/public'; @@ -390,47 +390,71 @@ export class Plugin pluginsSetup.observabilityShared.navigation.registerSections( from(appUpdater$).pipe( - map((value) => { - const deepLinks = value(app)?.deepLinks ?? []; + mergeMap((value) => + from(coreSetup.getStartServices()).pipe( + map(([coreStart, pluginsStart]) => { + const deepLinks = value(app)?.deepLinks ?? []; + + const overviewLink = !Boolean(pluginsSetup.serverless) + ? [ + { + label: i18n.translate('xpack.observability.overviewLinkTitle', { + defaultMessage: 'Overview', + }), + app: observabilityAppId, + path: OVERVIEW_PATH, + }, + ] + : []; + + const isAiAssistantEnabled = + pluginsStart.observabilityAIAssistant.service.isEnabled(); + + console.log({ isAiAssistantEnabled }); + + const aiAssistantLink = + isAiAssistantEnabled && + !Boolean(pluginsSetup.serverless) && + Boolean(pluginsSetup.observabilityAIAssistant) + ? [ + { + label: i18n.translate('xpack.observability.aiAssistantLinkTitle', { + defaultMessage: 'AI Assistant', + }), + app: 'observabilityAIAssistant', + path: '/conversations/new', + }, + ] + : []; + + // Reformat the visible links to be NavigationEntry objects instead of + // AppDeepLink objects. + // + // In our case the deep links and sections being registered are the + // same, and the logic to hide them based on flags or capabilities is + // the same, so we just want to make a new list with the properties + // needed by `registerSections`, which are different than the + // properties used by the deepLinks. + // + // See https://github.com/elastic/kibana/issues/103325. + const otherLinks: NavigationEntry[] = deepLinks + .filter((link) => link.navLinkStatus === AppNavLinkStatus.visible) + .map((link) => ({ + app: observabilityAppId, + label: link.title, + path: link.path ?? '', + })); - const overviewLink = !Boolean(pluginsSetup.serverless) - ? [ + return [ { - label: i18n.translate('xpack.observability.overviewLinkTitle', { - defaultMessage: 'Overview', - }), - app: observabilityAppId, - path: OVERVIEW_PATH, + label: '', + sortKey: 100, + entries: [...overviewLink, ...otherLinks, ...aiAssistantLink], }, - ] - : []; - - // Reformat the visible links to be NavigationEntry objects instead of - // AppDeepLink objects. - // - // In our case the deep links and sections being registered are the - // same, and the logic to hide them based on flags or capabilities is - // the same, so we just want to make a new list with the properties - // needed by `registerSections`, which are different than the - // properties used by the deepLinks. - // - // See https://github.com/elastic/kibana/issues/103325. - const otherLinks: NavigationEntry[] = deepLinks - .filter((link) => link.navLinkStatus === AppNavLinkStatus.visible) - .map((link) => ({ - app: observabilityAppId, - label: link.title, - path: link.path ?? '', - })); - - return [ - { - label: '', - sortKey: 100, - entries: [...overviewLink, ...otherLinks], - }, - ]; - }) + ]; + }) + ) + ) ) ); From 32cc250bc10c18e7394571e71f2d2358aa5de3bd Mon Sep 17 00:00:00 2001 From: Tiago Costa Date: Tue, 13 Feb 2024 09:17:20 +0000 Subject: [PATCH 46/83] fix(NA): access-multiple-gcs-buckets-when-promoting-snapshots (#176786) Follow up for https://github.com/elastic/kibana/pull/176781 so we can fix the access to the GCS buckets during snapshot promotion --------- Co-authored-by: Alex Szabo --- .buildkite/scripts/common/activate_service_account.sh | 5 ++++- .buildkite/scripts/steps/es_snapshots/promote_manifest.ts | 5 +++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/.buildkite/scripts/common/activate_service_account.sh b/.buildkite/scripts/common/activate_service_account.sh index 83e30e37b8f07..5569367fd281b 100755 --- a/.buildkite/scripts/common/activate_service_account.sh +++ b/.buildkite/scripts/common/activate_service_account.sh @@ -62,7 +62,10 @@ if [[ -z "$EMAIL" ]]; then EMAIL="kibana-ci-access-coverage@$GCLOUD_EMAIL_POSTFIX" ;; "kibana-ci-es-snapshots-daily") - EMAIL="kibana-ci-access-es-snapshots@$GCLOUD_EMAIL_POSTFIX" + EMAIL="kibana-ci-access-es-daily@$GCLOUD_EMAIL_POSTFIX" + ;; + "kibana-ci-es-snapshots-permanent") + EMAIL="kibana-ci-access-es-permanent@$GCLOUD_EMAIL_POSTFIX" ;; "kibana-so-types-snapshots") EMAIL="kibana-ci-access-so-snapshots@$GCLOUD_EMAIL_POSTFIX" diff --git a/.buildkite/scripts/steps/es_snapshots/promote_manifest.ts b/.buildkite/scripts/steps/es_snapshots/promote_manifest.ts index 274807a8e5038..dfe834e28e4e5 100644 --- a/.buildkite/scripts/steps/es_snapshots/promote_manifest.ts +++ b/.buildkite/scripts/steps/es_snapshots/promote_manifest.ts @@ -18,7 +18,7 @@ import { BASE_BUCKET_DAILY, BASE_BUCKET_PERMANENT } from './bucket_config'; throw Error('Manifest URL missing'); } - const mainCWD = process.cwd(); + const projectRoot = process.cwd(); const tempDir = fs.mkdtempSync('snapshot-promotion'); process.chdir(tempDir); @@ -39,10 +39,11 @@ import { BASE_BUCKET_DAILY, BASE_BUCKET_PERMANENT } from './bucket_config'; execSync( ` set -euo pipefail - ${mainCWD}/.buildkite/scripts/common/activate_service_account.sh ${bucket} + ${projectRoot}/.buildkite/scripts/common/activate_service_account.sh ${BASE_BUCKET_DAILY} cp manifest.json manifest-latest-verified.json gsutil -h "Cache-Control:no-cache, max-age=0, no-transform" cp manifest-latest-verified.json gs://${BASE_BUCKET_DAILY}/${version}/ rm manifest.json + ${projectRoot}/.buildkite/scripts/common/activate_service_account.sh ${BASE_BUCKET_PERMANENT} cp manifest-permanent.json manifest.json gsutil -m cp -r gs://${bucket}/* gs://${BASE_BUCKET_PERMANENT}/${version}/ gsutil -h "Cache-Control:no-cache, max-age=0, no-transform" cp manifest.json gs://${BASE_BUCKET_PERMANENT}/${version}/ From ece790cc82b2f39940e4039899000870fe0bfe73 Mon Sep 17 00:00:00 2001 From: Jedr Blaszyk Date: Tue, 13 Feb 2024 10:25:15 +0100 Subject: [PATCH 47/83] [Search] Fetch connectors with Connectors API (#176384) Use Connectors API to fetch (get, list) connectors --- .../lib/fetch_connectors.test.ts | 271 ++++++++++-------- .../lib/fetch_connectors.ts | 128 ++++----- .../update_connector_configuration.test.ts | 12 +- .../lib/update_connector_configuration.ts | 5 +- .../lib/update_filtering.ts | 7 +- .../lib/update_filtering_draft.ts | 7 +- .../types/connectors_api.ts | 7 +- .../lib/pipelines/get_index_pipeline.test.ts | 38 +-- .../routes/enterprise_search/connectors.ts | 8 +- .../server/routes/connectors_routes.ts | 6 +- 10 files changed, 229 insertions(+), 260 deletions(-) diff --git a/packages/kbn-search-connectors/lib/fetch_connectors.test.ts b/packages/kbn-search-connectors/lib/fetch_connectors.test.ts index e1bb872710594..e90f5c200a439 100644 --- a/packages/kbn-search-connectors/lib/fetch_connectors.test.ts +++ b/packages/kbn-search-connectors/lib/fetch_connectors.test.ts @@ -6,186 +6,205 @@ * Side Public License, v 1. */ -import { CONNECTORS_INDEX } from '..'; +import { errors } from '@elastic/elasticsearch'; + +import { ElasticsearchClient } from '@kbn/core/server'; import { fetchConnectorById, fetchConnectorByIndexName, fetchConnectors } from './fetch_connectors'; -const indexNotFoundError = { +const otherError = { meta: { body: { error: { - type: 'index_not_found_exception', + type: 'other_error', }, }, }, }; -const otherError = { - meta: { - body: { - error: { - type: 'other_error', - }, +const notFoundError = new errors.ResponseError({ + statusCode: 404, + body: { + error: { + type: `document_missing_exception`, }, }, -}; +} as any); describe('fetchConnectors lib', () => { const mockClient = { - get: jest.fn(), - search: jest.fn(), + transport: { + request: jest.fn(), + }, }; - beforeEach(() => { jest.clearAllMocks(); }); - describe('fetch connector by id', () => { - it('should fetch connector by id', async () => { - mockClient.get.mockImplementationOnce(() => - Promise.resolve({ - _id: 'connectorId', - _primary_term: 'primaryTerm', - _seq_no: 5, - _source: { source: 'source' }, - }) - ); - await expect(fetchConnectorById(mockClient as any, 'id')).resolves.toEqual({ - primaryTerm: 'primaryTerm', - seqNo: 5, - value: { id: 'connectorId', source: 'source' }, - }); - expect(mockClient.get).toHaveBeenCalledWith({ - id: 'id', - index: CONNECTORS_INDEX, + describe('fetchConnectorById', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('should fetch a connector by ID', async () => { + const mockResult = { id: 'connectorId', service_type: 'someServiceType' }; + mockClient.transport.request.mockResolvedValue(mockResult); + + await expect( + fetchConnectorById(mockClient as unknown as ElasticsearchClient, 'connectorId') + ).resolves.toEqual(mockResult); + expect(mockClient.transport.request).toHaveBeenCalledWith({ + method: 'GET', + path: `/_connector/connectorId`, }); }); - it('should return undefined on index not found error', async () => { - mockClient.get.mockImplementationOnce(() => Promise.reject(indexNotFoundError)); - await expect(fetchConnectorById(mockClient as any, 'id')).resolves.toEqual(undefined); - expect(mockClient.get).toHaveBeenCalledWith({ - id: 'id', - index: CONNECTORS_INDEX, + + it('should return undefined if connector not found', async () => { + mockClient.transport.request.mockImplementationOnce(() => Promise.reject(notFoundError)); + + await expect( + fetchConnectorById(mockClient as unknown as ElasticsearchClient, 'nonExistingId') + ).resolves.toBeUndefined(); + + expect(mockClient.transport.request).toHaveBeenCalledWith({ + method: 'GET', + path: `/_connector/nonExistingId`, }); }); - it('should throw on other errors', async () => { - mockClient.get.mockImplementationOnce(() => Promise.reject(otherError)); - await expect(fetchConnectorById(mockClient as any, 'id')).rejects.toEqual(otherError); - expect(mockClient.get).toHaveBeenCalledWith({ - id: 'id', - index: CONNECTORS_INDEX, + + it('should throw an error for other exceptions', async () => { + mockClient.transport.request.mockImplementationOnce(() => Promise.reject(otherError)); + + await expect( + fetchConnectorById(mockClient as unknown as ElasticsearchClient, 'badId') + ).rejects.toEqual(otherError); + + expect(mockClient.transport.request).toHaveBeenCalledWith({ + method: 'GET', + path: `/_connector/badId`, }); }); }); describe('fetch connector by name', () => { it('should fetch connector by index name', async () => { - mockClient.search.mockImplementationOnce(() => - Promise.resolve({ hits: { hits: [{ _id: 'connectorId', _source: { source: 'source' } }] } }) - ); - mockClient.get.mockImplementationOnce(() => - Promise.resolve({ _id: 'connectorId', _source: { source: 'source' } }) - ); - await expect(fetchConnectorByIndexName(mockClient as any, 'id')).resolves.toEqual({ - id: 'connectorId', - source: 'source', - }); - expect(mockClient.search).toHaveBeenCalledWith({ - index: CONNECTORS_INDEX, - query: { - term: { - ['index_name']: 'id', - }, + const mockResult = { + count: 1, + results: [{ id: 'connectorId', service_type: 'someServiceType' }], + }; + mockClient.transport.request.mockImplementationOnce(() => Promise.resolve(mockResult)); + + await expect( + fetchConnectorByIndexName(mockClient as unknown as ElasticsearchClient, 'indexName') + ).resolves.toEqual(mockResult.results[0]); + expect(mockClient.transport.request).toHaveBeenCalledWith({ + method: 'GET', + path: `/_connector`, + querystring: { + index_name: 'indexName', }, }); }); - it('should return undefined on index not found error', async () => { - mockClient.search.mockImplementationOnce(() => Promise.reject(indexNotFoundError)); + it('should return undefined on connector not found case', async () => { + mockClient.transport.request.mockImplementationOnce(() => + Promise.resolve({ count: 0, results: [] }) + ); await expect(fetchConnectorByIndexName(mockClient as any, 'id')).resolves.toEqual(undefined); - expect(mockClient.search).toHaveBeenCalledWith({ - index: CONNECTORS_INDEX, - query: { - term: { - ['index_name']: 'id', - }, + expect(mockClient.transport.request).toHaveBeenCalledWith({ + method: 'GET', + path: `/_connector`, + querystring: { + index_name: 'id', }, }); }); it('should throw on other errors', async () => { - mockClient.search.mockImplementationOnce(() => Promise.reject(otherError)); + mockClient.transport.request.mockImplementationOnce(() => Promise.reject(otherError)); await expect(fetchConnectorByIndexName(mockClient as any, 'id')).rejects.toEqual(otherError); - expect(mockClient.search).toHaveBeenCalledWith({ - index: CONNECTORS_INDEX, - query: { - term: { - ['index_name']: 'id', - }, + expect(mockClient.transport.request).toHaveBeenCalledWith({ + method: 'GET', + path: `/_connector`, + querystring: { + index_name: 'id', }, }); }); }); describe('fetch connectors', () => { - it('should fetch connectors', async () => { - mockClient.search.mockImplementationOnce(() => - Promise.resolve({ hits: { hits: [{ _id: 'connectorId', _source: { source: 'source' } }] } }) + it('should fetch all connectors', async () => { + const mockResult = { + results: [ + { id: 'connector1', service_type: 'type1' }, + { id: 'connector2', service_type: 'type2' }, + ], + count: 2, + }; + mockClient.transport.request.mockResolvedValue(mockResult); + + await expect(fetchConnectors(mockClient as unknown as ElasticsearchClient)).resolves.toEqual( + mockResult.results ); - await expect(fetchConnectors(mockClient as any)).resolves.toEqual([ - { - id: 'connectorId', - source: 'source', + expect(mockClient.transport.request).toHaveBeenCalledWith({ + method: 'GET', + path: `/_connector`, + querystring: { + from: 0, + size: 1000, }, - ]); - expect(mockClient.search).toHaveBeenCalledWith({ - from: 0, - index: CONNECTORS_INDEX, - query: { match_all: {} }, - size: 1000, }); }); it('should fetch all connectors if there are more than 1000', async () => { - const hits = [...Array(1000).keys()].map((key) => ({ - _id: key, - _source: { source: 'source' }, + const firstBatch = Array.from({ length: 1000 }, (_, i) => ({ + id: `connector${i + 1}`, + service_type: 'type1', })); - const resultHits = hits.map((hit) => ({ ...hit._source, id: hit._id })); - - let count = 0; + const secondBatch = Array.from({ length: 1000 }, (_, i) => ({ + id: `connector${i + 1000 + 1}`, + service_type: 'type1', + })); + const thirdBatch = [{ id: 'connector2001', service_type: 'type1' }]; + mockClient.transport.request + .mockResolvedValueOnce({ results: firstBatch, count: 2001 }) + .mockResolvedValueOnce({ results: secondBatch, count: 2001 }) + .mockResolvedValueOnce({ results: thirdBatch, count: 2001 }); - mockClient.search.mockImplementation(() => { - count += 1; - if (count === 3) { - return Promise.resolve({ hits: { hits: [] } }); - } - return Promise.resolve({ hits: { hits } }); - }); - await expect(fetchConnectors(mockClient as any)).resolves.toEqual([ - ...resultHits, - ...resultHits, + await expect(fetchConnectors(mockClient as unknown as ElasticsearchClient)).resolves.toEqual([ + ...firstBatch, + ...secondBatch, + ...thirdBatch, ]); - expect(mockClient.search).toHaveBeenCalledWith({ - from: 0, - index: CONNECTORS_INDEX, - query: { match_all: {} }, - size: 1000, - }); - expect(mockClient.search).toHaveBeenCalledTimes(3); - }); - it('should return empty array on index not found error', async () => { - mockClient.search.mockImplementationOnce(() => Promise.reject(indexNotFoundError)); - await expect(fetchConnectors(mockClient as any)).resolves.toEqual([]); - expect(mockClient.search).toHaveBeenCalledWith({ - from: 0, - index: CONNECTORS_INDEX, - query: { match_all: {} }, - size: 1000, - }); + + expect(mockClient.transport.request).toHaveBeenCalledTimes(3); + + expect(mockClient.transport.request).toHaveBeenNthCalledWith( + 1, + expect.objectContaining({ + querystring: expect.objectContaining({ from: 0, size: 1000 }), + }) + ); + + expect(mockClient.transport.request).toHaveBeenNthCalledWith( + 2, + expect.objectContaining({ + querystring: expect.objectContaining({ from: 1000, size: 1000 }), + }) + ); + + expect(mockClient.transport.request).toHaveBeenNthCalledWith( + 3, + expect.objectContaining({ + querystring: expect.objectContaining({ from: 2000, size: 1000 }), + }) + ); }); it('should throw on other errors', async () => { - mockClient.search.mockImplementationOnce(() => Promise.reject(otherError)); + mockClient.transport.request.mockImplementationOnce(() => Promise.reject(otherError)); await expect(fetchConnectors(mockClient as any)).rejects.toEqual(otherError); - expect(mockClient.search).toHaveBeenCalledWith({ - from: 0, - index: CONNECTORS_INDEX, - query: { match_all: {} }, - size: 1000, + expect(mockClient.transport.request).toHaveBeenCalledWith({ + method: 'GET', + path: `/_connector`, + querystring: { + from: 0, + size: 1000, + }, }); }); }); diff --git a/packages/kbn-search-connectors/lib/fetch_connectors.ts b/packages/kbn-search-connectors/lib/fetch_connectors.ts index e9b24a01e87ca..724c943877208 100644 --- a/packages/kbn-search-connectors/lib/fetch_connectors.ts +++ b/packages/kbn-search-connectors/lib/fetch_connectors.ts @@ -6,37 +6,30 @@ * Side Public License, v 1. */ -import { QueryDslQueryContainer, SearchHit } from '@elastic/elasticsearch/lib/api/types'; import { ElasticsearchClient } from '@kbn/core/server'; -import { OptimisticConcurrency } from '../types/optimistic_concurrency'; -import { Connector, ConnectorDocument } from '../types/connectors'; +import { ConnectorAPIListConnectorsResponse } from '..'; -import { isIndexNotFoundException } from '../utils/identify_exceptions'; -import { CONNECTORS_INDEX, CRAWLER_SERVICE_TYPE } from '..'; -import { isNotNullish } from '../utils/is_not_nullish'; +import { Connector } from '../types/connectors'; + +import { isNotFoundException } from '../utils/identify_exceptions'; +import { CRAWLER_SERVICE_TYPE } from '..'; export const fetchConnectorById = async ( client: ElasticsearchClient, connectorId: string -): Promise | undefined> => { +): Promise => { try { - const connectorResult = await client.get({ - id: connectorId, - index: CONNECTORS_INDEX, + const result = await client.transport.request({ + method: 'GET', + path: `/_connector/${connectorId}`, }); - return connectorResult._source - ? { - primaryTerm: connectorResult._primary_term, - seqNo: connectorResult._seq_no, - value: { ...connectorResult._source, id: connectorResult._id }, - } - : undefined; - } catch (error) { - if (isIndexNotFoundException(error)) { + return result; + } catch (err) { + if (isNotFoundException(err)) { return undefined; } - throw error; + throw err; } }; @@ -45,20 +38,16 @@ export const fetchConnectorByIndexName = async ( indexName: string ): Promise => { try { - const connectorResult = await client.search({ - index: CONNECTORS_INDEX, - query: { term: { index_name: indexName } }, + const connectorListResult = await client.transport.request({ + method: 'GET', + path: `/_connector`, + querystring: { + index_name: indexName, + }, }); - // Because we cannot guarantee that the index has been refreshed and is giving us the most recent source - // we need to fetch the source with a direct get from the index, which will always be up to date - const result = connectorResult.hits.hits[0]?._source - ? (await fetchConnectorById(client, connectorResult.hits.hits[0]._id))?.value - : undefined; + const result = connectorListResult.count > 0 ? connectorListResult.results[0] : undefined; return result; } catch (error) { - if (isIndexNotFoundException(error)) { - return undefined; - } throw error; } }; @@ -70,62 +59,43 @@ export const fetchConnectors = async ( searchQuery?: string ): Promise => { const q = searchQuery && searchQuery.length > 0 ? searchQuery : undefined; - const query: QueryDslQueryContainer = q + + const querystring: Record = q ? { - bool: { - should: [ - { - wildcard: { - name: { - value: `*${q}*`, - }, - }, - }, - { - wildcard: { - index_name: { - value: `*${q}*`, - }, - }, - }, - ], - }, + query: q, } : indexNames - ? { terms: { index_name: indexNames } } - : { match_all: {} }; + ? { + index_name: indexNames.join(','), + } + : {}; - try { - let hits: Array> = []; - let accumulator: Array> = []; + let hits: Connector[] = []; + let accumulator: Connector[] = []; - do { - const connectorResult = await client.search({ + do { + const connectorResult = await client.transport.request({ + method: 'GET', + path: `/_connector`, + querystring: { + ...querystring, from: accumulator.length, - index: CONNECTORS_INDEX, - query, size: 1000, - }); - hits = connectorResult.hits.hits; - accumulator = accumulator.concat(hits); - } while (hits.length >= 1000); + }, + }); - const result = accumulator - .map(({ _source, _id }) => (_source ? { ..._source, id: _id } : undefined)) - .filter(isNotNullish); + hits = connectorResult.results; + accumulator = accumulator.concat(hits); + } while (hits.length >= 1000); - if (fetchOnlyCrawlers !== undefined) { - return result.filter((hit) => { - return !fetchOnlyCrawlers - ? hit.service_type !== CRAWLER_SERVICE_TYPE - : hit.service_type === CRAWLER_SERVICE_TYPE; - }); - } - return result; - } catch (error) { - if (isIndexNotFoundException(error)) { - return []; - } - throw error; + const result = accumulator; + + if (fetchOnlyCrawlers !== undefined) { + return result.filter((hit) => { + return !fetchOnlyCrawlers + ? hit.service_type !== CRAWLER_SERVICE_TYPE + : hit.service_type === CRAWLER_SERVICE_TYPE; + }); } + return result; }; diff --git a/packages/kbn-search-connectors/lib/update_connector_configuration.test.ts b/packages/kbn-search-connectors/lib/update_connector_configuration.test.ts index fd1e014037863..9f6d0b188e535 100644 --- a/packages/kbn-search-connectors/lib/update_connector_configuration.test.ts +++ b/packages/kbn-search-connectors/lib/update_connector_configuration.test.ts @@ -24,13 +24,9 @@ describe('updateConnectorConfiguration lib function', () => { beforeEach(() => { jest.clearAllMocks(); (fetchConnectorById as jest.Mock).mockResolvedValue({ - primaryTerm: 0, - seqNo: 3, - value: { - configuration: { test: { label: 'haha', value: 'this' } }, - id: 'connectorId', - status: ConnectorStatus.NEEDS_CONFIGURATION, - }, + configuration: { test: { label: 'haha', value: 'this' } }, + id: 'connectorId', + status: ConnectorStatus.NEEDS_CONFIGURATION, }); }); @@ -46,8 +42,6 @@ describe('updateConnectorConfiguration lib function', () => { status: ConnectorStatus.CONFIGURED, }, id: 'connectorId', - if_primary_term: 0, - if_seq_no: 3, index: CONNECTORS_INDEX, }); }); diff --git a/packages/kbn-search-connectors/lib/update_connector_configuration.ts b/packages/kbn-search-connectors/lib/update_connector_configuration.ts index 78a5fc1893c83..f9588b984409f 100644 --- a/packages/kbn-search-connectors/lib/update_connector_configuration.ts +++ b/packages/kbn-search-connectors/lib/update_connector_configuration.ts @@ -22,8 +22,7 @@ export const updateConnectorConfiguration = async ( connectorId: string, configuration: Record ) => { - const connectorResult = await fetchConnectorById(client, connectorId); - const connector = connectorResult?.value; + const connector = await fetchConnectorById(client, connectorId); if (connector) { const status = connector.status === ConnectorStatus.NEEDS_CONFIGURATION || @@ -49,8 +48,6 @@ export const updateConnectorConfiguration = async ( await client.update({ doc: { configuration: updatedConfig, status }, id: connectorId, - if_primary_term: connectorResult?.primaryTerm, - if_seq_no: connectorResult?.seqNo, index: CONNECTORS_INDEX, }); return updatedConfig; diff --git a/packages/kbn-search-connectors/lib/update_filtering.ts b/packages/kbn-search-connectors/lib/update_filtering.ts index 5d32639f23396..9651828fdc313 100644 --- a/packages/kbn-search-connectors/lib/update_filtering.ts +++ b/packages/kbn-search-connectors/lib/update_filtering.ts @@ -37,11 +37,10 @@ export const updateFiltering = async ( created_at: filteringRule.created_at ? filteringRule.created_at : now, updated_at: now, })); - const connectorResult = await fetchConnectorById(client, connectorId); - if (!connectorResult) { + const connector = await fetchConnectorById(client, connectorId); + if (!connector) { throw new Error(`Could not find connector with id ${connectorId}`); } - const { value: connector, seqNo, primaryTerm } = connectorResult; const active: FilteringRules = { advanced_snippet: { created_at: connector.filtering[0].active.advanced_snippet.created_at || now, @@ -58,8 +57,6 @@ export const updateFiltering = async ( const result = await client.update({ doc: { ...connector, filtering: [{ ...connector.filtering[0], active, draft: active }] }, id: connectorId, - if_primary_term: primaryTerm, - if_seq_no: seqNo, index: CONNECTORS_INDEX, }); diff --git a/packages/kbn-search-connectors/lib/update_filtering_draft.ts b/packages/kbn-search-connectors/lib/update_filtering_draft.ts index deb6eabd66e23..154f85f4becb0 100644 --- a/packages/kbn-search-connectors/lib/update_filtering_draft.ts +++ b/packages/kbn-search-connectors/lib/update_filtering_draft.ts @@ -49,17 +49,14 @@ export const updateFilteringDraft = async ( state: FilteringValidationState.EDITED, }, }; - const connectorResult = await fetchConnectorById(client, connectorId); - if (!connectorResult) { + const connector = await fetchConnectorById(client, connectorId); + if (!connector) { throw new Error(`Could not find connector with id ${connectorId}`); } - const { value: connector, seqNo, primaryTerm } = connectorResult; const result = await client.update({ doc: { ...connector, filtering: [{ ...connector.filtering[0], draft }] }, id: connectorId, - if_primary_term: primaryTerm, - if_seq_no: seqNo, index: CONNECTORS_INDEX, }); diff --git a/packages/kbn-search-connectors/types/connectors_api.ts b/packages/kbn-search-connectors/types/connectors_api.ts index c344073831a46..3b96eeb09b0d8 100644 --- a/packages/kbn-search-connectors/types/connectors_api.ts +++ b/packages/kbn-search-connectors/types/connectors_api.ts @@ -8,7 +8,7 @@ // TODO: delete this once ES client can be used for Connectors API -import { ConnectorSyncJob } from './connectors'; +import { ConnectorSyncJob, Connector } from './connectors'; enum Result { created = 'created', @@ -18,6 +18,11 @@ enum Result { no_op = 'noop', } +export interface ConnectorAPIListConnectorsResponse { + count: number; + results: Connector[]; +} + export interface ConnectorsAPIUpdateResponse { result: Result; } diff --git a/x-pack/plugins/enterprise_search/server/lib/pipelines/get_index_pipeline.test.ts b/x-pack/plugins/enterprise_search/server/lib/pipelines/get_index_pipeline.test.ts index 07260423b4590..699dd3b7f6485 100644 --- a/x-pack/plugins/enterprise_search/server/lib/pipelines/get_index_pipeline.test.ts +++ b/x-pack/plugins/enterprise_search/server/lib/pipelines/get_index_pipeline.test.ts @@ -13,18 +13,15 @@ import { getIndexPipelineParameters } from './get_index_pipeline'; describe('getIndexPipelineParameters', () => { const defaultMockClient = () => ({ asCurrentUser: { - get: jest.fn().mockResolvedValue({}), indices: { getMapping: jest.fn().mockResolvedValue({}), }, ingest: { getPipeline: jest.fn().mockRejectedValue('Pipeline not found'), }, - search: jest.fn().mockResolvedValue({ - hits: { - hits: [], - }, - }), + transport: { + request: jest.fn().mockResolvedValue({}), + }, }, }); let mockClient = defaultMockClient(); @@ -41,26 +38,19 @@ describe('getIndexPipelineParameters', () => { ); }); it('returns connector pipeline params if found', async () => { - mockClient.asCurrentUser.search = jest.fn().mockResolvedValue({ - hits: { - hits: [ - { - _id: 'unit-test', - _source: {}, + mockClient.asCurrentUser.transport.request = jest.fn().mockResolvedValue({ + count: 1, + results: [ + { + id: 'unit-test', + pipeline: { + extract_binary_content: false, + name: 'unit-test-pipeline', + reduce_whitespace: true, + run_ml_inference: true, }, - ], - }, - }); - mockClient.asCurrentUser.get = jest.fn().mockResolvedValue({ - _id: 'unit-test', - _source: { - pipeline: { - extract_binary_content: false, - name: 'unit-test-pipeline', - reduce_whitespace: true, - run_ml_inference: true, }, - }, + ], }); await expect(getIndexPipelineParameters('my-index', client)).resolves.toEqual({ extract_binary_content: false, diff --git a/x-pack/plugins/enterprise_search/server/routes/enterprise_search/connectors.ts b/x-pack/plugins/enterprise_search/server/routes/enterprise_search/connectors.ts index 4226e4326ce0a..b7c7ae7217ddc 100644 --- a/x-pack/plugins/enterprise_search/server/routes/enterprise_search/connectors.ts +++ b/x-pack/plugins/enterprise_search/server/routes/enterprise_search/connectors.ts @@ -567,7 +567,7 @@ export function registerConnectorRoutes({ router, log }: RouteDependencies) { } return response.ok({ body: { - connector: connectorResult?.value, + connector: connectorResult, }, }); }) @@ -592,9 +592,9 @@ export function registerConnectorRoutes({ router, log }: RouteDependencies) { let connectorResponse; try { const connector = await fetchConnectorById(client.asCurrentUser, connectorId); - const indexNameToDelete = shouldDeleteIndex ? connector?.value.index_name : null; - const apiKeyId = connector?.value.api_key_id; - const secretId = connector?.value.api_key_secret_id; + const indexNameToDelete = shouldDeleteIndex ? connector?.index_name : null; + const apiKeyId = connector?.api_key_id; + const secretId = connector?.api_key_secret_id; connectorResponse = await deleteConnectorById(client.asCurrentUser, connectorId); diff --git a/x-pack/plugins/serverless_search/server/routes/connectors_routes.ts b/x-pack/plugins/serverless_search/server/routes/connectors_routes.ts index 4454f290a81ee..d7ca0329e9701 100644 --- a/x-pack/plugins/serverless_search/server/routes/connectors_routes.ts +++ b/x-pack/plugins/serverless_search/server/routes/connectors_routes.ts @@ -51,12 +51,12 @@ export const registerConnectorsRoutes = ({ http, router }: RouteDependencies) => }, async (context, request, response) => { const { client } = (await context.core).elasticsearch; - const result = await fetchConnectorById(client.asCurrentUser, request.params.connectorId); + const connector = await fetchConnectorById(client.asCurrentUser, request.params.connectorId); - return result + return connector ? response.ok({ body: { - connector: result.value, + connector, }, headers: { 'content-type': 'application/json' }, }) From 63bd950df09e5278332754dc33dafad6c1c39137 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=B8ren=20Louv-Jansen?= Date: Tue, 13 Feb 2024 10:42:24 +0100 Subject: [PATCH 48/83] [Obs AI Assistant] Add Search Connector tab to settings (#176655) This PR simply adds a new tab that: - describes what search connectors are and how they can improve relevancy of assistant responses (copy writer welcome!) - links to enterprise search The tab will be disabled if enterprise search plugin is not enabled **Motivation for PR** Making customers aware of search connectors and their benefits in relation to the AI Assistant. We can perhaps simplify the process of adding Search Connectors down the line but for now I suggest keeping it simple. ![image](https://github.com/elastic/kibana/assets/209966/9653bc28-cc69-4215-ae9d-b13c293bdd64) --- .../observability/kibana.jsonc | 8 ++- .../observability/public/app.tsx | 3 +- .../public/context/app_context.tsx | 2 - .../hooks/use_create_knowledge_base_entry.ts | 2 +- .../hooks/use_delete_knowledge_base_entry.ts | 2 +- .../hooks/use_get_knowledge_base_entries.ts | 2 +- .../use_import_knowledge_base_entries.ts | 2 +- .../observability/public/plugin.ts | 3 + .../components/search_connector_tab.tsx | 55 +++++++++++++++++++ .../routes/components/settings_page.tsx | 42 ++++++++------ .../public/routes/components/settings_tab.tsx | 5 ++ .../observability/public/routes/config.tsx | 7 ++- .../observability/tsconfig.json | 3 +- .../enterprise_search/public/plugin.ts | 6 +- 14 files changed, 113 insertions(+), 29 deletions(-) create mode 100644 src/plugins/ai_assistant_management/observability/public/routes/components/search_connector_tab.tsx diff --git a/src/plugins/ai_assistant_management/observability/kibana.jsonc b/src/plugins/ai_assistant_management/observability/kibana.jsonc index 6ddb104cbf16d..c9e7b96bc4312 100644 --- a/src/plugins/ai_assistant_management/observability/kibana.jsonc +++ b/src/plugins/ai_assistant_management/observability/kibana.jsonc @@ -7,7 +7,13 @@ "server": false, "browser": true, "requiredPlugins": ["management"], - "optionalPlugins": ["actions", "home", "observabilityAIAssistant", "serverless"], + "optionalPlugins": [ + "actions", + "home", + "observabilityAIAssistant", + "serverless", + "enterpriseSearch" + ], "requiredBundles": ["kibanaReact"] } } diff --git a/src/plugins/ai_assistant_management/observability/public/app.tsx b/src/plugins/ai_assistant_management/observability/public/app.tsx index 667e104dd6466..1d0081344800c 100644 --- a/src/plugins/ai_assistant_management/observability/public/app.tsx +++ b/src/plugins/ai_assistant_management/observability/public/app.tsx @@ -47,12 +47,11 @@ export const mountManagementSection = async ({ core, mountParams }: MountParams) diff --git a/src/plugins/ai_assistant_management/observability/public/context/app_context.tsx b/src/plugins/ai_assistant_management/observability/public/context/app_context.tsx index 832701e9aed94..5d72e33206a99 100644 --- a/src/plugins/ai_assistant_management/observability/public/context/app_context.tsx +++ b/src/plugins/ai_assistant_management/observability/public/context/app_context.tsx @@ -9,14 +9,12 @@ import React, { createContext } from 'react'; import type { ChromeBreadcrumb } from '@kbn/core-chrome-browser'; import type { CoreStart, HttpSetup } from '@kbn/core/public'; -import type { ObservabilityAIAssistantPluginStart } from '@kbn/observability-ai-assistant-plugin/public'; import type { StartDependencies } from '../plugin'; export interface ContextValue extends StartDependencies { application: CoreStart['application']; http: HttpSetup; notifications: CoreStart['notifications']; - observabilityAIAssistant: ObservabilityAIAssistantPluginStart; setBreadcrumbs: (crumbs: ChromeBreadcrumb[]) => void; uiSettings: CoreStart['uiSettings']; } diff --git a/src/plugins/ai_assistant_management/observability/public/hooks/use_create_knowledge_base_entry.ts b/src/plugins/ai_assistant_management/observability/public/hooks/use_create_knowledge_base_entry.ts index 9c68437774255..d1e71ed9bdc67 100644 --- a/src/plugins/ai_assistant_management/observability/public/hooks/use_create_knowledge_base_entry.ts +++ b/src/plugins/ai_assistant_management/observability/public/hooks/use_create_knowledge_base_entry.ts @@ -21,7 +21,7 @@ export function useCreateKnowledgeBaseEntry() { observabilityAIAssistant, } = useAppContext(); const queryClient = useQueryClient(); - const observabilityAIAssistantApi = observabilityAIAssistant.service.callApi; + const observabilityAIAssistantApi = observabilityAIAssistant?.service.callApi; return useMutation< void, diff --git a/src/plugins/ai_assistant_management/observability/public/hooks/use_delete_knowledge_base_entry.ts b/src/plugins/ai_assistant_management/observability/public/hooks/use_delete_knowledge_base_entry.ts index 3c07a1c047cb0..8dcaab115577b 100644 --- a/src/plugins/ai_assistant_management/observability/public/hooks/use_delete_knowledge_base_entry.ts +++ b/src/plugins/ai_assistant_management/observability/public/hooks/use_delete_knowledge_base_entry.ts @@ -20,7 +20,7 @@ export function useDeleteKnowledgeBaseEntry() { notifications: { toasts }, } = useAppContext(); const queryClient = useQueryClient(); - const observabilityAIAssistantApi = observabilityAIAssistant.service.callApi; + const observabilityAIAssistantApi = observabilityAIAssistant?.service.callApi; return useMutation( [REACT_QUERY_KEYS.CREATE_KB_ENTRIES], diff --git a/src/plugins/ai_assistant_management/observability/public/hooks/use_get_knowledge_base_entries.ts b/src/plugins/ai_assistant_management/observability/public/hooks/use_get_knowledge_base_entries.ts index ca0be9aa77944..40cb3998212f6 100644 --- a/src/plugins/ai_assistant_management/observability/public/hooks/use_get_knowledge_base_entries.ts +++ b/src/plugins/ai_assistant_management/observability/public/hooks/use_get_knowledge_base_entries.ts @@ -21,7 +21,7 @@ export function useGetKnowledgeBaseEntries({ }) { const { observabilityAIAssistant } = useAppContext(); - const observabilityAIAssistantApi = observabilityAIAssistant.service.callApi; + const observabilityAIAssistantApi = observabilityAIAssistant?.service.callApi; const { isLoading, isError, isSuccess, isRefetching, data, refetch } = useQuery({ queryKey: [REACT_QUERY_KEYS.GET_KB_ENTRIES, query, sortBy, sortDirection], diff --git a/src/plugins/ai_assistant_management/observability/public/hooks/use_import_knowledge_base_entries.ts b/src/plugins/ai_assistant_management/observability/public/hooks/use_import_knowledge_base_entries.ts index 399d234b651d6..cbcf6e40d3efe 100644 --- a/src/plugins/ai_assistant_management/observability/public/hooks/use_import_knowledge_base_entries.ts +++ b/src/plugins/ai_assistant_management/observability/public/hooks/use_import_knowledge_base_entries.ts @@ -21,7 +21,7 @@ export function useImportKnowledgeBaseEntries() { notifications: { toasts }, } = useAppContext(); const queryClient = useQueryClient(); - const observabilityAIAssistantApi = observabilityAIAssistant.service.callApi; + const observabilityAIAssistantApi = observabilityAIAssistant?.service.callApi; return useMutation< void, diff --git a/src/plugins/ai_assistant_management/observability/public/plugin.ts b/src/plugins/ai_assistant_management/observability/public/plugin.ts index 7da6f9999b2f8..36785ebe096cb 100644 --- a/src/plugins/ai_assistant_management/observability/public/plugin.ts +++ b/src/plugins/ai_assistant_management/observability/public/plugin.ts @@ -11,6 +11,8 @@ import { CoreSetup, Plugin } from '@kbn/core/public'; import { ManagementSetup } from '@kbn/management-plugin/public'; import { HomePublicPluginSetup } from '@kbn/home-plugin/public'; import { ServerlessPluginStart } from '@kbn/serverless/public'; +import { EnterpriseSearchPublicStart } from '@kbn/enterprise-search-plugin/public'; + import type { ObservabilityAIAssistantPluginSetup, ObservabilityAIAssistantPluginStart, @@ -31,6 +33,7 @@ export interface SetupDependencies { export interface StartDependencies { observabilityAIAssistant?: ObservabilityAIAssistantPluginStart; serverless?: ServerlessPluginStart; + enterpriseSearch?: EnterpriseSearchPublicStart; } export class AiAssistantManagementObservabilityPlugin diff --git a/src/plugins/ai_assistant_management/observability/public/routes/components/search_connector_tab.tsx b/src/plugins/ai_assistant_management/observability/public/routes/components/search_connector_tab.tsx new file mode 100644 index 0000000000000..280a36660ae1d --- /dev/null +++ b/src/plugins/ai_assistant_management/observability/public/routes/components/search_connector_tab.tsx @@ -0,0 +1,55 @@ +/* + * 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 { i18n } from '@kbn/i18n'; +import { EuiLink, EuiText } from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n-react'; +import { useAppContext } from '../../hooks/use_app_context'; + +export const SELECTED_CONNECTOR_LOCAL_STORAGE_KEY = + 'xpack.observabilityAiAssistant.lastUsedConnector'; + +export function SearchConnectorTab() { + const { application } = useAppContext(); + const url = application.getUrlForApp('enterprise_search', { path: '/content/connectors' }); + + return ( + <> + + {i18n.translate( + 'aiAssistantManagementObservability.searchConnectorTab.searchConnectorsEnablesYouTextLabel', + { + defaultMessage: + 'Connectors enable you to index content from external sources thereby making it available for the AI Assistant. This can greatly improve the relevance of the AI Assistant’s responses.', + } + )} + + + + + {i18n.translate( + 'aiAssistantManagementObservability.searchConnectorTab.searchConnectorsManagementPageLinkLabel', + { defaultMessage: 'Connectors' } + )} + + ), + }} + /> + + + ); +} diff --git a/src/plugins/ai_assistant_management/observability/public/routes/components/settings_page.tsx b/src/plugins/ai_assistant_management/observability/public/routes/components/settings_page.tsx index 4c7ad7f8aa738..85a75b74ed429 100644 --- a/src/plugins/ai_assistant_management/observability/public/routes/components/settings_page.tsx +++ b/src/plugins/ai_assistant_management/observability/public/routes/components/settings_page.tsx @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import React, { useEffect, useState } from 'react'; +import React, { useEffect } from 'react'; import { i18n } from '@kbn/i18n'; import { EuiSpacer, EuiTab, EuiTabs, EuiTitle } from '@elastic/eui'; import { useAppContext } from '../../hooks/use_app_context'; @@ -15,10 +15,12 @@ import { KnowledgeBaseTab } from './knowledge_base_tab'; import { useObservabilityAIAssistantManagementRouterParams } from '../../hooks/use_observability_management_params'; import { useObservabilityAIAssistantManagementRouter } from '../../hooks/use_observability_management_router'; import type { TabsRt } from '../config'; +import { SearchConnectorTab } from './search_connector_tab'; export function SettingsPage() { const { application: { navigateToApp }, serverless, + enterpriseSearch, setBreadcrumbs, } = useAppContext(); @@ -60,7 +62,7 @@ export function SettingsPage() { } }, [navigateToApp, serverless, setBreadcrumbs]); - const tabs: Array<{ id: TabsRt; name: string; content: JSX.Element }> = [ + const tabs: Array<{ id: TabsRt; name: string; content: JSX.Element; disabled?: boolean }> = [ { id: 'settings', name: i18n.translate('aiAssistantManagementObservability.settingsPage.settingsLabel', { @@ -75,16 +77,20 @@ export function SettingsPage() { }), content: , }, + { + id: 'search_connector', + name: i18n.translate('aiAssistantManagementObservability.settingsPage.searchConnector', { + defaultMessage: 'Search Connectors', + }), + content: , + disabled: enterpriseSearch == null, + }, ]; - const [selectedTabId, setSelectedTabId] = useState( - tab ? tabs.find((t) => t.id === tab)?.id : tabs[0].id - ); - + const selectedTabId = tabs.some((t) => t.id === tab) ? tab : tabs[0].id; const selectedTabContent = tabs.find((obj) => obj.id === selectedTabId)?.content; const onSelectedTabChanged = (id: TabsRt) => { - setSelectedTabId(id); router.push('/', { path: '/', query: { tab: id } }); }; @@ -101,16 +107,18 @@ export function SettingsPage() { - {tabs.map((t, index) => ( - onSelectedTabChanged(t.id)} - isSelected={t.id === selectedTabId} - > - {t.name} - - ))} + {tabs + .filter((t) => !t.disabled) + .map((t, index) => ( + onSelectedTabChanged(t.id)} + isSelected={t.id === selectedTabId} + > + {t.name} + + ))} diff --git a/src/plugins/ai_assistant_management/observability/public/routes/components/settings_tab.tsx b/src/plugins/ai_assistant_management/observability/public/routes/components/settings_tab.tsx index 9385f1e42f899..ab68cb3abb306 100644 --- a/src/plugins/ai_assistant_management/observability/public/routes/components/settings_tab.tsx +++ b/src/plugins/ai_assistant_management/observability/public/routes/components/settings_tab.tsx @@ -28,6 +28,11 @@ export function SettingsTab() { observabilityAIAssistant, } = useAppContext(); + // If the AI Assistant is not available, don't render the settings tab + if (!observabilityAIAssistant) { + return null; + } + const { connectors = [], selectedConnector, diff --git a/src/plugins/ai_assistant_management/observability/public/routes/config.tsx b/src/plugins/ai_assistant_management/observability/public/routes/config.tsx index 447d817c95e10..990f4e964d2c4 100644 --- a/src/plugins/ai_assistant_management/observability/public/routes/config.tsx +++ b/src/plugins/ai_assistant_management/observability/public/routes/config.tsx @@ -11,7 +11,12 @@ import * as t from 'io-ts'; import { createRouter } from '@kbn/typed-react-router-config'; import { SettingsPage } from './components/settings_page'; -const Tabs = t.union([t.literal('settings'), t.literal('knowledge_base'), t.undefined]); +const Tabs = t.union([ + t.literal('settings'), + t.literal('knowledge_base'), + t.literal('search_connector'), + t.undefined, +]); export type TabsRt = t.TypeOf; const aIAssistantManagementObservabilityRoutes = { diff --git a/src/plugins/ai_assistant_management/observability/tsconfig.json b/src/plugins/ai_assistant_management/observability/tsconfig.json index 09822d6179850..4e83f07086a89 100644 --- a/src/plugins/ai_assistant_management/observability/tsconfig.json +++ b/src/plugins/ai_assistant_management/observability/tsconfig.json @@ -15,7 +15,8 @@ "@kbn/core-chrome-browser", "@kbn/observability-ai-assistant-plugin", "@kbn/serverless", - "@kbn/translations-plugin" + "@kbn/translations-plugin", + "@kbn/enterprise-search-plugin" ], "exclude": ["target/**/*"] } diff --git a/x-pack/plugins/enterprise_search/public/plugin.ts b/x-pack/plugins/enterprise_search/public/plugin.ts index 598732c5004d3..f38395e8b3340 100644 --- a/x-pack/plugins/enterprise_search/public/plugin.ts +++ b/x-pack/plugins/enterprise_search/public/plugin.ts @@ -437,13 +437,17 @@ export class EnterpriseSearchPlugin implements Plugin { } } - public async start(core: CoreStart) { + public start(core: CoreStart) { if (!this.config.ui?.enabled) { return; } // This must be called here in start() and not in `applications/index.tsx` to prevent loading // race conditions with our apps' `routes.ts` being initialized before `renderApp()` docLinks.setDocLinks(core.docLinks); + + // Return empty start contract rather than void in order for plugins + // that depend on the enterprise search plugin to determine whether it is enabled or not + return {}; } public stop() {} From 7ef1ca70a6c8a951fec6f88e0352f4b36ff2855e Mon Sep 17 00:00:00 2001 From: Thomas Watson Date: Tue, 13 Feb 2024 10:54:19 +0100 Subject: [PATCH 49/83] Clean up branch names in Renovate config (#176370) --- renovate.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/renovate.json b/renovate.json index 10289de37facd..4b89a0c6378e0 100644 --- a/renovate.json +++ b/renovate.json @@ -12,8 +12,7 @@ ], "baseBranches": [ "main", - "7.16", - "7.15" + "7.17" ], "prConcurrentLimit": 0, "prHourlyLimit": 0, From 466a05ae843f3aeefaa035121dbb9e642bd0eed6 Mon Sep 17 00:00:00 2001 From: Cristina Amico Date: Tue, 13 Feb 2024 11:02:20 +0100 Subject: [PATCH 50/83] [Fleet] Fix wrong policy_id in dev tools flyout request (#176729) Fixes https://github.com/elastic/kibana/issues/172798 ## Summary Small bug fix: when adding a new integration, the `policy_id` in dev tools flyout request was not correct Steps to reproduce: - Add one agent policy to the stack - Go to any integration and navigate to `add integration` page - Switch to `Existing hosts` and select an existing policy. - Open `Preview API request`. The policy id in the preview should be the one from the selected policy - Switch back to `New hosts` - Open `Preview API request`. The policy id should be `` again (not the one from the previously selected policy) ![Screenshot 2024-02-12 at 17 56 45](https://github.com/elastic/kibana/assets/16084106/6f2e5085-7418-4fef-ac4e-79b57d551722) --- .../single_page_layout/hooks/devtools_request.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/hooks/devtools_request.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/hooks/devtools_request.tsx index ca8c24b9a748d..90683ded5979c 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/hooks/devtools_request.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/hooks/devtools_request.tsx @@ -48,7 +48,7 @@ export function useDevToolsRequest({ newAgentPolicy, withSysMonitoring && !packagePolicyIsSystem )}\n\n${generateCreatePackagePolicyDevToolsRequest({ - ...packagePolicy, + ...{ ...packagePolicy, policy_id: '' }, })}`, i18n.translate( 'xpack.fleet.createPackagePolicy.devtoolsRequestWithAgentPolicyDescription', From 8477b6b8bc0f9a2b32e6650a8064d7b3cd461e9b Mon Sep 17 00:00:00 2001 From: Sander Philipse <94373878+sphilipse@users.noreply.github.com> Date: Tue, 13 Feb 2024 11:57:32 +0100 Subject: [PATCH 51/83] [Search] Fix multiple radio buttons conflicting in connector config (#176795) ## Summary This fixes an issue where multiple radio groups in a single connector config would conflict leading to UI glitches. --- .../components/configuration/connector_configuration_field.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/kbn-search-connectors/components/configuration/connector_configuration_field.tsx b/packages/kbn-search-connectors/components/configuration/connector_configuration_field.tsx index cb9108a783fdb..b26d27253d8f7 100644 --- a/packages/kbn-search-connectors/components/configuration/connector_configuration_field.tsx +++ b/packages/kbn-search-connectors/components/configuration/connector_configuration_field.tsx @@ -174,7 +174,7 @@ export const ConnectorConfigurationField: React.FC ({ id: option.value, label: option.label }))} onChange={(id) => { validateAndSetConfigValue(id); From db08fb5c5b307f087faad3afc6d14b661b31a7ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cau=C3=AA=20Marcondes?= <55978943+cauemarcondes@users.noreply.github.com> Date: Tue, 13 Feb 2024 10:58:35 +0000 Subject: [PATCH 52/83] [APM] Show Universal Profiling on Transaction view (#176302) New Setting: Default value is `False` Screenshot 2024-02-08 at 15 06 43 --- Screenshot 2024-02-08 at 14 59 17 Screenshot 2024-02-08 at 14 59 33 --- docs/management/advanced-options.asciidoc | 3 + packages/kbn-io-ts-utils/index.ts | 1 + .../src/iso_to_epoch_secs_rt/index.test.ts | 33 +++ .../src/iso_to_epoch_secs_rt/index.ts | 25 ++ .../server/collectors/management/schema.ts | 4 + .../server/collectors/management/types.ts | 1 + src/plugins/telemetry/schema/oss_plugins.json | 8 +- .../__snapshots__/es_fields.test.ts.snap | 6 + x-pack/plugins/apm/common/es_fields/apm.ts | 2 + .../profiling_flamegraph.tsx | 61 +---- .../profiling_top_functions.tsx | 27 +-- .../app/settings/general_settings/index.tsx | 5 +- .../profiling_flamegraph.tsx | 80 ++++++ .../app/transaction_details/profiling_tab.tsx | 122 ++++++++++ .../profiling_top_functions.tsx | 87 +++++++ .../transaction_details_tabs.tsx | 78 +++--- .../profiling/flamegraph/flamegraph_link.tsx | 50 ++++ .../shared/profiling/flamegraph/index.tsx | 44 ++++ .../top_functions/top_functions_link.tsx | 51 ++++ .../use_profiling_integration_setting.ts | 17 +- .../create_apm_event_client/index.ts | 4 + .../apm/server/routes/profiling/route.ts | 229 ++++++++++++++++-- .../lib/fetch_profiling_flamegraph.ts | 24 +- .../lib/fetch_profiling_functions.ts | 23 +- x-pack/plugins/observability/common/index.ts | 1 + .../observability/common/ui_settings_keys.ts | 1 + .../observability/server/ui_settings.ts | 10 + .../profiling/server/routes/flamechart.ts | 23 +- .../profiling/server/routes/functions.ts | 24 +- .../common/profiling_es_client.ts | 4 + .../server/services/fetch_flamechart/index.ts | 43 ++-- .../server/services/functions/index.ts | 25 +- .../services/search_stack_traces/index.ts | 34 +-- .../utils/create_profiling_es_client.ts | 8 + 34 files changed, 956 insertions(+), 202 deletions(-) create mode 100644 packages/kbn-io-ts-utils/src/iso_to_epoch_secs_rt/index.test.ts create mode 100644 packages/kbn-io-ts-utils/src/iso_to_epoch_secs_rt/index.ts create mode 100644 x-pack/plugins/apm/public/components/app/transaction_details/profiling_flamegraph.tsx create mode 100644 x-pack/plugins/apm/public/components/app/transaction_details/profiling_tab.tsx create mode 100644 x-pack/plugins/apm/public/components/app/transaction_details/profiling_top_functions.tsx create mode 100644 x-pack/plugins/apm/public/components/shared/profiling/flamegraph/flamegraph_link.tsx create mode 100644 x-pack/plugins/apm/public/components/shared/profiling/flamegraph/index.tsx create mode 100644 x-pack/plugins/apm/public/components/shared/profiling/top_functions/top_functions_link.tsx diff --git a/docs/management/advanced-options.asciidoc b/docs/management/advanced-options.asciidoc index 28f334d207514..4a77b7e7d0a49 100644 --- a/docs/management/advanced-options.asciidoc +++ b/docs/management/advanced-options.asciidoc @@ -448,6 +448,9 @@ Determines whether the <> is enable [[observability-apm-enable-infra-view]]`observability:enableInfrastructureView`:: Enables the Infrastructure view in the APM app. +[[observability-apm-enable-transaction-profiling]]`observability:apmEnableTransactionProfiling`:: +Enable Universal Profiling on Transaction view. + [[observability-enable-inspect-es-queries]]`observability:enableInspectEsQueries`:: When enabled, allows you to inspect {es} queries in API responses. diff --git a/packages/kbn-io-ts-utils/index.ts b/packages/kbn-io-ts-utils/index.ts index 8f1b974eccdcb..2a6c02f6cdf17 100644 --- a/packages/kbn-io-ts-utils/index.ts +++ b/packages/kbn-io-ts-utils/index.ts @@ -15,6 +15,7 @@ export { jsonRt } from './src/json_rt'; export { mergeRt } from './src/merge_rt'; export { strictKeysRt } from './src/strict_keys_rt'; export { isoToEpochRt } from './src/iso_to_epoch_rt'; +export { isoToEpochSecsRt } from './src/iso_to_epoch_secs_rt'; export { toNumberRt } from './src/to_number_rt'; export { toBooleanRt } from './src/to_boolean_rt'; export { toJsonSchema } from './src/to_json_schema'; diff --git a/packages/kbn-io-ts-utils/src/iso_to_epoch_secs_rt/index.test.ts b/packages/kbn-io-ts-utils/src/iso_to_epoch_secs_rt/index.test.ts new file mode 100644 index 0000000000000..d9c06a2f61b5c --- /dev/null +++ b/packages/kbn-io-ts-utils/src/iso_to_epoch_secs_rt/index.test.ts @@ -0,0 +1,33 @@ +/* + * 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 { isoToEpochSecsRt } from '.'; +import { isRight } from 'fp-ts/lib/Either'; + +describe('isoToEpochSecsRt', () => { + it('validates whether its input is a valid ISO timestamp', () => { + expect(isRight(isoToEpochSecsRt.decode(1566299881499))).toBe(false); + + expect(isRight(isoToEpochSecsRt.decode('2019-08-20T11:18:31.407Z'))).toBe(true); + }); + + it('decodes valid ISO timestamps to epoch secs time', () => { + const iso = '2019-08-20T11:18:31.407Z'; + const result = isoToEpochSecsRt.decode(iso); + + if (isRight(result)) { + expect(result.right).toBe(new Date(iso).getTime() / 1000); + } else { + fail(); + } + }); + + it('encodes epoch secs time to ISO string', () => { + expect(isoToEpochSecsRt.encode(1566299911407)).toBe('2019-08-20T11:18:31.407Z'); + }); +}); diff --git a/packages/kbn-io-ts-utils/src/iso_to_epoch_secs_rt/index.ts b/packages/kbn-io-ts-utils/src/iso_to_epoch_secs_rt/index.ts new file mode 100644 index 0000000000000..a4bbfdc59b603 --- /dev/null +++ b/packages/kbn-io-ts-utils/src/iso_to_epoch_secs_rt/index.ts @@ -0,0 +1,25 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { chain } from 'fp-ts/lib/Either'; +import { pipe } from 'fp-ts/lib/function'; +import * as t from 'io-ts'; +import { isoToEpochRt } from '../iso_to_epoch_rt'; + +export const isoToEpochSecsRt = new t.Type( + 'isoToEpochSecsRt', + t.number.is, + (value) => + pipe( + isoToEpochRt.decode(value), + chain((epochMsValue) => { + return t.success(epochMsValue / 1000); + }) + ), + (output) => new Date(output).toISOString() +); diff --git a/src/plugins/kibana_usage_collection/server/collectors/management/schema.ts b/src/plugins/kibana_usage_collection/server/collectors/management/schema.ts index b742808749aac..4a98048f0d168 100644 --- a/src/plugins/kibana_usage_collection/server/collectors/management/schema.ts +++ b/src/plugins/kibana_usage_collection/server/collectors/management/schema.ts @@ -568,6 +568,10 @@ export const stackManagementSchema: MakeSchemaFrom = { type: 'boolean', _meta: { description: 'Non-default value of setting.' }, }, + 'observability:apmEnableTransactionProfiling': { + type: 'boolean', + _meta: { description: 'Non-default value of setting.' }, + }, 'observability:profilingShowErrorFrames': { type: 'boolean', _meta: { description: 'Non-default value of setting.' }, diff --git a/src/plugins/kibana_usage_collection/server/collectors/management/types.ts b/src/plugins/kibana_usage_collection/server/collectors/management/types.ts index 347d71d2794cc..3ef4623444aeb 100644 --- a/src/plugins/kibana_usage_collection/server/collectors/management/types.ts +++ b/src/plugins/kibana_usage_collection/server/collectors/management/types.ts @@ -167,5 +167,6 @@ export interface UsageStats { 'observability:profilingCostPervCPUPerHour': number; 'observability:profilingAWSCostDiscountRate': number; 'data_views:fields_excluded_data_tiers': string; + 'observability:apmEnableTransactionProfiling': boolean; 'devTools:enableDockedConsole': boolean; } diff --git a/src/plugins/telemetry/schema/oss_plugins.json b/src/plugins/telemetry/schema/oss_plugins.json index 9f26915a2b9e9..47736fc79c944 100644 --- a/src/plugins/telemetry/schema/oss_plugins.json +++ b/src/plugins/telemetry/schema/oss_plugins.json @@ -10168,6 +10168,12 @@ "description": "Non-default value of setting." } }, + "observability:apmEnableTransactionProfiling": { + "type": "boolean", + "_meta": { + "description": "Non-default value of setting." + } + }, "observability:profilingShowErrorFrames": { "type": "boolean", "_meta": { @@ -11705,4 +11711,4 @@ } } } -} +} \ No newline at end of file diff --git a/x-pack/plugins/apm/common/es_fields/__snapshots__/es_fields.test.ts.snap b/x-pack/plugins/apm/common/es_fields/__snapshots__/es_fields.test.ts.snap index 0fa795b696455..f313ef593cf04 100644 --- a/x-pack/plugins/apm/common/es_fields/__snapshots__/es_fields.test.ts.snap +++ b/x-pack/plugins/apm/common/es_fields/__snapshots__/es_fields.test.ts.snap @@ -320,6 +320,8 @@ exports[`Error TRANSACTION_OVERFLOW_COUNT 1`] = `undefined`; exports[`Error TRANSACTION_PAGE_URL 1`] = `undefined`; +exports[`Error TRANSACTION_PROFILER_STACK_TRACE_IDS 1`] = `undefined`; + exports[`Error TRANSACTION_RESULT 1`] = `undefined`; exports[`Error TRANSACTION_ROOT 1`] = `undefined`; @@ -645,6 +647,8 @@ exports[`Span TRANSACTION_OVERFLOW_COUNT 1`] = `undefined`; exports[`Span TRANSACTION_PAGE_URL 1`] = `undefined`; +exports[`Span TRANSACTION_PROFILER_STACK_TRACE_IDS 1`] = `undefined`; + exports[`Span TRANSACTION_RESULT 1`] = `undefined`; exports[`Span TRANSACTION_ROOT 1`] = `undefined`; @@ -988,6 +992,8 @@ exports[`Transaction TRANSACTION_OVERFLOW_COUNT 1`] = `undefined`; exports[`Transaction TRANSACTION_PAGE_URL 1`] = `undefined`; +exports[`Transaction TRANSACTION_PROFILER_STACK_TRACE_IDS 1`] = `undefined`; + exports[`Transaction TRANSACTION_RESULT 1`] = `"transaction result"`; exports[`Transaction TRANSACTION_ROOT 1`] = `undefined`; diff --git a/x-pack/plugins/apm/common/es_fields/apm.ts b/x-pack/plugins/apm/common/es_fields/apm.ts index a6cef6ce73e11..05efeceb11f28 100644 --- a/x-pack/plugins/apm/common/es_fields/apm.ts +++ b/x-pack/plugins/apm/common/es_fields/apm.ts @@ -65,6 +65,8 @@ export const TRANSACTION_OVERFLOW_COUNT = 'transaction.aggregation.overflow_count'; // for transaction metrics export const TRANSACTION_ROOT = 'transaction.root'; +export const TRANSACTION_PROFILER_STACK_TRACE_IDS = + 'transaction.profiler_stack_trace_ids'; export const EVENT_OUTCOME = 'event.outcome'; diff --git a/x-pack/plugins/apm/public/components/app/profiling_overview/profiling_flamegraph.tsx b/x-pack/plugins/apm/public/components/app/profiling_overview/profiling_flamegraph.tsx index bbae54abdf708..2504ce687307b 100644 --- a/x-pack/plugins/apm/public/components/app/profiling_overview/profiling_flamegraph.tsx +++ b/x-pack/plugins/apm/public/components/app/profiling_overview/profiling_flamegraph.tsx @@ -5,16 +5,7 @@ * 2.0. */ -import { - EuiEmptyPrompt, - EuiFlexGroup, - EuiFlexItem, - EuiLink, - EuiSpacer, -} from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; -import { EmbeddableFlamegraph } from '@kbn/observability-shared-plugin/public'; -import { isEmpty } from 'lodash'; +import { EuiFlexGroup, EuiFlexItem, EuiSpacer } from '@elastic/eui'; import React from 'react'; import { ApmDataSourceWithSummary } from '../../../../common/data_source'; import { ApmDocumentType } from '../../../../common/document_type'; @@ -23,12 +14,9 @@ import { mergeKueries, toKueryFilterFormat, } from '../../../../common/utils/kuery_utils'; -import { - FETCH_STATUS, - isPending, - useFetcher, -} from '../../../hooks/use_fetcher'; -import { useProfilingPlugin } from '../../../hooks/use_profiling_plugin'; +import { useFetcher } from '../../../hooks/use_fetcher'; +import { ProfilingFlamegraphChart } from '../../shared/profiling/flamegraph'; +import { ProfilingFlamegraphLink } from '../../shared/profiling/flamegraph/flamegraph_link'; import { HostnamesFilterWarning } from './host_names_filter_warning'; interface Props { @@ -54,8 +42,6 @@ export function ProfilingFlamegraph({ rangeFrom, rangeTo, }: Props) { - const { profilingLocators } = useProfilingPlugin(); - const { data, status } = useFetcher( (callApmApi) => { if (dataSource) { @@ -92,41 +78,16 @@ export function ProfilingFlamegraph({ -
- - {i18n.translate('xpack.apm.profiling.flamegraph.link', { - defaultMessage: 'Go to Universal Profiling Flamegraph', - })} - -
+
- {status === FETCH_STATUS.SUCCESS && isEmpty(data) ? ( - - {i18n.translate('xpack.apm.profiling.flamegraph.noDataFound', { - defaultMessage: 'No data found', - })} - - } - /> - ) : ( - - )} + ); } diff --git a/x-pack/plugins/apm/public/components/app/profiling_overview/profiling_top_functions.tsx b/x-pack/plugins/apm/public/components/app/profiling_overview/profiling_top_functions.tsx index 0462af188d3f9..8785a4974a2d3 100644 --- a/x-pack/plugins/apm/public/components/app/profiling_overview/profiling_top_functions.tsx +++ b/x-pack/plugins/apm/public/components/app/profiling_overview/profiling_top_functions.tsx @@ -5,8 +5,7 @@ * 2.0. */ -import { EuiFlexGroup, EuiFlexItem, EuiLink, EuiSpacer } from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; +import { EuiFlexGroup, EuiFlexItem, EuiSpacer } from '@elastic/eui'; import { EmbeddableFunctions } from '@kbn/observability-shared-plugin/public'; import React from 'react'; import { ApmDataSourceWithSummary } from '../../../../common/data_source'; @@ -17,7 +16,7 @@ import { toKueryFilterFormat, } from '../../../../common/utils/kuery_utils'; import { isPending, useFetcher } from '../../../hooks/use_fetcher'; -import { useProfilingPlugin } from '../../../hooks/use_profiling_plugin'; +import { ProfilingTopNFunctionsLink } from '../../shared/profiling/top_functions/top_functions_link'; import { HostnamesFilterWarning } from './host_names_filter_warning'; interface Props { @@ -47,8 +46,6 @@ export function ProfilingTopNFunctions({ rangeFrom, rangeTo, }: Props) { - const { profilingLocators } = useProfilingPlugin(); - const { data, status } = useFetcher( (callApmApi) => { if (dataSource) { @@ -96,20 +93,12 @@ export function ProfilingTopNFunctions({ -
- - {i18n.translate('xpack.apm.profiling.topnFunctions.link', { - defaultMessage: 'Go to Universal Profiling Functions', - })} - -
+
diff --git a/x-pack/plugins/apm/public/components/app/settings/general_settings/index.tsx b/x-pack/plugins/apm/public/components/app/settings/general_settings/index.tsx index 5eae7486a9290..b21ece193cef5 100644 --- a/x-pack/plugins/apm/public/components/app/settings/general_settings/index.tsx +++ b/x-pack/plugins/apm/public/components/app/settings/general_settings/index.tsx @@ -21,6 +21,7 @@ import { enableAgentExplorerView, apmEnableProfilingIntegration, apmEnableTableSearchBar, + apmEnableTransactionProfiling, } from '@kbn/observability-plugin/common'; import { isEmpty } from 'lodash'; import React from 'react'; @@ -57,7 +58,9 @@ function getApmSettingsKeys(isProfilingIntegrationEnabled: boolean) { ]; if (isProfilingIntegrationEnabled) { - keys.push(apmEnableProfilingIntegration); + keys.push( + ...[apmEnableProfilingIntegration, apmEnableTransactionProfiling] + ); } return keys; diff --git a/x-pack/plugins/apm/public/components/app/transaction_details/profiling_flamegraph.tsx b/x-pack/plugins/apm/public/components/app/transaction_details/profiling_flamegraph.tsx new file mode 100644 index 0000000000000..2f87d2be50214 --- /dev/null +++ b/x-pack/plugins/apm/public/components/app/transaction_details/profiling_flamegraph.tsx @@ -0,0 +1,80 @@ +/* + * 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 { EuiSpacer } from '@elastic/eui'; +import React from 'react'; +import { useFetcher } from '../../../hooks/use_fetcher'; +import { useTimeRange } from '../../../hooks/use_time_range'; +import { ProfilingFlamegraphChart } from '../../shared/profiling/flamegraph'; +import { ProfilingFlamegraphLink } from '../../shared/profiling/flamegraph/flamegraph_link'; + +interface Props { + serviceName: string; + rangeFrom: string; + rangeTo: string; + kuery: string; + transactionName: string; + transactionType?: string; + environment: string; +} + +export function ProfilingFlamegraph({ + serviceName, + rangeFrom, + rangeTo, + kuery, + transactionName, + transactionType, + environment, +}: Props) { + const { start, end } = useTimeRange({ rangeFrom, rangeTo }); + + const { data, status } = useFetcher( + (callApmApi) => { + if (!transactionType) { + return; + } + return callApmApi( + 'GET /internal/apm/services/{serviceName}/transactions/flamegraph', + { + params: { + path: { serviceName }, + query: { + start, + end, + kuery, + transactionName, + transactionType, + environment, + }, + }, + } + ); + }, + [ + serviceName, + start, + end, + kuery, + transactionName, + transactionType, + environment, + ] + ); + + return ( + <> + + + + + ); +} diff --git a/x-pack/plugins/apm/public/components/app/transaction_details/profiling_tab.tsx b/x-pack/plugins/apm/public/components/app/transaction_details/profiling_tab.tsx new file mode 100644 index 0000000000000..1728e214ff8f3 --- /dev/null +++ b/x-pack/plugins/apm/public/components/app/transaction_details/profiling_tab.tsx @@ -0,0 +1,122 @@ +/* + * 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 { + EuiFlexGroup, + EuiFlexItem, + EuiLoadingSpinner, + EuiSpacer, + EuiTabbedContent, + EuiTabbedContentProps, +} from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import React, { useMemo } from 'react'; +import { ProfilingEmptyState } from '@kbn/observability-shared-plugin/public'; +import { useApmParams } from '../../../hooks/use_apm_params'; +import { useProfilingPlugin } from '../../../hooks/use_profiling_plugin'; +import { ProfilingFlamegraph } from './profiling_flamegraph'; +import { ProfilingTopNFunctions } from './profiling_top_functions'; + +function ProfilingTab() { + const { + query: { + rangeFrom, + rangeTo, + environment, + kuery, + transactionName, + transactionType, + }, + path: { serviceName }, + } = useApmParams('/services/{serviceName}/transactions/view'); + const { isProfilingAvailable, isLoading } = useProfilingPlugin(); + + const tabs = useMemo((): EuiTabbedContentProps['tabs'] => { + return [ + { + id: 'flamegraph', + name: i18n.translate( + 'xpack.apm.transactions.profiling.tabs.flamegraph', + { defaultMessage: 'Flamegraph' } + ), + content: ( + <> + + + + ), + }, + { + id: 'topNFunctions', + name: i18n.translate( + 'xpack.apm.transactions.profiling.tabs.topNFunctions', + { defaultMessage: 'Top 10 Functions' } + ), + content: ( + <> + + + + ), + }, + ]; + }, [ + environment, + kuery, + rangeFrom, + rangeTo, + serviceName, + transactionName, + transactionType, + ]); + + if (isLoading) { + return ( + + + + + + ); + } + + if (isProfilingAvailable === false) { + return ; + } + + return ( + + ); +} + +export const profilingTab = { + dataTestSubj: 'apmProfilingTabButton', + key: 'Profiling', + label: i18n.translate('xpack.apm.transactionDetails.tabs.ProfilingLabel', { + defaultMessage: 'Universal Profiling', + }), + component: ProfilingTab, +}; diff --git a/x-pack/plugins/apm/public/components/app/transaction_details/profiling_top_functions.tsx b/x-pack/plugins/apm/public/components/app/transaction_details/profiling_top_functions.tsx new file mode 100644 index 0000000000000..96ef5f3497346 --- /dev/null +++ b/x-pack/plugins/apm/public/components/app/transaction_details/profiling_top_functions.tsx @@ -0,0 +1,87 @@ +/* + * 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 { EuiSpacer } from '@elastic/eui'; +import { EmbeddableFunctions } from '@kbn/observability-shared-plugin/public'; +import React from 'react'; +import { isPending, useFetcher } from '../../../hooks/use_fetcher'; +import { useTimeRange } from '../../../hooks/use_time_range'; +import { ProfilingTopNFunctionsLink } from '../../shared/profiling/top_functions/top_functions_link'; + +interface Props { + serviceName: string; + rangeFrom: string; + rangeTo: string; + kuery: string; + transactionName: string; + transactionType?: string; + environment: string; +} + +export function ProfilingTopNFunctions({ + serviceName, + rangeFrom, + rangeTo, + kuery, + transactionName, + transactionType, + environment, +}: Props) { + const { start, end } = useTimeRange({ rangeFrom, rangeTo }); + + const { data, status } = useFetcher( + (callApmApi) => { + if (!transactionType) { + return; + } + return callApmApi( + 'GET /internal/apm/services/{serviceName}/transactions/functions', + { + params: { + path: { serviceName }, + query: { + start, + end, + kuery, + transactionName, + startIndex: 0, + endIndex: 10, + transactionType, + environment, + }, + }, + } + ); + }, + [ + serviceName, + start, + end, + kuery, + transactionName, + transactionType, + environment, + ] + ); + + return ( + <> + + + + + ); +} diff --git a/x-pack/plugins/apm/public/components/app/transaction_details/transaction_details_tabs.tsx b/x-pack/plugins/apm/public/components/app/transaction_details/transaction_details_tabs.tsx index ba74d9e94d08e..56511f94fb38d 100644 --- a/x-pack/plugins/apm/public/components/app/transaction_details/transaction_details_tabs.tsx +++ b/x-pack/plugins/apm/public/components/app/transaction_details/transaction_details_tabs.tsx @@ -5,37 +5,29 @@ * 2.0. */ -import React, { useCallback, useEffect, useState } from 'react'; +import React, { useCallback, useEffect, useMemo, useState } from 'react'; +import { XYBrushEvent } from '@elastic/charts'; +import { EuiPanel, EuiSpacer, EuiTab, EuiTabs } from '@elastic/eui'; import { omit } from 'lodash'; import { useHistory } from 'react-router-dom'; -import { - EuiPanel, - EuiSpacer, - EuiTabs, - EuiTab, - EuiFlexItem, - EuiFlexGroup, -} from '@elastic/eui'; - -import { XYBrushEvent } from '@elastic/charts'; +import { maybe } from '../../../../common/utils/maybe'; import { useLegacyUrlParams } from '../../../context/url_params_context/use_url_params'; import { useAnyOfApmParams } from '../../../hooks/use_apm_params'; +import { useCriticalPathFeatureEnabledSetting } from '../../../hooks/use_critical_path_feature_enabled_setting'; +import { FETCH_STATUS } from '../../../hooks/use_fetcher'; +import { useSampleChartSelection } from '../../../hooks/use_sample_chart_selection'; import { TraceSamplesFetchResult, useTransactionTraceSamplesFetcher, } from '../../../hooks/use_transaction_trace_samples_fetcher'; - -import { maybe } from '../../../../common/utils/maybe'; import { fromQuery, toQuery } from '../../shared/links/url_helpers'; - +import { aggregatedCriticalPathTab } from './aggregated_critical_path_tab'; import { failedTransactionsCorrelationsTab } from './failed_transactions_correlations_tab'; import { latencyCorrelationsTab } from './latency_correlations_tab'; +import { profilingTab } from './profiling_tab'; import { traceSamplesTab } from './trace_samples_tab'; -import { useSampleChartSelection } from '../../../hooks/use_sample_chart_selection'; -import { FETCH_STATUS } from '../../../hooks/use_fetcher'; -import { useCriticalPathFeatureEnabledSetting } from '../../../hooks/use_critical_path_feature_enabled_setting'; -import { aggregatedCriticalPathTab } from './aggregated_critical_path_tab'; +import { useTransactionProfilingSetting } from '../../../hooks/use_profiling_integration_setting'; export interface TabContentProps { clearChartSelection: () => void; @@ -46,12 +38,6 @@ export interface TabContentProps { traceSamplesFetchResult: TraceSamplesFetchResult; } -const tabs = [ - traceSamplesTab, - latencyCorrelationsTab, - failedTransactionsCorrelationsTab, -]; - export function TransactionDetailsTabs() { const { query } = useAnyOfApmParams( '/services/{serviceName}/transactions/view', @@ -59,10 +45,24 @@ export function TransactionDetailsTabs() { ); const isCriticalPathFeatureEnabled = useCriticalPathFeatureEnabledSetting(); + const isTransactionProfilingEnabled = useTransactionProfilingSetting(); + + const availableTabs = useMemo(() => { + const tabs = [ + traceSamplesTab, + latencyCorrelationsTab, + failedTransactionsCorrelationsTab, + ]; + if (isCriticalPathFeatureEnabled) { + tabs.push(aggregatedCriticalPathTab); + } + + if (isTransactionProfilingEnabled) { + tabs.push(profilingTab); + } - const availableTabs = isCriticalPathFeatureEnabled - ? tabs.concat(aggregatedCriticalPathTab) - : tabs; + return tabs; + }, [isCriticalPathFeatureEnabled, isTransactionProfilingEnabled]); const { urlParams } = useLegacyUrlParams(); const history = useHistory(); @@ -140,20 +140,16 @@ export function TransactionDetailsTabs() { - - - - - + ); diff --git a/x-pack/plugins/apm/public/components/shared/profiling/flamegraph/flamegraph_link.tsx b/x-pack/plugins/apm/public/components/shared/profiling/flamegraph/flamegraph_link.tsx new file mode 100644 index 0000000000000..2c2349ab8d6bf --- /dev/null +++ b/x-pack/plugins/apm/public/components/shared/profiling/flamegraph/flamegraph_link.tsx @@ -0,0 +1,50 @@ +/* + * 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 { + EuiFlexGroup, + EuiFlexGroupProps, + EuiFlexItem, + EuiLink, +} from '@elastic/eui'; +import React from 'react'; +import { i18n } from '@kbn/i18n'; +import { useProfilingPlugin } from '../../../../hooks/use_profiling_plugin'; + +interface Props { + kuery: string; + rangeFrom: string; + rangeTo: string; + justifyContent?: EuiFlexGroupProps['justifyContent']; +} + +export function ProfilingFlamegraphLink({ + kuery, + rangeFrom, + rangeTo, + justifyContent = 'flexStart', +}: Props) { + const { profilingLocators } = useProfilingPlugin(); + return ( + + + + {i18n.translate('xpack.apm.profiling.flamegraph.link', { + defaultMessage: 'Go to Universal Profiling Flamegraph', + })} + + + + ); +} diff --git a/x-pack/plugins/apm/public/components/shared/profiling/flamegraph/index.tsx b/x-pack/plugins/apm/public/components/shared/profiling/flamegraph/index.tsx new file mode 100644 index 0000000000000..cfe92331486a2 --- /dev/null +++ b/x-pack/plugins/apm/public/components/shared/profiling/flamegraph/index.tsx @@ -0,0 +1,44 @@ +/* + * 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 { EuiEmptyPrompt } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { EmbeddableFlamegraph } from '@kbn/observability-shared-plugin/public'; +import { BaseFlameGraph } from '@kbn/profiling-utils'; +import { isEmpty } from 'lodash'; +import React from 'react'; +import { FETCH_STATUS, isPending } from '../../../../hooks/use_fetcher'; + +interface Props { + data?: BaseFlameGraph; + status: FETCH_STATUS; +} + +export function ProfilingFlamegraphChart({ data, status }: Props) { + return ( + <> + {status === FETCH_STATUS.SUCCESS && + (isEmpty(data) || data?.TotalSamples === 0) ? ( + + {i18n.translate('xpack.apm.profiling.flamegraph.noDataFound', { + defaultMessage: 'No data found', + })} + + } + /> + ) : ( + + )} + + ); +} diff --git a/x-pack/plugins/apm/public/components/shared/profiling/top_functions/top_functions_link.tsx b/x-pack/plugins/apm/public/components/shared/profiling/top_functions/top_functions_link.tsx new file mode 100644 index 0000000000000..852abd9a94376 --- /dev/null +++ b/x-pack/plugins/apm/public/components/shared/profiling/top_functions/top_functions_link.tsx @@ -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 { + EuiFlexGroup, + EuiFlexGroupProps, + EuiFlexItem, + EuiLink, +} from '@elastic/eui'; +import React from 'react'; +import { i18n } from '@kbn/i18n'; +import { useProfilingPlugin } from '../../../../hooks/use_profiling_plugin'; + +interface Props { + kuery: string; + rangeFrom: string; + rangeTo: string; + justifyContent?: EuiFlexGroupProps['justifyContent']; +} + +export function ProfilingTopNFunctionsLink({ + kuery, + rangeFrom, + rangeTo, + justifyContent = 'flexStart', +}: Props) { + const { profilingLocators } = useProfilingPlugin(); + + return ( + + + + {i18n.translate('xpack.apm.profiling.topnFunctions.link', { + defaultMessage: 'Go to Universal Profiling Functions', + })} + + + + ); +} diff --git a/x-pack/plugins/apm/public/hooks/use_profiling_integration_setting.ts b/x-pack/plugins/apm/public/hooks/use_profiling_integration_setting.ts index 1bf19e1043e91..bb35b8db6e451 100644 --- a/x-pack/plugins/apm/public/hooks/use_profiling_integration_setting.ts +++ b/x-pack/plugins/apm/public/hooks/use_profiling_integration_setting.ts @@ -6,8 +6,12 @@ */ import { useUiSetting } from '@kbn/kibana-react-plugin/public'; -import { apmEnableProfilingIntegration } from '@kbn/observability-plugin/common'; +import { + apmEnableProfilingIntegration, + apmEnableTransactionProfiling, +} from '@kbn/observability-plugin/common'; import { ApmFeatureFlagName } from '../../common/apm_feature_flags'; +import { useApmPluginContext } from '../context/apm_plugin/use_apm_plugin_context'; import { useApmFeatureFlag } from './use_apm_feature_flag'; export function useProfilingIntegrationSetting() { @@ -23,3 +27,14 @@ export function useProfilingIntegrationSetting() { isProfilingIntegrationUiSettingEnabled ); } + +export function useTransactionProfilingSetting() { + const { core } = useApmPluginContext(); + const isProfilingIntegrationEnabled = useProfilingIntegrationSetting(); + + const isTransactionProfilingEnabled = core.uiSettings.get( + apmEnableTransactionProfiling + ); + + return isProfilingIntegrationEnabled && isTransactionProfilingEnabled; +} diff --git a/x-pack/plugins/apm/server/lib/helpers/create_es_client/create_apm_event_client/index.ts b/x-pack/plugins/apm/server/lib/helpers/create_es_client/create_apm_event_client/index.ts index 53e4c91f384f3..1a180d2adaf14 100644 --- a/x-pack/plugins/apm/server/lib/helpers/create_es_client/create_apm_event_client/index.ts +++ b/x-pack/plugins/apm/server/lib/helpers/create_es_client/create_apm_event_client/index.ts @@ -342,4 +342,8 @@ export class APMEventClient { cb: (opts) => this.esClient.termsEnum(requestParams, opts), }); } + + getIndicesFromProcessorEvent(processorEvent: ProcessorEvent) { + return processorEventsToIndex([processorEvent], this.indices); + } } diff --git a/x-pack/plugins/apm/server/routes/profiling/route.ts b/x-pack/plugins/apm/server/routes/profiling/route.ts index 0421cb994124a..9009f60da03a1 100644 --- a/x-pack/plugins/apm/server/routes/profiling/route.ts +++ b/x-pack/plugins/apm/server/routes/profiling/route.ts @@ -5,10 +5,18 @@ * 2.0. */ -import { toNumberRt } from '@kbn/io-ts-utils'; +import { isoToEpochSecsRt, toNumberRt } from '@kbn/io-ts-utils'; import type { BaseFlameGraph, TopNFunctions } from '@kbn/profiling-utils'; import * as t from 'io-ts'; -import { HOST_NAME } from '../../../common/es_fields/apm'; +import { kqlQuery, termQuery } from '@kbn/observability-plugin/server'; +import { ProcessorEvent } from '@kbn/observability-plugin/common'; +import { + HOST_NAME, + SERVICE_NAME, + TRANSACTION_NAME, + TRANSACTION_PROFILER_STACK_TRACE_IDS, + TRANSACTION_TYPE, +} from '../../../common/es_fields/apm'; import { mergeKueries, toKueryFilterFormat, @@ -22,6 +30,7 @@ import { serviceTransactionDataSourceRt, } from '../default_api_types'; import { getServiceHostNames } from './get_service_host_names'; +import { environmentQuery } from '../../../common/utils/environment_query'; const profilingFlamegraphRoute = createApmServerRoute({ endpoint: 'GET /internal/apm/services/{serviceName}/profiling/flamegraph', @@ -66,17 +75,35 @@ const profilingFlamegraphRoute = createApmServerRoute({ if (!serviceHostNames.length) { return undefined; } + const startSecs = start / 1000; + const endSecs = end / 1000; const flamegraph = await profilingDataAccessStart?.services.fetchFlamechartData({ core, esClient: esClient.asCurrentUser, - rangeFromMs: start, - rangeToMs: end, - kuery: mergeKueries([ - `(${toKueryFilterFormat(HOST_NAME, serviceHostNames)})`, - kuery, - ]), + totalSeconds: endSecs - startSecs, + query: { + bool: { + filter: [ + ...kqlQuery( + mergeKueries([ + `(${toKueryFilterFormat(HOST_NAME, serviceHostNames)})`, + kuery, + ]) + ), + { + range: { + ['@timestamp']: { + gte: String(startSecs), + lt: String(endSecs), + format: 'epoch_second', + }, + }, + }, + ], + }, + }, }); return { flamegraph, hostNames: serviceHostNames }; @@ -137,17 +164,36 @@ const profilingFunctionsRoute = createApmServerRoute({ return undefined; } + const startSecs = start / 1000; + const endSecs = end / 1000; + const functions = await profilingDataAccessStart?.services.fetchFunction({ core, esClient: esClient.asCurrentUser, - rangeFromMs: start, - rangeToMs: end, - kuery: mergeKueries([ - `(${toKueryFilterFormat(HOST_NAME, serviceHostNames)})`, - kuery, - ]), startIndex, endIndex, + totalSeconds: endSecs - startSecs, + query: { + bool: { + filter: [ + ...kqlQuery( + mergeKueries([ + `(${toKueryFilterFormat(HOST_NAME, serviceHostNames)})`, + kuery, + ]) + ), + { + range: { + ['@timestamp']: { + gte: String(startSecs), + lt: String(endSecs), + format: 'epoch_second', + }, + }, + }, + ], + }, + }, }); return { functions, hostNames: serviceHostNames }; } @@ -156,6 +202,159 @@ const profilingFunctionsRoute = createApmServerRoute({ }, }); +const transactionsFlamegraphRoute = createApmServerRoute({ + endpoint: 'GET /internal/apm/services/{serviceName}/transactions/flamegraph', + params: t.type({ + path: t.type({ serviceName: t.string }), + query: t.intersection([ + kueryRt, + environmentRt, + t.type({ + transactionName: t.string, + start: isoToEpochSecsRt, + end: isoToEpochSecsRt, + transactionType: t.string, + }), + ]), + }), + options: { tags: ['access:apm'] }, + handler: async (resources): Promise => { + const { context, plugins, params } = resources; + const core = await context.core; + const [esClient, profilingDataAccessStart, apmEventClient] = + await Promise.all([ + core.elasticsearch.client, + await plugins.profilingDataAccess?.start(), + getApmEventClient(resources), + ]); + if (profilingDataAccessStart) { + const { serviceName } = params.path; + const { + start, + end, + kuery, + transactionName, + transactionType, + environment, + } = params.query; + + const indices = apmEventClient.getIndicesFromProcessorEvent( + ProcessorEvent.transaction + ); + + return await profilingDataAccessStart?.services.fetchFlamechartData({ + core, + esClient: esClient.asCurrentUser, + indices, + stacktraceIdsField: TRANSACTION_PROFILER_STACK_TRACE_IDS, + totalSeconds: end - start, + query: { + bool: { + filter: [ + ...kqlQuery(kuery), + ...termQuery(SERVICE_NAME, serviceName), + ...termQuery(TRANSACTION_NAME, transactionName), + ...environmentQuery(environment), + ...termQuery(TRANSACTION_TYPE, transactionType), + { + range: { + ['@timestamp']: { + gte: String(start), + lt: String(end), + format: 'epoch_second', + }, + }, + }, + ], + }, + }, + }); + } + + return undefined; + }, +}); + +const transactionsFunctionsRoute = createApmServerRoute({ + endpoint: 'GET /internal/apm/services/{serviceName}/transactions/functions', + params: t.type({ + path: t.type({ serviceName: t.string }), + query: t.intersection([ + environmentRt, + t.type({ + start: isoToEpochSecsRt, + end: isoToEpochSecsRt, + startIndex: toNumberRt, + endIndex: toNumberRt, + transactionName: t.string, + transactionType: t.string, + }), + kueryRt, + ]), + }), + options: { tags: ['access:apm'] }, + handler: async (resources): Promise => { + const { context, plugins, params } = resources; + const core = await context.core; + + const [esClient, profilingDataAccessStart, apmEventClient] = + await Promise.all([ + core.elasticsearch.client, + await plugins.profilingDataAccess?.start(), + getApmEventClient(resources), + ]); + if (profilingDataAccessStart) { + const { + start, + end, + startIndex, + endIndex, + kuery, + transactionName, + transactionType, + environment, + } = params.query; + const { serviceName } = params.path; + + const indices = apmEventClient.getIndicesFromProcessorEvent( + ProcessorEvent.transaction + ); + + return profilingDataAccessStart?.services.fetchFunction({ + core, + esClient: esClient.asCurrentUser, + startIndex, + endIndex, + indices, + stacktraceIdsField: TRANSACTION_PROFILER_STACK_TRACE_IDS, + totalSeconds: end - start, + query: { + bool: { + filter: [ + ...kqlQuery(kuery), + ...termQuery(SERVICE_NAME, serviceName), + ...termQuery(TRANSACTION_NAME, transactionName), + ...environmentQuery(environment), + ...termQuery(TRANSACTION_TYPE, transactionType), + { + range: { + ['@timestamp']: { + gte: String(start), + lt: String(end), + format: 'epoch_second', + }, + }, + }, + ], + }, + }, + }); + } + + return undefined; + }, +}); + const profilingStatusRoute = createApmServerRoute({ endpoint: 'GET /internal/apm/profiling/status', options: { tags: ['access:apm'] }, @@ -190,4 +389,6 @@ export const profilingRouteRepository = { ...profilingFlamegraphRoute, ...profilingStatusRoute, ...profilingFunctionsRoute, + ...transactionsFlamegraphRoute, + ...transactionsFunctionsRoute, }; diff --git a/x-pack/plugins/infra/server/routes/profiling/lib/fetch_profiling_flamegraph.ts b/x-pack/plugins/infra/server/routes/profiling/lib/fetch_profiling_flamegraph.ts index 8b3ab0414f5c8..51e2e9f8d7f11 100644 --- a/x-pack/plugins/infra/server/routes/profiling/lib/fetch_profiling_flamegraph.ts +++ b/x-pack/plugins/infra/server/routes/profiling/lib/fetch_profiling_flamegraph.ts @@ -8,6 +8,7 @@ import type { CoreRequestHandlerContext } from '@kbn/core-http-request-handler-context-server'; import type { ProfilingDataAccessPluginStart } from '@kbn/profiling-data-access-plugin/server'; import type { BaseFlameGraph } from '@kbn/profiling-utils'; +import { kqlQuery } from '@kbn/observability-plugin/server'; import type { InfraProfilingFlamegraphRequestParams } from '../../../../common/http_api/profiling_api'; export async function fetchProfilingFlamegraph( @@ -15,11 +16,28 @@ export async function fetchProfilingFlamegraph( profilingDataAccess: ProfilingDataAccessPluginStart, coreRequestContext: CoreRequestHandlerContext ): Promise { + const startSecs = from / 1000; + const endSecs = to / 1000; + return await profilingDataAccess.services.fetchFlamechartData({ core: coreRequestContext, esClient: coreRequestContext.elasticsearch.client.asCurrentUser, - rangeFromMs: from, - rangeToMs: to, - kuery, + totalSeconds: endSecs - startSecs, + query: { + bool: { + filter: [ + ...kqlQuery(kuery), + { + range: { + ['@timestamp']: { + gte: String(startSecs), + lt: String(endSecs), + format: 'epoch_second', + }, + }, + }, + ], + }, + }, }); } diff --git a/x-pack/plugins/infra/server/routes/profiling/lib/fetch_profiling_functions.ts b/x-pack/plugins/infra/server/routes/profiling/lib/fetch_profiling_functions.ts index fb78075ca3545..fdf2399be4faa 100644 --- a/x-pack/plugins/infra/server/routes/profiling/lib/fetch_profiling_functions.ts +++ b/x-pack/plugins/infra/server/routes/profiling/lib/fetch_profiling_functions.ts @@ -8,6 +8,7 @@ import type { CoreRequestHandlerContext } from '@kbn/core-http-request-handler-context-server'; import type { ProfilingDataAccessPluginStart } from '@kbn/profiling-data-access-plugin/server'; import type { TopNFunctions } from '@kbn/profiling-utils'; +import { kqlQuery } from '@kbn/observability-plugin/server'; import type { InfraProfilingFunctionsRequestParams } from '../../../../common/http_api/profiling_api'; export async function fetchProfilingFunctions( @@ -16,14 +17,30 @@ export async function fetchProfilingFunctions( coreRequestContext: CoreRequestHandlerContext ): Promise { const { kuery, from, to, startIndex, endIndex } = params; + const startSecs = from / 1000; + const endSecs = to / 1000; return await profilingDataAccess.services.fetchFunction({ core: coreRequestContext, esClient: coreRequestContext.elasticsearch.client.asCurrentUser, - rangeFromMs: from, - rangeToMs: to, - kuery, startIndex, endIndex, + totalSeconds: endSecs - startSecs, + query: { + bool: { + filter: [ + ...kqlQuery(kuery), + { + range: { + ['@timestamp']: { + gte: String(startSecs), + lt: String(endSecs), + format: 'epoch_second', + }, + }, + }, + ], + }, + }, }); } diff --git a/x-pack/plugins/observability/common/index.ts b/x-pack/plugins/observability/common/index.ts index 4d2f8c560261c..850be12f6bb7f 100644 --- a/x-pack/plugins/observability/common/index.ts +++ b/x-pack/plugins/observability/common/index.ts @@ -51,6 +51,7 @@ export { profilingPervCPUWattArm64, profilingAWSCostDiscountRate, profilingCostPervCPUPerHour, + apmEnableTransactionProfiling, } from './ui_settings_keys'; export { diff --git a/x-pack/plugins/observability/common/ui_settings_keys.ts b/x-pack/plugins/observability/common/ui_settings_keys.ts index 366aa3ce860b6..a5d251694cdd1 100644 --- a/x-pack/plugins/observability/common/ui_settings_keys.ts +++ b/x-pack/plugins/observability/common/ui_settings_keys.ts @@ -39,3 +39,4 @@ export const profilingCo2PerKWH = 'observability:profilingCo2PerKWH'; export const profilingDatacenterPUE = 'observability:profilingDatacenterPUE'; export const profilingAWSCostDiscountRate = 'observability:profilingAWSCostDiscountRate'; export const profilingCostPervCPUPerHour = 'observability:profilingCostPervCPUPerHour'; +export const apmEnableTransactionProfiling = 'observability:apmEnableTransactionProfiling'; diff --git a/x-pack/plugins/observability/server/ui_settings.ts b/x-pack/plugins/observability/server/ui_settings.ts index 02717b59f2f4e..789b0e2676e64 100644 --- a/x-pack/plugins/observability/server/ui_settings.ts +++ b/x-pack/plugins/observability/server/ui_settings.ts @@ -39,6 +39,7 @@ import { profilingAWSCostDiscountRate, profilingCostPervCPUPerHour, enableInfrastructureProfilingIntegration, + apmEnableTransactionProfiling, enableInfrastructureHostsCustomDashboards, } from '../common/ui_settings_keys'; @@ -552,6 +553,15 @@ export const uiSettings: Record = { schema: schema.number({ min: 0, max: 100 }), requiresPageReload: true, }, + [apmEnableTransactionProfiling]: { + category: [observabilityFeatureId], + name: i18n.translate('xpack.observability.apmEnableTransactionProfiling', { + defaultMessage: 'Enable Universal Profiling on Transaction view', + }), + value: false, + schema: schema.boolean(), + requiresPageReload: true, + }, }; function throttlingDocsLink({ href }: { href: string }) { diff --git a/x-pack/plugins/profiling/server/routes/flamechart.ts b/x-pack/plugins/profiling/server/routes/flamechart.ts index ac8d988e5f0ca..86d384f62f609 100644 --- a/x-pack/plugins/profiling/server/routes/flamechart.ts +++ b/x-pack/plugins/profiling/server/routes/flamechart.ts @@ -6,6 +6,7 @@ */ import { schema } from '@kbn/config-schema'; +import { kqlQuery } from '@kbn/observability-plugin/server'; import { IDLE_SOCKET_TIMEOUT, RouteRegisterParameters } from '.'; import { getRoutePaths } from '../../common'; import { handleRouteHandlerError } from '../utils/handle_route_error_handler'; @@ -35,15 +36,31 @@ export function registerFlameChartSearchRoute({ const { timeFrom, timeTo, kuery } = request.query; const core = await context.core; + const startSecs = timeFrom / 1000; + const endSecs = timeTo / 1000; try { const esClient = await getClient(context); const flamegraph = await profilingDataAccess.services.fetchFlamechartData({ core, esClient, - rangeFromMs: timeFrom, - rangeToMs: timeTo, - kuery, + totalSeconds: endSecs - startSecs, + query: { + bool: { + filter: [ + ...kqlQuery(kuery), + { + range: { + ['@timestamp']: { + gte: String(startSecs), + lt: String(endSecs), + format: 'epoch_second', + }, + }, + }, + ], + }, + }, }); return response.ok({ body: flamegraph }); diff --git a/x-pack/plugins/profiling/server/routes/functions.ts b/x-pack/plugins/profiling/server/routes/functions.ts index ca118cc54a81b..a261708c5b457 100644 --- a/x-pack/plugins/profiling/server/routes/functions.ts +++ b/x-pack/plugins/profiling/server/routes/functions.ts @@ -6,6 +6,7 @@ */ import { schema, TypeOf } from '@kbn/config-schema'; +import { kqlQuery } from '@kbn/observability-plugin/server'; import { IDLE_SOCKET_TIMEOUT, RouteRegisterParameters } from '.'; import { getRoutePaths } from '../../common'; import { handleRouteHandlerError } from '../utils/handle_route_error_handler'; @@ -40,15 +41,32 @@ export function registerTopNFunctionsSearchRoute({ const core = await context.core; const { timeFrom, timeTo, startIndex, endIndex, kuery }: QuerySchemaType = request.query; + const startSecs = timeFrom / 1000; + const endSecs = timeTo / 1000; + const esClient = await getClient(context); const topNFunctions = await profilingDataAccess.services.fetchFunction({ core, esClient, - rangeFromMs: timeFrom, - rangeToMs: timeTo, - kuery, startIndex, endIndex, + totalSeconds: endSecs - startSecs, + query: { + bool: { + filter: [ + ...kqlQuery(kuery), + { + range: { + ['@timestamp']: { + gte: String(startSecs), + lt: String(endSecs), + format: 'epoch_second', + }, + }, + }, + ], + }, + }, }); return response.ok({ diff --git a/x-pack/plugins/profiling_data_access/common/profiling_es_client.ts b/x-pack/plugins/profiling_data_access/common/profiling_es_client.ts index 39c95aa26d933..4fdaef437c64f 100644 --- a/x-pack/plugins/profiling_data_access/common/profiling_es_client.ts +++ b/x-pack/plugins/profiling_data_access/common/profiling_es_client.ts @@ -29,6 +29,8 @@ export interface ProfilingESClient { pervCPUWattArm64?: number; awsCostDiscountRate?: number; costPervCPUPerHour?: number; + indices?: string[]; + stacktraceIdsField?: string; }): Promise; profilingStatus(params?: { waitForResourcesCreated?: boolean }): Promise; getEsClient(): ElasticsearchClient; @@ -42,5 +44,7 @@ export interface ProfilingESClient { pervCPUWattArm64?: number; awsCostDiscountRate?: number; costPervCPUPerHour?: number; + indices?: string[]; + stacktraceIdsField?: string; }): Promise; } diff --git a/x-pack/plugins/profiling_data_access/server/services/fetch_flamechart/index.ts b/x-pack/plugins/profiling_data_access/server/services/fetch_flamechart/index.ts index 669b444e18c21..46b288c5ea361 100644 --- a/x-pack/plugins/profiling_data_access/server/services/fetch_flamechart/index.ts +++ b/x-pack/plugins/profiling_data_access/server/services/fetch_flamechart/index.ts @@ -4,7 +4,7 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ - +import { QueryDslQueryContainer } from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import { CoreRequestHandlerContext, ElasticsearchClient } from '@kbn/core/server'; import { profilingAWSCostDiscountRate, @@ -15,24 +15,28 @@ import { profilingPervCPUWattX86, } from '@kbn/observability-plugin/common'; import { percentToFactor } from '../../utils/percent_to_factor'; -import { kqlQuery } from '../../utils/query'; import { RegisterServicesParams } from '../register_services'; export interface FetchFlamechartParams { esClient: ElasticsearchClient; core: CoreRequestHandlerContext; - rangeFromMs: number; - rangeToMs: number; - kuery: string; + indices?: string[]; + stacktraceIdsField?: string; + query: QueryDslQueryContainer; + totalSeconds: number; } const targetSampleSize = 20000; // minimum number of samples to get statistically sound results export function createFetchFlamechart({ createProfilingEsClient }: RegisterServicesParams) { - return async ({ core, esClient, rangeFromMs, rangeToMs, kuery }: FetchFlamechartParams) => { - const rangeFromSecs = rangeFromMs / 1000; - const rangeToSecs = rangeToMs / 1000; - + return async ({ + core, + esClient, + indices, + stacktraceIdsField, + query, + totalSeconds, + }: FetchFlamechartParams) => { const [ co2PerKWH, datacenterPUE, @@ -50,24 +54,9 @@ export function createFetchFlamechart({ createProfilingEsClient }: RegisterServi ]); const profilingEsClient = createProfilingEsClient({ esClient }); - const totalSeconds = rangeToSecs - rangeFromSecs; + const flamegraph = await profilingEsClient.profilingFlamegraph({ - query: { - bool: { - filter: [ - ...kqlQuery(kuery), - { - range: { - ['@timestamp']: { - gte: String(rangeFromSecs), - lt: String(rangeToSecs), - format: 'epoch_second', - }, - }, - }, - ], - }, - }, + query, sampleSize: targetSampleSize, durationSeconds: totalSeconds, co2PerKWH, @@ -76,6 +65,8 @@ export function createFetchFlamechart({ createProfilingEsClient }: RegisterServi pervCPUWattArm64, awsCostDiscountRate: percentToFactor(awsCostDiscountRate), costPervCPUPerHour, + indices, + stacktraceIdsField, }); return { ...flamegraph, TotalSeconds: totalSeconds }; }; diff --git a/x-pack/plugins/profiling_data_access/server/services/functions/index.ts b/x-pack/plugins/profiling_data_access/server/services/functions/index.ts index 9a2c7121199a9..5c07dd0ca9ed1 100644 --- a/x-pack/plugins/profiling_data_access/server/services/functions/index.ts +++ b/x-pack/plugins/profiling_data_access/server/services/functions/index.ts @@ -15,6 +15,7 @@ import { } from '@kbn/observability-plugin/common'; import { CoreRequestHandlerContext, ElasticsearchClient } from '@kbn/core/server'; import { createTopNFunctions } from '@kbn/profiling-utils'; +import { QueryDslQueryContainer } from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import { percentToFactor } from '../../utils/percent_to_factor'; import { withProfilingSpan } from '../../utils/with_profiling_span'; import { RegisterServicesParams } from '../register_services'; @@ -23,11 +24,12 @@ import { searchStackTraces } from '../search_stack_traces'; export interface FetchFunctionsParams { core: CoreRequestHandlerContext; esClient: ElasticsearchClient; - rangeFromMs: number; - rangeToMs: number; - kuery: string; startIndex: number; endIndex: number; + indices?: string[]; + stacktraceIdsField?: string; + query: QueryDslQueryContainer; + totalSeconds: number; } const targetSampleSize = 20000; // minimum number of samples to get statistically sound results @@ -36,16 +38,13 @@ export function createFetchFunctions({ createProfilingEsClient }: RegisterServic return async ({ core, esClient, - rangeFromMs, - rangeToMs, - kuery, startIndex, endIndex, + indices, + stacktraceIdsField, + query, + totalSeconds, }: FetchFunctionsParams) => { - const rangeFromSecs = rangeFromMs / 1000; - const rangeToSecs = rangeToMs / 1000; - const totalSeconds = rangeToSecs - rangeFromSecs; - const [ co2PerKWH, datacenterPUE, @@ -69,9 +68,6 @@ export function createFetchFunctions({ createProfilingEsClient }: RegisterServic const { events, stackTraces, executables, stackFrames, samplingRate } = await searchStackTraces( { client: profilingEsClient, - rangeFrom: rangeFromSecs, - rangeTo: rangeToSecs, - kuery, sampleSize: targetSampleSize, durationSeconds: totalSeconds, co2PerKWH, @@ -80,6 +76,9 @@ export function createFetchFunctions({ createProfilingEsClient }: RegisterServic pervCPUWattArm64, awsCostDiscountRate: percentToFactor(awsCostDiscountRate), costPervCPUPerHour, + indices, + stacktraceIdsField, + query, showErrorFrames, } ); diff --git a/x-pack/plugins/profiling_data_access/server/services/search_stack_traces/index.ts b/x-pack/plugins/profiling_data_access/server/services/search_stack_traces/index.ts index 9881399482ce1..cf96c1242b249 100644 --- a/x-pack/plugins/profiling_data_access/server/services/search_stack_traces/index.ts +++ b/x-pack/plugins/profiling_data_access/server/services/search_stack_traces/index.ts @@ -6,15 +6,12 @@ */ import { decodeStackTraceResponse } from '@kbn/profiling-utils'; +import { QueryDslQueryContainer } from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import { ProfilingESClient } from '../../../common/profiling_es_client'; -import { kqlQuery } from '../../utils/query'; export async function searchStackTraces({ client, sampleSize, - rangeFrom, - rangeTo, - kuery, durationSeconds, co2PerKWH, datacenterPUE, @@ -22,13 +19,13 @@ export async function searchStackTraces({ pervCPUWattArm64, awsCostDiscountRate, costPervCPUPerHour, + indices, + stacktraceIdsField, + query, showErrorFrames, }: { client: ProfilingESClient; sampleSize: number; - rangeFrom: number; - rangeTo: number; - kuery: string; durationSeconds: number; co2PerKWH: number; datacenterPUE: number; @@ -36,26 +33,13 @@ export async function searchStackTraces({ pervCPUWattArm64: number; awsCostDiscountRate: number; costPervCPUPerHour: number; + indices?: string[]; + stacktraceIdsField?: string; + query: QueryDslQueryContainer; showErrorFrames: boolean; }) { const response = await client.profilingStacktraces({ - query: { - bool: { - filter: [ - ...kqlQuery(kuery), - { - range: { - ['@timestamp']: { - gte: String(rangeFrom), - lt: String(rangeTo), - format: 'epoch_second', - boost: 1.0, - }, - }, - }, - ], - }, - }, + query, sampleSize, durationSeconds, co2PerKWH, @@ -64,6 +48,8 @@ export async function searchStackTraces({ pervCPUWattArm64, awsCostDiscountRate, costPervCPUPerHour, + indices, + stacktraceIdsField, }); return decodeStackTraceResponse(response, showErrorFrames); diff --git a/x-pack/plugins/profiling_data_access/server/utils/create_profiling_es_client.ts b/x-pack/plugins/profiling_data_access/server/utils/create_profiling_es_client.ts index 4e293031aa327..a06568418771a 100644 --- a/x-pack/plugins/profiling_data_access/server/utils/create_profiling_es_client.ts +++ b/x-pack/plugins/profiling_data_access/server/utils/create_profiling_es_client.ts @@ -49,6 +49,8 @@ export function createProfilingEsClient({ costPervCPUPerHour, pervCPUWattArm64, pervCPUWattX86, + indices, + stacktraceIdsField, }) { const controller = new AbortController(); const promise = withProfilingSpan('_profiling/stacktraces', () => { @@ -66,6 +68,8 @@ export function createProfilingEsClient({ datacenter_pue: datacenterPUE, aws_cost_factor: awsCostDiscountRate, cost_per_core_hour: costPervCPUPerHour, + indices, + stacktrace_ids_field: stacktraceIdsField, }, }, { @@ -110,6 +114,8 @@ export function createProfilingEsClient({ costPervCPUPerHour, pervCPUWattArm64, pervCPUWattX86, + indices, + stacktraceIdsField, }) { const controller = new AbortController(); @@ -128,6 +134,8 @@ export function createProfilingEsClient({ datacenter_pue: datacenterPUE, aws_cost_factor: awsCostDiscountRate, cost_per_core_hour: costPervCPUPerHour, + indices, + stacktrace_ids_field: stacktraceIdsField, }, }, { From 78f61714ce2de4569c8fdb78297a7b0cc4d5be6c Mon Sep 17 00:00:00 2001 From: Artem Shelkovnikov Date: Tue, 13 Feb 2024 12:17:12 +0100 Subject: [PATCH 53/83] Increase idle timeout for connectors to 5 minutes (#175681) ## Summary Update the logic to mark sync jobs as "idle": Before any job (Crawler/Connector) would be marked as "idle" after 1 minute. This amount is too low, and we want to increase it to 5 minutes. See similar change in connectors: https://github.com/elastic/connectors/pull/2090 What it means in practice is that the summary of "idle" jobs will account crawler jobs as idle after 1 minute of no progress (no change) and connector sync jobs as idle after 5 minutes. In practice it affects only this screen (see `Idle syncs`): image Because timeouts are different, I had to modify the query that populates the screen for all connectors + crawler to reflect that timeout is different depending on service type. ### 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) --------- Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../components/connectors/connector_stats.tsx | 32 ++++++++---- .../server/lib/stats/get_sync_jobs.ts | 15 ++++-- .../server/utils/get_sync_jobs_queries.ts | 51 +------------------ 3 files changed, 35 insertions(+), 63 deletions(-) diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/connectors/connector_stats.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/connectors/connector_stats.tsx index 4dfba24c13500..9fec3349d5831 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/connectors/connector_stats.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/connectors/connector_stats.tsx @@ -123,15 +123,29 @@ export const ConnectorStats: React.FC = ({ isCrawler }) => - {i18n.translate('xpack.enterpriseSearch.connectorStats.idleSyncsOrphanedSyncsLabel', { - defaultMessage: - '{idleCount} Idle syncs / {orphanedCount} Orphaned syncs / {errorCount} Sync errors', - values: { - errorCount: data?.errors || 0, - idleCount: data?.idle, - orphanedCount: data?.orphaned_jobs, - }, - })} + {isCrawler + ? i18n.translate( + 'xpack.enterpriseSearch.connectorStats.crawlerSyncsOrphanedSyncsLabel', + { + defaultMessage: '{orphanedCount} Orphaned syncs / {errorCount} Sync errors', + values: { + errorCount: data?.errors || 0, + orphanedCount: data?.orphaned_jobs, + }, + } + ) + : i18n.translate( + 'xpack.enterpriseSearch.connectorStats.connectorSyncsOrphanedSyncsLabel', + { + defaultMessage: + '{idleCount} Idle syncs / {orphanedCount} Orphaned syncs / {errorCount} Sync errors', + values: { + errorCount: data?.errors || 0, + idleCount: data?.idle, + orphanedCount: data?.orphaned_jobs, + }, + } + )} diff --git a/x-pack/plugins/enterprise_search/server/lib/stats/get_sync_jobs.ts b/x-pack/plugins/enterprise_search/server/lib/stats/get_sync_jobs.ts index 2e24a95d12424..ffa8d7ab1892f 100644 --- a/x-pack/plugins/enterprise_search/server/lib/stats/get_sync_jobs.ts +++ b/x-pack/plugins/enterprise_search/server/lib/stats/get_sync_jobs.ts @@ -42,10 +42,15 @@ export const fetchSyncJobsStats = async ( query: getInProgressJobsCountQuery(isCrawler), }); - const idleJobsCountResponse = await client.asCurrentUser.count({ - index: CONNECTORS_JOBS_INDEX, - query: getIdleJobsCountQuery(isCrawler), - }); + // Idle syncs don't make sense for Crawler, because it does not have concept of "Idle" syncs at all. + // We tried tracking idle syncs in a way similar to connectors, but it results in all crawler jobs + // marked as idle. + const idleJobsCountResponse = isCrawler + ? undefined + : await client.asCurrentUser.count({ + index: CONNECTORS_JOBS_INDEX, + query: getIdleJobsCountQuery(), + }); const errorResponse = await client.asCurrentUser.count({ index: CONNECTORS_INDEX, @@ -65,7 +70,7 @@ export const fetchSyncJobsStats = async ( const response = { connected: connectedResponse.count, errors: errorResponse.count, - idle: idleJobsCountResponse.count, + idle: idleJobsCountResponse?.count || 0, in_progress: inProgressJobsCountResponse.count, incomplete: incompleteResponse.count, orphaned_jobs: orphanedJobsCountResponse.count, diff --git a/x-pack/plugins/enterprise_search/server/utils/get_sync_jobs_queries.ts b/x-pack/plugins/enterprise_search/server/utils/get_sync_jobs_queries.ts index 9dcdc5b44192c..5fe2e9b17eff7 100644 --- a/x-pack/plugins/enterprise_search/server/utils/get_sync_jobs_queries.ts +++ b/x-pack/plugins/enterprise_search/server/utils/get_sync_jobs_queries.ts @@ -126,54 +126,7 @@ export const getInProgressJobsCountQuery = (isCrawler?: boolean) => { }; }; -export const getIdleJobsCountQuery = (isCrawler?: boolean) => { - if (isCrawler === undefined) { - return { - bool: { - filter: [ - { - term: { - status: SyncStatus.IN_PROGRESS, - }, - }, - { - range: { - last_seen: { - lt: moment().subtract(1, 'minute').toISOString(), - }, - }, - }, - ], - }, - }; - } - - if (isCrawler) { - return { - bool: { - filter: [ - { - term: { - status: SyncStatus.IN_PROGRESS, - }, - }, - { - term: { - 'connector.service_type': CRAWLER_SERVICE_TYPE, - }, - }, - { - range: { - last_seen: { - lt: moment().subtract(1, 'minute').toISOString(), - }, - }, - }, - ], - }, - }; - } - +export const getIdleJobsCountQuery = () => { return { bool: { filter: [ @@ -194,7 +147,7 @@ export const getIdleJobsCountQuery = (isCrawler?: boolean) => { { range: { last_seen: { - lt: moment().subtract(1, 'minute').toISOString(), + lt: moment().subtract(5, 'minute').toISOString(), }, }, }, From a2d61ed0b103a96769c8e7ff8bab5dffd20e0183 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gerg=C5=91=20=C3=81brah=C3=A1m?= Date: Tue, 13 Feb 2024 12:25:33 +0100 Subject: [PATCH 54/83] [EDR Workflows][Fleet] Search and display policy name for uninstall tokens (#176626) ## Summary In Fleet / Uninstall Tokens tab: - user can see the name of the related Agent Policy in a new column, if the policy still exists, image - otherwise an info tooltip indicating why the name is missing. test: `This token's related Agent policy has already been deleted, so the policy name is unavailable.` image - Policy name (and the tooltip if missing) is indicated in Uninstall Command flyout, as well. imageimage - User can search for Uninstall Tokens by policy ID and policy name as well: the search is a **case sensitive partial match**, excluding any special character (note: the text on the top of each screenshot has been updated, see first screenshot) ![image](https://github.com/elastic/kibana/assets/39014407/a5461186-a063-4dc3-8f50-3a6fd0af626d) ![image](https://github.com/elastic/kibana/assets/39014407/2ccaffd7-1358-4113-8e4b-a930d5e20d0f) ![image](https://github.com/elastic/kibana/assets/39014407/d6840f08-ac4f-4d72-b827-abbff25c146a) ![image](https://github.com/elastic/kibana/assets/39014407/c18f4437-1fc9-4c4e-b846-21b68651ebb9) - a hint is added to indicate that a deleted agent policy's name is unknown test: `If an Agent policy is deleted, its policy name is also deleted. Use the policy ID to search for uninstall tokens related to deleted Agent policies.` image ### 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 - [ ] [Flaky Test Runner](https://ci-stats.kibana.dev/trigger_flaky_test_runner/1) was used on any tests changed --------- Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../common/types/models/uninstall_token.ts | 1 + .../common/types/rest_spec/uninstall_token.ts | 1 + .../fleet/cypress/e2e/uninstall_token.cy.ts | 54 +++- .../uninstall_token_list_page/index.test.tsx | 41 ++- .../uninstall_token_list_page/index.tsx | 63 ++-- .../uninstall_token_list_page/translations.ts | 18 +- .../empty_policy_name_hint.tsx | 32 ++ .../uninstall_command_flyout.test.tsx | 26 +- .../uninstall_command_flyout.tsx | 28 +- .../routes/uninstall_token/handlers.test.ts | 22 +- .../server/routes/uninstall_token/handlers.ts | 22 +- .../uninstall_token_service/index.test.ts | 74 +++++ .../security/uninstall_token_service/index.ts | 134 ++++++-- .../server/types/rest_spec/uninstall_token.ts | 3 +- .../translations/translations/fr-FR.json | 1 - .../translations/translations/ja-JP.json | 1 - .../translations/translations/zh-CN.json | 1 - .../apis/uninstall_token/get.ts | 306 ++++++++++++++++-- x-pack/test/fleet_api_integration/helpers.ts | 17 +- 19 files changed, 730 insertions(+), 115 deletions(-) create mode 100644 x-pack/plugins/fleet/public/components/uninstall_command_flyout/empty_policy_name_hint.tsx diff --git a/x-pack/plugins/fleet/common/types/models/uninstall_token.ts b/x-pack/plugins/fleet/common/types/models/uninstall_token.ts index cc760da6ee7cc..2d8a87c6fddcc 100644 --- a/x-pack/plugins/fleet/common/types/models/uninstall_token.ts +++ b/x-pack/plugins/fleet/common/types/models/uninstall_token.ts @@ -8,6 +8,7 @@ export interface UninstallToken { id: string; policy_id: string; + policy_name: string | null; token: string; created_at: string; } diff --git a/x-pack/plugins/fleet/common/types/rest_spec/uninstall_token.ts b/x-pack/plugins/fleet/common/types/rest_spec/uninstall_token.ts index c721c742ded92..53de3ce04156a 100644 --- a/x-pack/plugins/fleet/common/types/rest_spec/uninstall_token.ts +++ b/x-pack/plugins/fleet/common/types/rest_spec/uninstall_token.ts @@ -12,6 +12,7 @@ import type { ListResult } from './common'; export interface GetUninstallTokensMetadataRequest { query: { policyId?: string; + search?: string; perPage?: number; page?: number; }; diff --git a/x-pack/plugins/fleet/cypress/e2e/uninstall_token.cy.ts b/x-pack/plugins/fleet/cypress/e2e/uninstall_token.cy.ts index a8e2b7945a973..a585d47150db3 100644 --- a/x-pack/plugins/fleet/cypress/e2e/uninstall_token.cy.ts +++ b/x-pack/plugins/fleet/cypress/e2e/uninstall_token.cy.ts @@ -16,18 +16,25 @@ import { request } from '../tasks/common'; import { login } from '../tasks/login'; describe('Uninstall token page', () => { - before(() => { - cleanupAgentPolicies(); - generatePolicies(); - }); - - after(() => { - cleanupAgentPolicies(); - }); [true, false].forEach((removePolicies) => { describe(`When ${ removePolicies ? 'removing policies' : 'not removing policies' } before checking uninstall tokens`, () => { + before(() => { + cleanupAgentPolicies(); + generatePolicies(); + + if (removePolicies) { + cleanupAgentPolicies(); + // Force page refresh after remove policies + cy.visit('app/fleet/uninstall-tokens'); + } + }); + + after(() => { + cleanupAgentPolicies(); + }); + beforeEach(() => { login(); @@ -38,12 +45,6 @@ describe('Uninstall token page', () => { .first() .then(($policyIdField) => $policyIdField[0].textContent) .as('policyIdInFirstLine'); - - if (removePolicies) { - cleanupAgentPolicies(); - // Force page refresh after remove policies - cy.visit('app/fleet/uninstall-tokens'); - } }); it('should show token by clicking on the eye button', () => { @@ -75,7 +76,12 @@ describe('Uninstall token page', () => { cy.getBySel(UNINSTALL_TOKENS.UNINSTALL_COMMAND_FLYOUT).should('exist'); cy.contains(`sudo elastic-agent uninstall --uninstall-token ${fetchedToken.token}`); - cy.contains(`Valid for the following agent policy: ${fetchedToken.policy_id}`); + + cy.contains( + `Valid for the following agent policy:${fetchedToken.policy_name || '-'} (${ + fetchedToken.policy_id + })` + ); }); }); @@ -86,6 +92,24 @@ describe('Uninstall token page', () => { cy.getBySel(UNINSTALL_TOKENS.POLICY_ID_TABLE_FIELD).should('have.length', 1); }); + + if (!removePolicies) { + it('should filter for policy name by partial match', () => { + cy.getBySel(UNINSTALL_TOKENS.POLICY_ID_TABLE_FIELD).should('have.length.at.least', 3); + + cy.getBySel(UNINSTALL_TOKENS.POLICY_ID_SEARCH_FIELD).type('Agent 200'); + + cy.getBySel(UNINSTALL_TOKENS.POLICY_ID_TABLE_FIELD).should('have.length', 1); + }); + } else { + it('should not be able to filter for policy name by partial match', () => { + cy.getBySel(UNINSTALL_TOKENS.POLICY_ID_TABLE_FIELD).should('have.length.at.least', 3); + + cy.getBySel(UNINSTALL_TOKENS.POLICY_ID_SEARCH_FIELD).type('Agent 200'); + + cy.getBySel(UNINSTALL_TOKENS.POLICY_ID_TABLE_FIELD).should('have.length', 0); + }); + } }); }); diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/uninstall_token_list_page/index.test.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/uninstall_token_list_page/index.test.tsx index 6cdb921050f19..d38259f75c97c 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/uninstall_token_list_page/index.test.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/uninstall_token_list_page/index.test.tsx @@ -89,12 +89,14 @@ describe('UninstallTokenList page', () => { const uninstallTokenMetadataFixture1: UninstallTokenMetadata = { id: 'id-1', policy_id: 'policy-id-1', + policy_name: 'Dummy Policy Name', created_at: '2023-06-19T08:47:31.457Z', }; const uninstallTokenMetadataFixture2: UninstallTokenMetadata = { id: 'id-2', policy_id: 'policy-id-2', + policy_name: null, created_at: '2023-06-20T08:47:31.457Z', }; @@ -103,16 +105,16 @@ describe('UninstallTokenList page', () => { token: '123456789', }; - const getTokensResponseFixture: MockResponseType = { + const generateGetUninstallTokensFixture = (items: UninstallTokenMetadata[]) => ({ isLoading: false, error: null, data: { - items: [uninstallTokenMetadataFixture1, uninstallTokenMetadataFixture2], - total: 2, + items, + total: items.length, page: 1, perPage: 20, }, - }; + }); const getTokenResponseFixture: MockResponseType = { error: null, @@ -121,7 +123,12 @@ describe('UninstallTokenList page', () => { }; beforeEach(() => { - useGetUninstallTokensMock.mockReturnValue(getTokensResponseFixture); + useGetUninstallTokensMock.mockReturnValue( + generateGetUninstallTokensFixture([ + uninstallTokenMetadataFixture1, + uninstallTokenMetadataFixture2, + ]) + ); }); it('should render table with token', () => { @@ -131,6 +138,26 @@ describe('UninstallTokenList page', () => { expect(renderResult.queryByText('policy-id-1')).toBeInTheDocument(); }); + it('should NOT show hint if Policy Name is found', () => { + useGetUninstallTokensMock.mockReturnValue( + generateGetUninstallTokensFixture([uninstallTokenMetadataFixture1]) + ); + const renderResult = render(); + + expect(renderResult.queryByTestId('emptyPolicyNameHint')).not.toBeInTheDocument(); + expect(renderResult.queryByText('Dummy Policy Name')).toBeInTheDocument(); + }); + + it('should show hint if Policy Name is not found', () => { + useGetUninstallTokensMock.mockReturnValue( + generateGetUninstallTokensFixture([uninstallTokenMetadataFixture2]) + ); + const renderResult = render(); + + expect(renderResult.queryByTestId('emptyPolicyNameHint')).toBeInTheDocument(); + expect(renderResult.queryByText('Dummy Policy Name')).not.toBeInTheDocument(); + }); + it('should hide token by default', () => { const renderResult = render(); @@ -168,7 +195,7 @@ describe('UninstallTokenList page', () => { expect(useGetUninstallTokenMock).toHaveBeenCalledWith(uninstallTokenFixture.id); }); - it('should filter by policyID', async () => { + it('should filter by policyID or policy name', async () => { const renderResult = render(); fireEvent.change(renderResult.getByTestId('uninstallTokensPolicyIdSearchInput'), { @@ -178,7 +205,7 @@ describe('UninstallTokenList page', () => { expect(useGetUninstallTokensMock).toHaveBeenCalledWith({ page: 1, perPage: 20, - policyId: 'searched policy id', + search: 'searched policy id', }); }); }); diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/uninstall_token_list_page/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/uninstall_token_list_page/index.tsx index 6259c54a305d4..08e0e40c2375a 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/uninstall_token_list_page/index.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/uninstall_token_list_page/index.tsx @@ -6,6 +6,8 @@ */ import type { CriteriaWithPagination, EuiBasicTableColumn } from '@elastic/eui'; +import { EuiIcon } from '@elastic/eui'; +import { EuiFlexGroup } from '@elastic/eui'; import { EuiFieldSearch } from '@elastic/eui'; import { EuiToolTip } from '@elastic/eui'; import { EuiButtonIcon } from '@elastic/eui'; @@ -15,6 +17,8 @@ import React, { useCallback, useMemo, useState } from 'react'; import { FormattedDate, FormattedMessage } from '@kbn/i18n-react'; import type { SendRequestResponse } from '@kbn/es-ui-shared-plugin/public'; +import { EmptyPolicyNameHint } from '../../../../../components/uninstall_command_flyout/empty_policy_name_hint'; + import { ApiKeyField } from '../../../../../components/api_key_field'; import type { UninstallTokenMetadata } from '../../../../../../common/types/models/uninstall_token'; import { @@ -31,18 +35,15 @@ import { CREATED_AT_TITLE, VIEW_UNINSTALL_COMMAND_LABEL, POLICY_ID_TITLE, - SEARCH_BY_POLICY_ID_PLACEHOLDER, + SEARCH_BY_POLICY_ID_OR_NAME_PLACEHOLDER, TOKEN_TITLE, + POLICY_NAME_TITLE, + SEARCH_BY_POLICY_ID_OR_NAME_HINT, } from './translations'; -const PolicyIdField = ({ policyId }: { policyId: string }) => ( - - {policyId} +const TextField = ({ text, dataTestSubj }: { text: string; dataTestSubj?: string }) => ( + + {text} ); @@ -74,7 +75,7 @@ const NoItemsMessage = ({ isLoading }: { isLoading: boolean }) => export const UninstallTokenListPage = () => { useBreadcrumbs('uninstall_tokens'); - const [policyIdSearch, setPolicyIdSearch] = useState(''); + const [policyIdOrNameSearch, setPolicyIdOrNameSearch] = useState(''); const [tokenIdForFlyout, setTokenIdForFlyout] = useState(null); const { pagination, setPagination, pageSizeOptions } = usePagination(); @@ -82,7 +83,7 @@ export const UninstallTokenListPage = () => { const { isLoading, data } = useGetUninstallTokens({ perPage: pagination.pageSize, page: pagination.currentPage, - policyId: policyIdSearch, + search: policyIdOrNameSearch, }); const tokens = data?.items ?? []; @@ -90,10 +91,23 @@ export const UninstallTokenListPage = () => { const columns: Array> = useMemo( () => [ + { + field: 'policy_name', + name: POLICY_NAME_TITLE, + render: (policyName: string | null) => { + if (policyName === null) { + return ; + } else { + return ; + } + }, + }, { field: 'policy_id', name: POLICY_ID_TITLE, - render: (policyId: string) => , + render: (policyId: string) => ( + + ), }, { field: 'created_at', @@ -145,7 +159,7 @@ export const UninstallTokenListPage = () => { const handleSearch = useCallback( (searchString: string): void => { - setPolicyIdSearch(searchString); + setPolicyIdOrNameSearch(searchString); setPagination((prevPagination) => ({ ...prevPagination, currentPage: 1 })); }, [setPagination] @@ -164,19 +178,26 @@ export const UninstallTokenListPage = () => { - + + + + + + + diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/uninstall_token_list_page/translations.ts b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/uninstall_token_list_page/translations.ts index 7658e03193d1d..6e951008d6316 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/uninstall_token_list_page/translations.ts +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/uninstall_token_list_page/translations.ts @@ -11,6 +11,10 @@ export const POLICY_ID_TITLE = i18n.translate('xpack.fleet.uninstallTokenList.po defaultMessage: 'Policy ID', }); +export const POLICY_NAME_TITLE = i18n.translate('xpack.fleet.uninstallTokenList.policyNameTitle', { + defaultMessage: 'Policy name', +}); + export const CREATED_AT_TITLE = i18n.translate('xpack.fleet.uninstallTokenList.createdAtTitle', { defaultMessage: 'Created at', }); @@ -27,7 +31,15 @@ export const VIEW_UNINSTALL_COMMAND_LABEL = i18n.translate( { defaultMessage: 'View uninstall command' } ); -export const SEARCH_BY_POLICY_ID_PLACEHOLDER = i18n.translate( - 'xpack.fleet.uninstallTokenList.searchByPolicyPlaceholder', - { defaultMessage: 'Search by policy ID' } +export const SEARCH_BY_POLICY_ID_OR_NAME_PLACEHOLDER = i18n.translate( + 'xpack.fleet.uninstallTokenList.searchByPolicyIdOrNamePlaceholder', + { defaultMessage: 'Search by policy ID or policy name' } +); + +export const SEARCH_BY_POLICY_ID_OR_NAME_HINT = i18n.translate( + 'xpack.fleet.uninstallTokenList.searchByPolicyIdOrNameHint', + { + defaultMessage: + 'If an Agent policy is deleted, its policy name is also deleted. Use the policy ID to search for uninstall tokens related to deleted Agent policies.', + } ); diff --git a/x-pack/plugins/fleet/public/components/uninstall_command_flyout/empty_policy_name_hint.tsx b/x-pack/plugins/fleet/public/components/uninstall_command_flyout/empty_policy_name_hint.tsx new file mode 100644 index 0000000000000..f61a3392d93a3 --- /dev/null +++ b/x-pack/plugins/fleet/public/components/uninstall_command_flyout/empty_policy_name_hint.tsx @@ -0,0 +1,32 @@ +/* + * 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 { EuiIcon, EuiToolTip } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import React from 'react'; + +export const EMPTY_POLICY_NAME_HINT = i18n.translate( + 'xpack.fleet.uninstallTokenList.emptyPolicyNameHint', + { + defaultMessage: + "This token's related Agent policy has already been deleted, so the policy name is unavailable.", + } +); + +export const EmptyPolicyNameHint = () => ( + <> + {'- '} + + + + +); diff --git a/x-pack/plugins/fleet/public/components/uninstall_command_flyout/uninstall_command_flyout.test.tsx b/x-pack/plugins/fleet/public/components/uninstall_command_flyout/uninstall_command_flyout.test.tsx index 4456c201fd54f..cabefda61289d 100644 --- a/x-pack/plugins/fleet/public/components/uninstall_command_flyout/uninstall_command_flyout.test.tsx +++ b/x-pack/plugins/fleet/public/components/uninstall_command_flyout/uninstall_command_flyout.test.tsx @@ -46,6 +46,7 @@ describe('UninstallCommandFlyout', () => { const uninstallTokenMetadataFixture: UninstallTokenMetadata = { id: 'id-1', policy_id: 'policy_id', + policy_name: 'policy_name', created_at: '2023-06-19T08:47:31.457Z', }; @@ -168,11 +169,32 @@ describe('UninstallCommandFlyout', () => { ); }); - it('displays the selected policy id to the user', () => { + it('displays the selected policy id and policy name to the user', () => { const renderResult = render(); const policyIdHint = renderResult.getByTestId('uninstall-command-flyout-policy-id-hint'); - expect(policyIdHint.textContent).toBe('Valid for the following agent policy: policy_id'); + expect(policyIdHint.textContent).toBe( + 'Valid for the following agent policy:policy_name (policy_id)' + ); + }); + + it('displays hint if policy name is missing', () => { + const getTokenResponseFixture: MockResponseType = { + isLoading: false, + error: null, + data: { + item: { ...uninstallTokenFixture, policy_name: null }, + }, + }; + useGetUninstallTokenMock.mockReturnValue(getTokenResponseFixture); + + const renderResult = render(); + + const policyIdHint = renderResult.getByTestId('uninstall-command-flyout-policy-id-hint'); + expect(policyIdHint.textContent).toBe( + "Valid for the following agent policy:- This token's related Agent policy has already been deleted, so the policy name is unavailable. (policy_id)" + ); + expect(renderResult.getByTestId('emptyPolicyNameHint')).toBeInTheDocument(); }); }); diff --git a/x-pack/plugins/fleet/public/components/uninstall_command_flyout/uninstall_command_flyout.tsx b/x-pack/plugins/fleet/public/components/uninstall_command_flyout/uninstall_command_flyout.tsx index f858b795023f0..447a6c2dc7877 100644 --- a/x-pack/plugins/fleet/public/components/uninstall_command_flyout/uninstall_command_flyout.tsx +++ b/x-pack/plugins/fleet/public/components/uninstall_command_flyout/uninstall_command_flyout.tsx @@ -14,10 +14,12 @@ import { EuiSpacer, EuiText, EuiTitle, + useEuiTheme, } from '@elastic/eui'; import React from 'react'; import { FormattedMessage } from '@kbn/i18n-react'; import { i18n } from '@kbn/i18n'; +import { css } from '@emotion/react'; import type { RequestError } from '../../hooks'; import { useStartServices } from '../../hooks'; @@ -32,6 +34,7 @@ import { import { UninstallCommandsPerPlatform } from './uninstall_commands_per_platform'; import type { UninstallCommandTarget } from './types'; +import { EmptyPolicyNameHint } from './empty_policy_name_hint'; const UninstallAgentDescription = () => { const { docLinks } = useStartServices(); @@ -104,9 +107,11 @@ const ErrorFetchingUninstallToken = ({ error }: { error: RequestError | null }) ); const UninstallCommandsByTokenId = ({ uninstallTokenId }: { uninstallTokenId: string }) => { + const theme = useEuiTheme(); const { isLoading, error, data } = useGetUninstallToken(uninstallTokenId); const token = data?.item.token; const policyId = data?.item.policy_id; + const policyName = data?.item.policy_name; return isLoading ? ( @@ -118,12 +123,23 @@ const UninstallCommandsByTokenId = ({ uninstallTokenId }: { uninstallTokenId: st - - {' '} - {policyId} + +

+ +

+

+ {policyName ?? } ({policyId}) +

); diff --git a/x-pack/plugins/fleet/server/routes/uninstall_token/handlers.test.ts b/x-pack/plugins/fleet/server/routes/uninstall_token/handlers.test.ts index 1ed3290625141..31caa167ba59d 100644 --- a/x-pack/plugins/fleet/server/routes/uninstall_token/handlers.test.ts +++ b/x-pack/plugins/fleet/server/routes/uninstall_token/handlers.test.ts @@ -61,9 +61,24 @@ describe('uninstall token handlers', () => { describe('getUninstallTokensMetadataHandler', () => { const uninstallTokensFixture: UninstallTokenMetadata[] = [ - { id: 'id-1', policy_id: 'policy-id-1', created_at: '2023-06-15T16:46:48.274Z' }, - { id: 'id-2', policy_id: 'policy-id-2', created_at: '2023-06-15T16:46:48.274Z' }, - { id: 'id-3', policy_id: 'policy-id-3', created_at: '2023-06-15T16:46:48.274Z' }, + { + id: 'id-1', + policy_id: 'policy-id-1', + policy_name: null, + created_at: '2023-06-15T16:46:48.274Z', + }, + { + id: 'id-2', + policy_id: 'policy-id-2', + policy_name: null, + created_at: '2023-06-15T16:46:48.274Z', + }, + { + id: 'id-3', + policy_id: 'policy-id-3', + policy_name: null, + created_at: '2023-06-15T16:46:48.274Z', + }, ]; const uninstallTokensResponseFixture: GetUninstallTokensMetadataResponse = { @@ -135,6 +150,7 @@ describe('uninstall token handlers', () => { const uninstallTokenFixture: UninstallToken = { id: 'id-1', policy_id: 'policy-id-1', + policy_name: null, created_at: '2023-06-15T16:46:48.274Z', token: '123456789', }; diff --git a/x-pack/plugins/fleet/server/routes/uninstall_token/handlers.ts b/x-pack/plugins/fleet/server/routes/uninstall_token/handlers.ts index 8c0220b4a1d17..189a1f8bc80f7 100644 --- a/x-pack/plugins/fleet/server/routes/uninstall_token/handlers.ts +++ b/x-pack/plugins/fleet/server/routes/uninstall_token/handlers.ts @@ -32,6 +32,16 @@ export const getUninstallTokensMetadataHandler: FleetRequestHandler< return response.customError(UNINSTALL_TOKEN_SERVICE_UNAVAILABLE_ERROR); } + const { page = 1, perPage = 20, policyId, search } = request.query; + + if (policyId && search) { + return response.badRequest({ + body: { + message: 'Query parameters `policyId` and `search` cannot be used at the same time.', + }, + }); + } + try { const fleetContext = await context.fleet; const soClient = fleetContext.internalSoClient; @@ -44,10 +54,18 @@ export const getUninstallTokensMetadataHandler: FleetRequestHandler< const managedPolicyIds = managedPolicies.map((policy) => policy.id); - const { page = 1, perPage = 20, policyId } = request.query; + let policyIdSearchTerm: string | undefined; + let policyNameSearchTerm: string | undefined; + if (search) { + policyIdSearchTerm = search.trim(); + policyNameSearchTerm = search.trim(); + } else if (policyId) { + policyIdSearchTerm = policyId.trim(); + } const body = await uninstallTokenService.getTokenMetadata( - policyId?.trim(), + policyIdSearchTerm, + policyNameSearchTerm, page, perPage, managedPolicyIds.length > 0 ? managedPolicyIds : undefined diff --git a/x-pack/plugins/fleet/server/services/security/uninstall_token_service/index.test.ts b/x-pack/plugins/fleet/server/services/security/uninstall_token_service/index.test.ts index 5cca694d844ab..faeac40c1009b 100644 --- a/x-pack/plugins/fleet/server/services/security/uninstall_token_service/index.test.ts +++ b/x-pack/plugins/fleet/server/services/security/uninstall_token_service/index.test.ts @@ -17,6 +17,7 @@ import { errors } from '@elastic/elasticsearch'; import { UninstallTokenError } from '../../../../common/errors'; +import type { AgentPolicy } from '../../../../common'; import { SO_SEARCH_LIMIT } from '../../../../common'; import type { @@ -50,6 +51,7 @@ describe('UninstallTokenService', () => { let mockContext: MockedFleetAppContext; let mockBuckets: any[] = []; let uninstallTokenService: UninstallTokenServiceInterface; + let getAgentPoliciesByIDsMock: jest.Mock; function getDefaultSO(encrypted: boolean = true): TokenSO { return encrypted @@ -194,6 +196,9 @@ describe('UninstallTokenService', () => { .getScopedClient({} as unknown as KibanaRequest) as jest.Mocked; agentPolicyService.deployPolicies = jest.fn(); + getAgentPoliciesByIDsMock = jest.fn().mockResolvedValue([]); + agentPolicyService.getByIDs = getAgentPoliciesByIDsMock; + uninstallTokenService = new UninstallTokenService(esoClientMock); mockFind(canEncrypt); mockCreatePointInTimeFinder(canEncrypt); @@ -231,12 +236,16 @@ describe('UninstallTokenService', () => { it('can correctly get one token', async () => { const so = getDefaultSO(canEncrypt); mockCreatePointInTimeFinderAsInternalUser([so]); + getAgentPoliciesByIDsMock.mockResolvedValue([ + { id: so.attributes.policy_id, name: 'cheese' }, + ] as Array>); const token = await uninstallTokenService.getToken(so.id); const expectedItem: UninstallToken = { id: so.id, policy_id: so.attributes.policy_id, + policy_name: 'cheese', token: getToken(so, canEncrypt), created_at: so.created_at, }; @@ -250,6 +259,28 @@ describe('UninstallTokenService', () => { perPage: SO_SEARCH_LIMIT, } ); + expect(getAgentPoliciesByIDsMock).toHaveBeenCalledWith( + soClientMock, + [so.attributes.policy_id], + { ignoreMissing: true } + ); + }); + + it('sets `policy_name` to `null` if linked policy does not exist', async () => { + const so = getDefaultSO(canEncrypt); + mockCreatePointInTimeFinderAsInternalUser([so]); + + const token = await uninstallTokenService.getToken(so.id); + + const expectedItem: UninstallToken = { + id: so.id, + policy_id: so.attributes.policy_id, + policy_name: null, + token: getToken(so, canEncrypt), + created_at: so.created_at, + }; + + expect(token).toEqual(expectedItem); }); it('throws error if token is missing', async () => { @@ -277,17 +308,22 @@ describe('UninstallTokenService', () => { it('can correctly get token metadata', async () => { const so = getDefaultSO(canEncrypt); const so2 = getDefaultSO2(canEncrypt); + getAgentPoliciesByIDsMock.mockResolvedValue([ + { id: so2.attributes.policy_id, name: 'only I have a name' }, + ] as Array>); const actualItems = (await uninstallTokenService.getTokenMetadata()).items; const expectedItems: UninstallTokenMetadata[] = [ { id: so.id, policy_id: so.attributes.policy_id, + policy_name: null, created_at: so.created_at, }, { id: so2.id, policy_id: so2.attributes.policy_id, + policy_name: 'only I have a name', created_at: so2.created_at, }, ]; @@ -316,6 +352,44 @@ describe('UninstallTokenService', () => { ); }); }); + + describe('prepareSearchString', () => { + let prepareSearchString: (str: string | undefined, wildcard: string) => string; + + beforeEach(() => { + ({ prepareSearchString } = uninstallTokenService as unknown as { + prepareSearchString: typeof prepareSearchString; + }); + }); + + it('should generate search string with given wildcard', () => { + expect(prepareSearchString('input', '*')).toEqual('*input*'); + expect(prepareSearchString('another', '.*')).toEqual('.*another.*'); + }); + + it('should remove special characters', () => { + expect(prepareSearchString('_in:put', '*')).toEqual('*in*put*'); + expect(prepareSearchString('', '*')).toEqual('*input*'); + expect(prepareSearchString('inp"ut"', '*')).toEqual('*inp*ut*'); + expect(prepareSearchString('"input"', '*')).toEqual('*input*'); + }); + + it('should replace multiple special characters with only one wildcard', () => { + expect(prepareSearchString('<<<>>>>', '*')).toEqual('*inp*ut*'); + }); + + it('should keep digits, letters and dash', () => { + expect(prepareSearchString('123-ABC-XYZ-4567890', '*')).toEqual('*123-ABC-XYZ-4567890*'); + }); + + it('should return undefined if there are no useful characters', () => { + expect(prepareSearchString('<<<<""""">>>>>', '*')).toEqual(undefined); + }); + + it('should return undefined if input is undefined', () => { + expect(prepareSearchString(undefined, '*')).toEqual(undefined); + }); + }); }); describe('get hashed uninstall tokens', () => { diff --git a/x-pack/plugins/fleet/server/services/security/uninstall_token_service/index.ts b/x-pack/plugins/fleet/server/services/security/uninstall_token_service/index.ts index 3d43f280ba116..d455154fdebe6 100644 --- a/x-pack/plugins/fleet/server/services/security/uninstall_token_service/index.ts +++ b/x-pack/plugins/fleet/server/services/security/uninstall_token_service/index.ts @@ -32,6 +32,8 @@ import type { import { isResponseError } from '@kbn/es-errors'; +import type { AgentPolicySOAttributes } from '../../../types'; + import { UninstallTokenError } from '../../../../common/errors'; import type { GetUninstallTokensMetadataResponse } from '../../../../common/types/rest_spec/uninstall_token'; @@ -41,7 +43,11 @@ import type { UninstallTokenMetadata, } from '../../../../common/types/models/uninstall_token'; -import { UNINSTALL_TOKENS_SAVED_OBJECT_TYPE, SO_SEARCH_LIMIT } from '../../../constants'; +import { + UNINSTALL_TOKENS_SAVED_OBJECT_TYPE, + SO_SEARCH_LIMIT, + AGENT_POLICY_SAVED_OBJECT_TYPE, +} from '../../../constants'; import { appContextService } from '../../app_context'; import { agentPolicyService } from '../../agent_policy'; @@ -74,19 +80,23 @@ export interface UninstallTokenServiceInterface { getToken(id: string): Promise; /** - * Get uninstall token metadata, optionally filtering by partial policyID, paginated + * Get uninstall token metadata, optionally filtering for policyID and policy name, with a logical OR relation: + * every uninstall token is returned with a related agent policy which partially matches either the given policyID or the policy name. + * The result is paginated. * - * @param policyIdFilter a string for partial matching the policyId + * @param policyIdSearchTerm a string for partial matching the policyId + * @param policyNameSearchTerm a string for partial matching the policy name * @param page * @param perPage - * @param excludePolicyIds + * @param excludedPolicyIds * @returns Uninstall Tokens Metadata Response */ getTokenMetadata( - policyIdFilter?: string, + policyIdSearchTerm?: string, + policyNameSearchTerm?: string, page?: number, perPage?: number, - excludePolicyIds?: string[] + excludedPolicyIds?: string[] ): Promise; /** @@ -171,45 +181,113 @@ export class UninstallTokenService implements UninstallTokenServiceInterface { const tokenObjects = await this.getDecryptedTokenObjects({ filter }); - return tokenObjects.length === 1 ? this.convertTokenObjectToToken(tokenObjects[0]) : null; + return tokenObjects.length === 1 + ? this.convertTokenObjectToToken( + await this.getPolicyIdNameDictionary([tokenObjects[0].attributes.policy_id]), + tokenObjects[0] + ) + : null; + } + + private prepareSearchString(str: string | undefined, wildcard: string): string | undefined { + const strWithoutSpecialCharacters = str + ?.split(/[^-\da-z]+/gi) + .filter((x) => x) + .join(wildcard); + + return strWithoutSpecialCharacters + ? wildcard + strWithoutSpecialCharacters + wildcard + : undefined; + } + + private async searchPoliciesByName(policyNameSearchString: string): Promise { + const policyNameFilter = `${AGENT_POLICY_SAVED_OBJECT_TYPE}.attributes.name:${policyNameSearchString}`; + + const agentPoliciesSOs = await this.soClient.find({ + type: AGENT_POLICY_SAVED_OBJECT_TYPE, + filter: policyNameFilter, + }); + + return agentPoliciesSOs.saved_objects.map((attr) => attr.id); } public async getTokenMetadata( - policyIdFilter?: string, + policyIdSearchTerm?: string, + policyNameSearchTerm?: string, page = 1, perPage = 20, - excludePolicyIds?: string[] + excludedPolicyIds?: string[] ): Promise { - const includeFilter = policyIdFilter ? `.*${policyIdFilter}.*` : undefined; + const policyIdFilter = this.prepareSearchString(policyIdSearchTerm, '.*'); - const tokenObjects = await this.getTokenObjectsByIncludeFilter(includeFilter, excludePolicyIds); + let policyIdsFoundByName: string[] | undefined; + const policyNameSearchString = this.prepareSearchString(policyNameSearchTerm, '*'); + if (policyNameSearchString) { + policyIdsFoundByName = await this.searchPoliciesByName(policyNameSearchString); + } - const items: UninstallTokenMetadata[] = tokenObjects - .slice((page - 1) * perPage, page * perPage) - .map(({ _id, _source }) => { + let includeFilter: string | undefined; + if (policyIdFilter || policyIdsFoundByName) { + includeFilter = [ + ...(policyIdsFoundByName ? policyIdsFoundByName : []), + ...(policyIdFilter ? [policyIdFilter] : []), + ].join('|'); + } + + const tokenObjects = await this.getTokenObjectsByPolicyIdFilter( + includeFilter, + excludedPolicyIds + ); + const tokenObjectsCurrentPage = tokenObjects.slice((page - 1) * perPage, page * perPage); + const policyIds = tokenObjectsCurrentPage.map( + (tokenObject) => tokenObject._source[UNINSTALL_TOKENS_SAVED_OBJECT_TYPE].policy_id + ); + const policyIdNameDictionary = await this.getPolicyIdNameDictionary(policyIds); + + const items: UninstallTokenMetadata[] = tokenObjectsCurrentPage.map( + ({ _id, _source }) => { this.assertPolicyId(_source[UNINSTALL_TOKENS_SAVED_OBJECT_TYPE]); this.assertCreatedAt(_source.created_at); + const policyId = _source[UNINSTALL_TOKENS_SAVED_OBJECT_TYPE].policy_id; return { id: _id.replace(`${UNINSTALL_TOKENS_SAVED_OBJECT_TYPE}:`, ''), - policy_id: _source[UNINSTALL_TOKENS_SAVED_OBJECT_TYPE].policy_id, + policy_id: policyId, + policy_name: policyIdNameDictionary[policyId] ?? null, created_at: _source.created_at, }; - }); + } + ); return { items, total: tokenObjects.length, page, perPage }; } + private async getPolicyIdNameDictionary(policyIds: string[]): Promise> { + const agentPolicies = await agentPolicyService.getByIDs(this.soClient, policyIds, { + ignoreMissing: true, + }); + + return agentPolicies.reduce((dict, policy) => { + dict[policy.id] = policy.name; + return dict; + }, {} as Record); + } + private async getDecryptedTokensForPolicyIds(policyIds: string[]): Promise { const tokenObjects = await this.getDecryptedTokenObjectsForPolicyIds(policyIds); + const policyIdNameDictionary = await this.getPolicyIdNameDictionary( + tokenObjects.map((obj) => obj.attributes.policy_id) + ); - return tokenObjects.map(this.convertTokenObjectToToken); + return tokenObjects.map((tokenObject) => + this.convertTokenObjectToToken(policyIdNameDictionary, tokenObject) + ); } private async getDecryptedTokenObjectsForPolicyIds( policyIds: string[] ): Promise>> { - const tokenObjectHits = await this.getTokenObjectsByIncludeFilter(policyIds); + const tokenObjectHits = await this.getTokenObjectsByPolicyIdFilter(policyIds); if (tokenObjectHits.length === 0) { return []; @@ -280,12 +358,15 @@ export class UninstallTokenService implements UninstallTokenServiceInterface { return tokenObjects; } - private convertTokenObjectToToken = ({ - id: _id, - attributes, - created_at: createdAt, - error, - }: SavedObjectsFindResult): UninstallToken => { + private convertTokenObjectToToken = ( + policyIdNameDictionary: Record, + { + id: _id, + attributes, + created_at: createdAt, + error, + }: SavedObjectsFindResult + ): UninstallToken => { if (error) { throw new UninstallTokenError(`Error when reading Uninstall Token with id '${_id}'.`); } @@ -297,12 +378,13 @@ export class UninstallTokenService implements UninstallTokenServiceInterface { return { id: _id, policy_id: attributes.policy_id, + policy_name: policyIdNameDictionary[attributes.policy_id] ?? null, token: attributes.token || attributes.token_plain, created_at: createdAt, }; }; - private async getTokenObjectsByIncludeFilter( + private async getTokenObjectsByPolicyIdFilter( include?: AggregationsTermsInclude, exclude?: AggregationsTermsExclude ): Promise>> { @@ -397,7 +479,7 @@ export class UninstallTokenService implements UninstallTokenServiceInterface { const existingTokens = new Set(); if (!force) { - (await this.getTokenObjectsByIncludeFilter(policyIds)).forEach((tokenObject) => { + (await this.getTokenObjectsByPolicyIdFilter(policyIds)).forEach((tokenObject) => { existingTokens.add(tokenObject._source[UNINSTALL_TOKENS_SAVED_OBJECT_TYPE].policy_id); }); } diff --git a/x-pack/plugins/fleet/server/types/rest_spec/uninstall_token.ts b/x-pack/plugins/fleet/server/types/rest_spec/uninstall_token.ts index 6fed3643c1e10..924e1da2cb9e8 100644 --- a/x-pack/plugins/fleet/server/types/rest_spec/uninstall_token.ts +++ b/x-pack/plugins/fleet/server/types/rest_spec/uninstall_token.ts @@ -8,7 +8,8 @@ import { schema } from '@kbn/config-schema'; export const GetUninstallTokensMetadataRequestSchema = { query: schema.object({ - policyId: schema.maybe(schema.string()), + policyId: schema.maybe(schema.string({ maxLength: 50 })), + search: schema.maybe(schema.string({ maxLength: 50 })), perPage: schema.maybe(schema.number({ defaultValue: 20, min: 5 })), page: schema.maybe(schema.number({ defaultValue: 1, min: 1 })), }), diff --git a/x-pack/plugins/translations/translations/fr-FR.json b/x-pack/plugins/translations/translations/fr-FR.json index c74ea4bc11d0a..ab9573719e9dc 100644 --- a/x-pack/plugins/translations/translations/fr-FR.json +++ b/x-pack/plugins/translations/translations/fr-FR.json @@ -17710,7 +17710,6 @@ "xpack.fleet.uninstallTokenList.loadingTokensMessage": "Chargement des jetons désinstallés...", "xpack.fleet.uninstallTokenList.pageDescription": "Les jetons désinstallés vous permettent d’obtenir la commande de désinstallation si vous devez désinstaller l’agent/le point de terminaison sur l’hôte.", "xpack.fleet.uninstallTokenList.policyIdTitle": "ID de stratégie", - "xpack.fleet.uninstallTokenList.searchByPolicyPlaceholder": "Rechercher par ID de stratégie", "xpack.fleet.uninstallTokenList.tokenTitle": "Token", "xpack.fleet.uninstallTokenList.viewUninstallCommandLabel": "Voir la commande de désinstallation", "xpack.fleet.updateAgentTags.errorNotificationTitle": "Échec de la mise à jour des balises", diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index c30efa9c96264..d590331781da8 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -17723,7 +17723,6 @@ "xpack.fleet.uninstallTokenList.loadingTokensMessage": "アンインストールトークンを読み込み中...", "xpack.fleet.uninstallTokenList.pageDescription": "アンインストールトークンは、ホスト上のエージェント/エンドポイントをアンインストールする必要がある場合に、アンインストールコマンドを取得します。", "xpack.fleet.uninstallTokenList.policyIdTitle": "ポリシーID", - "xpack.fleet.uninstallTokenList.searchByPolicyPlaceholder": "ポリシーIDで検索", "xpack.fleet.uninstallTokenList.tokenTitle": "トークン", "xpack.fleet.uninstallTokenList.viewUninstallCommandLabel": "アンインストールコマンドを表示", "xpack.fleet.updateAgentTags.errorNotificationTitle": "タグの更新が失敗しました", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index ac1c00b14a7bc..c849a71a75e09 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -17817,7 +17817,6 @@ "xpack.fleet.uninstallTokenList.loadingTokensMessage": "正在加载卸载令牌......", "xpack.fleet.uninstallTokenList.pageDescription": "如果需要卸载主机上的代理/终端,卸载令牌允许您获取卸载命令。", "xpack.fleet.uninstallTokenList.policyIdTitle": "策略 ID", - "xpack.fleet.uninstallTokenList.searchByPolicyPlaceholder": "按策略 ID 搜索", "xpack.fleet.uninstallTokenList.tokenTitle": "令牌", "xpack.fleet.uninstallTokenList.viewUninstallCommandLabel": "查看卸载命令", "xpack.fleet.updateAgentTags.errorNotificationTitle": "标签更新失败", diff --git a/x-pack/test/fleet_api_integration/apis/uninstall_token/get.ts b/x-pack/test/fleet_api_integration/apis/uninstall_token/get.ts index 3493d5092f945..e1686b8236ef2 100644 --- a/x-pack/test/fleet_api_integration/apis/uninstall_token/get.ts +++ b/x-pack/test/fleet_api_integration/apis/uninstall_token/get.ts @@ -10,7 +10,11 @@ import { GetUninstallTokensMetadataResponse, GetUninstallTokenResponse, } from '@kbn/fleet-plugin/common/types/rest_spec/uninstall_token'; -import { uninstallTokensRouteService } from '@kbn/fleet-plugin/common/services'; +import { + agentPolicyRouteService, + uninstallTokensRouteService, +} from '@kbn/fleet-plugin/common/services'; +import { AgentPolicy } from '@kbn/fleet-plugin/common'; import { testUsers } from '../test_users'; import { FtrProviderContext } from '../../../api_integration/ftr_provider_context'; import { addUninstallTokenToPolicy, generateNPolicies } from '../../helpers'; @@ -32,10 +36,13 @@ export default function (providerContext: FtrProviderContext) { describe('GET uninstall_tokens', () => { describe('pagination', () => { - let generatedPolicyIds: Set; + let generatedPolicies: Map; before(async () => { - generatedPolicyIds = new Set(await generateNPolicies(supertest, 20)); + const generatedPoliciesArray = await generateNPolicies(supertest, 20); + + generatedPolicies = new Map(); + generatedPoliciesArray.forEach((policy) => generatedPolicies.set(policy.id, policy)); }); after(async () => { @@ -48,23 +55,27 @@ export default function (providerContext: FtrProviderContext) { .expect(200); const body: GetUninstallTokensMetadataResponse = response.body; - expect(body.total).to.equal(generatedPolicyIds.size); + expect(body.total).to.equal(generatedPolicies.size); expect(body.page).to.equal(1); expect(body.perPage).to.equal(20); - expect(body.items.length).to.equal(generatedPolicyIds.size); + expect(body.items.length).to.equal(generatedPolicies.size); body.items.forEach(({ policy_id: policyId }) => - expect(generatedPolicyIds.has(policyId)).to.be(true) + expect(generatedPolicies.has(policyId)).to.be(true) ); }); - it('should return token metadata with creation date and id', async () => { + it('should return token metadata with creation date, id, and correct policy name', async () => { const response = await supertest .get(uninstallTokensRouteService.getListPath()) .expect(200); const body: GetUninstallTokensMetadataResponse = response.body; expect(body.items[0]).to.have.property('policy_id'); + expect(body.items[0]).to.have.property('policy_name'); + expect(body.items[0].policy_name).to.equal( + generatedPolicies.get(body.items[0].policy_id)?.name + ); expect(body.items[0]).to.have.property('created_at'); expect(body.items[0]).to.have.property('id'); @@ -75,13 +86,14 @@ export default function (providerContext: FtrProviderContext) { }); it('should return default perPage number of token metadata if total is above default perPage', async () => { - generatedPolicyIds.add((await generateNPolicies(supertest, 1))[0]); + const additionalPolicy = (await generateNPolicies(supertest, 1))[0]; + generatedPolicies.set(additionalPolicy.id, additionalPolicy); const response1 = await supertest .get(uninstallTokensRouteService.getListPath()) .expect(200); const body1: GetUninstallTokensMetadataResponse = response1.body; - expect(body1.total).to.equal(generatedPolicyIds.size); + expect(body1.total).to.equal(generatedPolicies.size); expect(body1.page).to.equal(1); expect(body1.perPage).to.equal(20); expect(body1.items.length).to.equal(20); @@ -91,7 +103,7 @@ export default function (providerContext: FtrProviderContext) { .query({ page: 2 }) .expect(200); const body2: GetUninstallTokensMetadataResponse = response2.body; - expect(body2.total).to.equal(generatedPolicyIds.size); + expect(body2.total).to.equal(generatedPolicies.size); expect(body2.page).to.equal(2); expect(body2.perPage).to.equal(20); expect(body2.items.length).to.equal(1); @@ -110,7 +122,7 @@ export default function (providerContext: FtrProviderContext) { .expect(200); const body: GetUninstallTokensMetadataResponse = response.body; - expect(body.total).to.equal(generatedPolicyIds.size); + expect(body.total).to.equal(generatedPolicies.size); expect(body.perPage).to.equal(8); expect(body.page).to.equal(i); @@ -118,9 +130,9 @@ export default function (providerContext: FtrProviderContext) { receivedPolicyIds.push(...receivedIds); } - expect(receivedPolicyIds.length).to.equal(generatedPolicyIds.size); + expect(receivedPolicyIds.length).to.equal(generatedPolicies.size); receivedPolicyIds.forEach((policyId) => - expect(generatedPolicyIds.has(policyId)).to.be(true) + expect(generatedPolicies.has(policyId)).to.be(true) ); }); @@ -151,15 +163,15 @@ export default function (providerContext: FtrProviderContext) { }); describe('when there are multiple tokens for a policy', () => { - let generatedPolicyIdsArray: string[]; + let generatedPolicies: AgentPolicy[]; let timestampBeforeAddingNewTokens: number; before(async () => { - generatedPolicyIdsArray = await generateNPolicies(supertest, 20); + generatedPolicies = await generateNPolicies(supertest, 20); timestampBeforeAddingNewTokens = Date.now(); - const savingAdditionalTokensPromises = generatedPolicyIdsArray.map((id) => + const savingAdditionalTokensPromises = generatedPolicies.map(({ id }) => addUninstallTokenToPolicy(kibanaServer, id, `${id} latest token`) ); @@ -176,7 +188,7 @@ export default function (providerContext: FtrProviderContext) { .expect(200); const body: GetUninstallTokensMetadataResponse = response.body; - expect(body.total).to.equal(generatedPolicyIdsArray.length); + expect(body.total).to.equal(generatedPolicies.length); expect(body.page).to.equal(1); expect(body.perPage).to.equal(20); @@ -187,11 +199,44 @@ export default function (providerContext: FtrProviderContext) { }); }); + describe('when there are managed policies', () => { + let notManagedPolicies: AgentPolicy[]; + let managedPolicies: AgentPolicy[]; + + before(async () => { + notManagedPolicies = await generateNPolicies(supertest, 3); + managedPolicies = await generateNPolicies(supertest, 4, { is_managed: true }); + }); + + after(async () => { + await kibanaServer.savedObjects.cleanStandardList(); + }); + + it('should not return token metadata for managed policies', async () => { + const response = await supertest + .get(uninstallTokensRouteService.getListPath()) + .expect(200); + + const body: GetUninstallTokensMetadataResponse = response.body; + expect(body.total).to.equal(notManagedPolicies.length); + + const returnedPolicyIds = new Set(); + body.items.forEach((uninstallToken) => returnedPolicyIds.add(uninstallToken.policy_id)); + + notManagedPolicies.forEach((notManagedPolicy) => { + expect(returnedPolicyIds.has(notManagedPolicy.id)).to.be(true); + }); + managedPolicies.forEach((managedPolicy) => { + expect(returnedPolicyIds.has(managedPolicy.id)).to.be(false); + }); + }); + }); + describe('when `policyId` query param is used', () => { - let generatedPolicyIdsArray: string[]; + let generatedPolicyArray: AgentPolicy[]; before(async () => { - generatedPolicyIdsArray = await generateNPolicies(supertest, 5); + generatedPolicyArray = await generateNPolicies(supertest, 5); }); after(async () => { @@ -199,7 +244,7 @@ export default function (providerContext: FtrProviderContext) { }); it('should return token metadata for full policyID if found', async () => { - const selectedPolicyId = generatedPolicyIdsArray[3]; + const selectedPolicyId = generatedPolicyArray[3].id; const response = await supertest .get(uninstallTokensRouteService.getListPath()) @@ -216,7 +261,7 @@ export default function (providerContext: FtrProviderContext) { }); it('should return token metadata for partial policyID if found', async () => { - const selectedPolicyId = generatedPolicyIdsArray[2]; + const selectedPolicyId = generatedPolicyArray[2].id; const response = await supertest .get(uninstallTokensRouteService.getListPath()) @@ -232,6 +277,23 @@ export default function (providerContext: FtrProviderContext) { expect(body.items[0].policy_id).to.equal(selectedPolicyId); }); + it('should not return token metadata by policy name', async () => { + const selectedPolicyName = generatedPolicyArray[2].name; + + const response = await supertest + .get(uninstallTokensRouteService.getListPath()) + .query({ + policyId: selectedPolicyName, + }) + .expect(200); + + const body: GetUninstallTokensMetadataResponse = response.body; + expect(body.total).to.equal(0); + expect(body.page).to.equal(1); + expect(body.perPage).to.equal(20); + expect(body.items).to.eql([]); + }); + it('should return nothing if policy is not found', async () => { const response = await supertest .get(uninstallTokensRouteService.getListPath()) @@ -248,6 +310,207 @@ export default function (providerContext: FtrProviderContext) { }); }); + describe('when `search` query param is used', () => { + let generatedManagedPolicyArray: AgentPolicy[]; + let generatedPolicyArray: AgentPolicy[]; + + before(async () => { + generatedPolicyArray = await generateNPolicies(supertest, 8); + generatedPolicyArray.push( + ...(await generateNPolicies(supertest, 1, { name: 'Special: Policy' })) + ); + generatedPolicyArray.push( + ...(await generateNPolicies(supertest, 1, { name: 'Special { + await kibanaServer.savedObjects.cleanStandardList(); + }); + + it('should return token metadata for full policyID', async () => { + const selectedPolicyId = generatedPolicyArray[3].id; + + const response = await supertest + .get(uninstallTokensRouteService.getListPath()) + .query({ + search: selectedPolicyId, + }) + .expect(200); + + const body: GetUninstallTokensMetadataResponse = response.body; + expect(body.total).to.equal(1); + expect(body.page).to.equal(1); + expect(body.perPage).to.equal(20); + expect(body.items[0].policy_id).to.equal(selectedPolicyId); + }); + + it('should return token metadata for partial policyID', async () => { + const selectedPolicyId = generatedPolicyArray[2].id; + + const response = await supertest + .get(uninstallTokensRouteService.getListPath()) + .query({ + search: selectedPolicyId.slice(4, 11), + }) + .expect(200); + + const body: GetUninstallTokensMetadataResponse = response.body; + expect(body.total).to.equal(1); + expect(body.page).to.equal(1); + expect(body.perPage).to.equal(20); + expect(body.items[0].policy_id).to.equal(selectedPolicyId); + }); + + it('should return token metadata for policyID even if policy is deleted', async () => { + const deletedPolicy = (await generateNPolicies(supertest, 1))[0]; + await supertest + .post(agentPolicyRouteService.getDeletePath()) + .set('kbn-xsrf', 'xxxx') + .send({ agentPolicyId: deletedPolicy.id }) + .expect(200); + + const response = await supertest + .get(uninstallTokensRouteService.getListPath()) + .query({ + search: deletedPolicy.id, + }) + .expect(200); + + const body: GetUninstallTokensMetadataResponse = response.body; + expect(body.total).to.equal(1); + expect(body.page).to.equal(1); + expect(body.perPage).to.equal(20); + expect(body.items[0].policy_id).to.equal(deletedPolicy.id); + expect(body.items[0].policy_name).to.equal(null); + }); + + it('should return token metadata for full policy name', async () => { + const selectedPolicy = generatedPolicyArray[6]; + + const response = await supertest + .get(uninstallTokensRouteService.getListPath()) + .query({ + search: selectedPolicy.name, + }) + .expect(200); + + const body: GetUninstallTokensMetadataResponse = response.body; + expect(body.total).to.equal(1); + expect(body.page).to.equal(1); + expect(body.perPage).to.equal(20); + expect(body.items[0].policy_id).to.equal(selectedPolicy.id); + }); + + it('should return token metadata for partial policy name', async () => { + const selectedPolicy = generatedPolicyArray[1]; + + const response = await supertest + .get(uninstallTokensRouteService.getListPath()) + .query({ + search: selectedPolicy.name.slice(4), + }) + .expect(200); + + const body: GetUninstallTokensMetadataResponse = response.body; + expect(body.total).to.equal(1); + expect(body.page).to.equal(1); + expect(body.perPage).to.equal(20); + expect(body.items[0].policy_id).to.equal(selectedPolicy.id); + }); + + it('should return nothing if policy is not found', async () => { + const response = await supertest + .get(uninstallTokensRouteService.getListPath()) + .query({ + search: 'not-existing-policy-id-or-name', + }) + .expect(200); + + const body: GetUninstallTokensMetadataResponse = response.body; + expect(body.total).to.equal(0); + expect(body.page).to.equal(1); + expect(body.perPage).to.equal(20); + expect(body.items).to.eql([]); + }); + + it('should return nothing if searched for managed policy id', async () => { + const response = await supertest + .get(uninstallTokensRouteService.getListPath()) + .query({ + search: generatedManagedPolicyArray[0].id, + }) + .expect(200); + + const body: GetUninstallTokensMetadataResponse = response.body; + expect(body.total).to.equal(0); + expect(body.page).to.equal(1); + expect(body.perPage).to.equal(20); + expect(body.items).to.eql([]); + }); + + it('should return nothing if searched for managed policy name', async () => { + const response = await supertest + .get(uninstallTokensRouteService.getListPath()) + .query({ + search: generatedManagedPolicyArray[0].name, + }) + .expect(200); + + const body: GetUninstallTokensMetadataResponse = response.body; + expect(body.total).to.equal(0); + expect(body.page).to.equal(1); + expect(body.perPage).to.equal(20); + expect(body.items).to.eql([]); + }); + + it('should return nothing if searched for managed policy name', async () => { + const response = await supertest + .get(uninstallTokensRouteService.getListPath()) + .query({ + search: generatedManagedPolicyArray[0].name, + }) + .expect(200); + + const body: GetUninstallTokensMetadataResponse = response.body; + expect(body.total).to.equal(0); + expect(body.page).to.equal(1); + expect(body.perPage).to.equal(20); + expect(body.items).to.eql([]); + }); + + it('should remove special characters', async () => { + const response = await supertest + .get(uninstallTokensRouteService.getListPath()) + .query({ + search: 'Special Policy', + }) + .expect(200); + + const body: GetUninstallTokensMetadataResponse = response.body; + expect(body.total).to.equal(2); + const returnedPolicyNames = body.items.map((item) => item.policy_name); + expect(returnedPolicyNames).to.contain('Special: Policy'); + expect(returnedPolicyNames).to.contain('Special { + const response = await supertest + .get(uninstallTokensRouteService.getListPath()) + .query({ + policyId: 'policy id', + search: 'policy name', + }) + .expect(400); + + const body = response.body; + expect(body.message).to.equal( + 'Query parameters `policyId` and `search` cannot be used at the same time.' + ); + }); + }); + describe('authorization', () => { it('should return 200 if the user has FLEET ALL (and INTEGRATIONS READ) privilege', async () => { const { username, password } = testUsers.fleet_all_int_read; @@ -293,6 +556,7 @@ export default function (providerContext: FtrProviderContext) { expect(body.item.id).to.equal(generatedUninstallTokenId); expect(body.item.policy_id).to.equal('the policy id'); + expect(body.item.policy_name).to.equal(null); expect(body.item.token).to.equal('the token'); expect(body.item).to.have.property('created_at'); }); diff --git a/x-pack/test/fleet_api_integration/helpers.ts b/x-pack/test/fleet_api_integration/helpers.ts index 0121ab32890d0..074ac2656e7f4 100644 --- a/x-pack/test/fleet_api_integration/helpers.ts +++ b/x-pack/test/fleet_api_integration/helpers.ts @@ -8,7 +8,11 @@ import * as uuid from 'uuid'; import { ToolingLog } from '@kbn/tooling-log'; import { agentPolicyRouteService } from '@kbn/fleet-plugin/common/services'; -import { CreateAgentPolicyResponse } from '@kbn/fleet-plugin/common'; +import { + AgentPolicy, + CreateAgentPolicyRequest, + CreateAgentPolicyResponse, +} from '@kbn/fleet-plugin/common'; import { KbnClient } from '@kbn/test'; import { UNINSTALL_TOKENS_SAVED_OBJECT_TYPE } from '@kbn/fleet-plugin/common'; import { FtrProviderContext } from '../api_integration/ftr_provider_context'; @@ -120,7 +124,11 @@ export function setPrereleaseSetting(supertest: any) { }); } -export const generateNPolicies = async (supertest: any, number: number) => { +export const generateNPolicies = async ( + supertest: any, + number: number, + overwrite?: Partial +): Promise => { const promises = []; for (let i = 0; i < number; i++) { @@ -128,15 +136,14 @@ export const generateNPolicies = async (supertest: any, number: number) => { supertest .post(agentPolicyRouteService.getCreatePath()) .set('kbn-xsrf', 'xxxx') - .send({ name: `Agent Policy ${uuid.v4()}`, namespace: 'default' }) + .send({ name: `Agent Policy ${uuid.v4()}`, namespace: 'default', ...overwrite }) .expect(200) ); } const responses = await Promise.all(promises); - const policyIds = responses.map(({ body }) => (body as CreateAgentPolicyResponse).item.id); - return policyIds; + return responses.map(({ body }) => (body as CreateAgentPolicyResponse).item); }; export const addUninstallTokenToPolicy = async ( From 67611d6c946d49b7505379725c9bc2bc3712e21f Mon Sep 17 00:00:00 2001 From: Elastic Machine Date: Tue, 13 Feb 2024 22:11:02 +1030 Subject: [PATCH 55/83] [main] Sync bundled packages with Package Storage (#176769) Automated by https://buildkite.com/elastic/package-storage-infra-kibana-discover-release-branches/builds/345 Co-authored-by: Silvia Mitter --- fleet_packages.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fleet_packages.json b/fleet_packages.json index bfde442c20d22..2e5e5cd6be8a4 100644 --- a/fleet_packages.json +++ b/fleet_packages.json @@ -24,7 +24,7 @@ [ { "name": "apm", - "version": "8.13.0-preview-1705349440", + "version": "8.13.0-preview-1705349441", "forceAlignStackVersion": true, "allowSyncToPrerelease": true }, From 873ae316877f987eb4d90ef699b5f8438e46720a Mon Sep 17 00:00:00 2001 From: Janki Salvi <117571355+js-jankisalvi@users.noreply.github.com> Date: Tue, 13 Feb 2024 12:52:03 +0100 Subject: [PATCH 56/83] [Cases] Fix property actions flaky tests (#176709) ## Summary Fixes #175314 Fixes #175313 Fixes #175312 Fixes #175311 Fixes #175310 Fixes #174667 Fixes #174384 ### 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 --- .../alert_property_actions.test.tsx | 21 +----- .../property_actions.test.tsx | 28 ++++---- ...ered_attachments_property_actions.test.tsx | 11 +-- .../user_comment_property_actions.test.tsx | 67 +++++++++---------- 4 files changed, 50 insertions(+), 77 deletions(-) diff --git a/x-pack/plugins/cases/public/components/user_actions/property_actions/alert_property_actions.test.tsx b/x-pack/plugins/cases/public/components/user_actions/property_actions/alert_property_actions.test.tsx index 63c7ea95d8d9a..ac2d1d245b56b 100644 --- a/x-pack/plugins/cases/public/components/user_actions/property_actions/alert_property_actions.test.tsx +++ b/x-pack/plugins/cases/public/components/user_actions/property_actions/alert_property_actions.test.tsx @@ -17,8 +17,7 @@ import { } from '../../../common/mock'; import { AlertPropertyActions } from './alert_property_actions'; -// FLAKY: https://github.com/elastic/kibana/issues/174667 -describe.skip('AlertPropertyActions', () => { +describe('AlertPropertyActions', () => { let appMock: AppMockRenderer; const props = { @@ -49,22 +48,6 @@ describe.skip('AlertPropertyActions', () => { ).toBeInTheDocument(); }); - it('renders the modal info correctly for one alert', async () => { - appMock.render(); - - expect(await screen.findByTestId('property-actions-user-action')).toBeInTheDocument(); - - userEvent.click(await screen.findByTestId('property-actions-user-action-ellipses')); - await waitForEuiPopoverOpen(); - - userEvent.click(await screen.findByTestId('property-actions-user-action-minusInCircle')); - - expect(await screen.findByTestId('property-actions-confirm-modal')).toBeInTheDocument(); - - expect(await screen.findByTestId('confirmModalTitleText')).toHaveTextContent('Remove alert'); - expect(await screen.findByText('Remove')).toBeInTheDocument(); - }); - it('renders the modal info correctly for multiple alert', async () => { appMock.render(); @@ -93,7 +76,7 @@ describe.skip('AlertPropertyActions', () => { expect(await screen.findByTestId('property-actions-confirm-modal')).toBeInTheDocument(); - userEvent.click(screen.getByText('Remove')); + userEvent.click(await screen.findByText('Remove')); await waitFor(() => { expect(props.onDelete).toHaveBeenCalled(); diff --git a/x-pack/plugins/cases/public/components/user_actions/property_actions/property_actions.test.tsx b/x-pack/plugins/cases/public/components/user_actions/property_actions/property_actions.test.tsx index 4d6964da2047c..6ae104a0d2521 100644 --- a/x-pack/plugins/cases/public/components/user_actions/property_actions/property_actions.test.tsx +++ b/x-pack/plugins/cases/public/components/user_actions/property_actions/property_actions.test.tsx @@ -6,7 +6,7 @@ */ import React from 'react'; -import { waitForEuiPopoverOpen } from '@elastic/eui/lib/test/rtl'; +import { waitForEuiPopoverOpen, screen } from '@elastic/eui/lib/test/rtl'; import userEvent from '@testing-library/user-event'; import type { AppMockRenderer } from '../../../common/mock'; import { createAppMockRenderer } from '../../../common/mock'; @@ -35,30 +35,30 @@ describe('UserActionPropertyActions', () => { }); it('renders the loading spinner correctly when loading', async () => { - const result = appMock.render(); + appMock.render(); - expect(result.getByTestId('user-action-title-loading')).toBeInTheDocument(); - expect(result.queryByTestId('property-actions-user-action')).not.toBeInTheDocument(); + expect(await screen.findByTestId('user-action-title-loading')).toBeInTheDocument(); + expect(screen.queryByTestId('property-actions-user-action')).not.toBeInTheDocument(); }); it('renders the property actions', async () => { - const result = appMock.render(); + appMock.render(); - expect(result.getByTestId('property-actions-user-action')).toBeInTheDocument(); + expect(await screen.findByTestId('property-actions-user-action')).toBeInTheDocument(); - userEvent.click(result.getByTestId('property-actions-user-action-ellipses')); + userEvent.click(await screen.findByTestId('property-actions-user-action-ellipses')); await waitForEuiPopoverOpen(); - expect(result.getByTestId('property-actions-user-action-group').children.length).toBe(1); - expect(result.queryByTestId('property-actions-user-action-pencil')).toBeInTheDocument(); + expect((await screen.findByTestId('property-actions-user-action-group')).children.length).toBe( + 1 + ); + expect(await screen.findByTestId('property-actions-user-action-pencil')).toBeInTheDocument(); }); it('does not render if properties are empty', async () => { - const result = appMock.render( - - ); + appMock.render(); - expect(result.queryByTestId('property-actions-user-action')).not.toBeInTheDocument(); - expect(result.queryByTestId('user-action-title-loading')).not.toBeInTheDocument(); + expect(screen.queryByTestId('property-actions-user-action')).not.toBeInTheDocument(); + expect(screen.queryByTestId('user-action-title-loading')).not.toBeInTheDocument(); }); }); diff --git a/x-pack/plugins/cases/public/components/user_actions/property_actions/registered_attachments_property_actions.test.tsx b/x-pack/plugins/cases/public/components/user_actions/property_actions/registered_attachments_property_actions.test.tsx index 3fb7b1fcc53cd..f0db59b3a682d 100644 --- a/x-pack/plugins/cases/public/components/user_actions/property_actions/registered_attachments_property_actions.test.tsx +++ b/x-pack/plugins/cases/public/components/user_actions/property_actions/registered_attachments_property_actions.test.tsx @@ -18,8 +18,7 @@ import { import { RegisteredAttachmentsPropertyActions } from './registered_attachments_property_actions'; import { AttachmentActionType } from '../../../client/attachment_framework/types'; -// FLAKY: https://github.com/elastic/kibana/issues/174384 -describe.skip('RegisteredAttachmentsPropertyActions', () => { +describe('RegisteredAttachmentsPropertyActions', () => { let appMock: AppMockRenderer; const props = { @@ -41,13 +40,7 @@ describe.skip('RegisteredAttachmentsPropertyActions', () => { userEvent.click(await screen.findByTestId('property-actions-user-action-ellipses')); - await waitForEuiPopoverOpen(); - - expect((await screen.findByTestId('property-actions-user-action-group')).children.length).toBe( - 1 - ); - - expect(await screen.findByTestId('property-actions-user-action-trash')).toBeInTheDocument(); + expect(await screen.findByTestId('property-actions-user-action-group')).toBeInTheDocument(); }); it('renders the modal info correctly', async () => { diff --git a/x-pack/plugins/cases/public/components/user_actions/property_actions/user_comment_property_actions.test.tsx b/x-pack/plugins/cases/public/components/user_actions/property_actions/user_comment_property_actions.test.tsx index 79f1c7ffc29d7..8fc3b0cb8adcb 100644 --- a/x-pack/plugins/cases/public/components/user_actions/property_actions/user_comment_property_actions.test.tsx +++ b/x-pack/plugins/cases/public/components/user_actions/property_actions/user_comment_property_actions.test.tsx @@ -6,7 +6,7 @@ */ import React from 'react'; -import { waitForEuiPopoverOpen } from '@elastic/eui/lib/test/rtl'; +import { waitForEuiPopoverOpen, screen } from '@elastic/eui/lib/test/rtl'; import userEvent from '@testing-library/user-event'; import type { AppMockRenderer } from '../../../common/mock'; import { @@ -17,12 +17,7 @@ import { import { UserCommentPropertyActions } from './user_comment_property_actions'; import { waitFor } from '@testing-library/react'; -// FLAKY: https://github.com/elastic/kibana/issues/175310 -// FLAKY: https://github.com/elastic/kibana/issues/175311 -// FLAKY: https://github.com/elastic/kibana/issues/175312 -// FLAKY: https://github.com/elastic/kibana/issues/175313 -// FLAKY: https://github.com/elastic/kibana/issues/175314 -describe.skip('UserCommentPropertyActions', () => { +describe('UserCommentPropertyActions', () => { let appMock: AppMockRenderer; const props = { @@ -38,80 +33,82 @@ describe.skip('UserCommentPropertyActions', () => { }); it('renders the correct number of actions', async () => { - const result = appMock.render(); + appMock.render(); - expect(result.getByTestId('property-actions-user-action')).toBeInTheDocument(); + expect(await screen.findByTestId('property-actions-user-action')).toBeInTheDocument(); - userEvent.click(result.getByTestId('property-actions-user-action-ellipses')); + userEvent.click(await screen.findByTestId('property-actions-user-action-ellipses')); await waitForEuiPopoverOpen(); - expect(result.getByTestId('property-actions-user-action-group').children.length).toBe(3); - expect(result.queryByTestId('property-actions-user-action-pencil')).toBeInTheDocument(); - expect(result.queryByTestId('property-actions-user-action-trash')).toBeInTheDocument(); - expect(result.queryByTestId('property-actions-user-action-quote')).toBeInTheDocument(); + expect((await screen.findByTestId('property-actions-user-action-group')).children.length).toBe( + 3 + ); + expect(screen.queryByTestId('property-actions-user-action-pencil')).toBeInTheDocument(); + expect(screen.queryByTestId('property-actions-user-action-trash')).toBeInTheDocument(); + expect(screen.queryByTestId('property-actions-user-action-quote')).toBeInTheDocument(); }); it('edits the comment correctly', async () => { - const result = appMock.render(); + appMock.render(); - expect(result.getByTestId('property-actions-user-action')).toBeInTheDocument(); + expect(await screen.findByTestId('property-actions-user-action')).toBeInTheDocument(); - userEvent.click(result.getByTestId('property-actions-user-action-ellipses')); + userEvent.click(await screen.findByTestId('property-actions-user-action-ellipses')); await waitForEuiPopoverOpen(); - expect(result.queryByTestId('property-actions-user-action-pencil')).toBeInTheDocument(); + expect(screen.queryByTestId('property-actions-user-action-pencil')).toBeInTheDocument(); - userEvent.click(result.getByTestId('property-actions-user-action-pencil')); + userEvent.click(await screen.findByTestId('property-actions-user-action-pencil')); expect(props.onEdit).toHaveBeenCalled(); }); it('quotes the comment correctly', async () => { - const result = appMock.render(); + appMock.render(); - expect(result.getByTestId('property-actions-user-action')).toBeInTheDocument(); + expect(await screen.findByTestId('property-actions-user-action')).toBeInTheDocument(); - userEvent.click(result.getByTestId('property-actions-user-action-ellipses')); + userEvent.click(await screen.findByTestId('property-actions-user-action-ellipses')); await waitForEuiPopoverOpen(); - expect(result.queryByTestId('property-actions-user-action-quote')).toBeInTheDocument(); + expect(screen.queryByTestId('property-actions-user-action-quote')).toBeInTheDocument(); - userEvent.click(result.getByTestId('property-actions-user-action-quote')); + userEvent.click(await screen.findByTestId('property-actions-user-action-quote')); expect(props.onQuote).toHaveBeenCalled(); }); it('deletes the comment correctly', async () => { - const result = appMock.render(); + appMock.render(); - expect(result.getByTestId('property-actions-user-action')).toBeInTheDocument(); + expect(await screen.findByTestId('property-actions-user-action')).toBeInTheDocument(); - userEvent.click(result.getByTestId('property-actions-user-action-ellipses')); + userEvent.click(await screen.findByTestId('property-actions-user-action-ellipses')); await waitForEuiPopoverOpen(); - expect(result.queryByTestId('property-actions-user-action-trash')).toBeInTheDocument(); + expect(screen.queryByTestId('property-actions-user-action-trash')).toBeInTheDocument(); - userEvent.click(result.getByTestId('property-actions-user-action-trash')); + userEvent.click(await screen.findByTestId('property-actions-user-action-trash')); await waitFor(() => { - expect(result.queryByTestId('property-actions-confirm-modal')).toBeInTheDocument(); + expect(screen.queryByTestId('property-actions-confirm-modal')).toBeInTheDocument(); }); - userEvent.click(result.getByText('Delete')); + userEvent.click(await screen.findByText('Delete')); expect(props.onDelete).toHaveBeenCalled(); }); it('does not show the property actions without delete permissions', async () => { appMock = createAppMockRenderer({ permissions: noCasesPermissions() }); - const result = appMock.render(); + appMock.render(); - expect(result.queryByTestId('property-actions-user-action')).not.toBeInTheDocument(); + expect(screen.queryByTestId('property-actions-user-action')).not.toBeInTheDocument(); }); it('does show the property actions with only delete permissions', async () => { appMock = createAppMockRenderer({ permissions: onlyDeleteCasesPermission() }); - const result = appMock.render(); + appMock.render(); - expect(result.getByTestId('property-actions-user-action')).toBeInTheDocument(); + expect(await screen.findByTestId('property-actions-user-action')).toBeInTheDocument(); }); }); From 1aa5e3829eade035001dc3d8675de96e0fc93c8f Mon Sep 17 00:00:00 2001 From: Maryam Saeidi Date: Tue, 13 Feb 2024 12:55:15 +0100 Subject: [PATCH 57/83] [Alert details page][Custom threshold] Add history chart to custom threshold alert details page (#176513) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Closes #175200 ## Summary This PR adds a history chart to the custom threshold alert details page. The history chart is only added if we have only 1 condition in the rule. Also, this PR fixes the issue of not applying group by information on the main chart that I mistakenly introduced during refactoring code in this [PR](https://github.com/elastic/kibana/pull/175777). ![image](https://github.com/elastic/kibana/assets/12370520/22b449c4-71de-4714-8ab8-9fdd244eb943) ## 🧪 How to test - Create a custom threshold rule with only one condition - Go to the alert details page from the alert table actions - You should be able to see the history chart for the last 30 days with the correct filtering both for optional KQL and group by information --- .../src/hooks/use_alerts_history.test.tsx | 57 +++++ .../src/hooks/use_alerts_history.ts | 17 +- .../alert_details_app_section.test.tsx.snap | 16 +- .../alert_details_app_section.test.tsx | 14 +- .../alert_details_app_section.tsx | 35 ++-- .../alert_history.tsx | 198 ++++++++++++++++++ .../helpers/get_filter_query.test.ts | 39 ---- .../helpers/get_filter_query.ts | 21 -- .../components/helpers/get_group.test.ts | 88 ++++++++ .../components/helpers/get_group.ts | 37 ++++ .../rule_condition_chart.tsx | 20 +- .../custom_threshold/components/types.ts | 19 ++ .../mocks/custom_threshold_rule.ts | 5 +- 13 files changed, 475 insertions(+), 91 deletions(-) create mode 100644 x-pack/plugins/observability/public/components/custom_threshold/components/alert_details_app_section/alert_history.tsx delete mode 100644 x-pack/plugins/observability/public/components/custom_threshold/components/alert_details_app_section/helpers/get_filter_query.test.ts delete mode 100644 x-pack/plugins/observability/public/components/custom_threshold/components/alert_details_app_section/helpers/get_filter_query.ts create mode 100644 x-pack/plugins/observability/public/components/custom_threshold/components/helpers/get_group.test.ts create mode 100644 x-pack/plugins/observability/public/components/custom_threshold/components/helpers/get_group.ts create mode 100644 x-pack/plugins/observability/public/components/custom_threshold/components/types.ts diff --git a/x-pack/packages/observability/alert_details/src/hooks/use_alerts_history.test.tsx b/x-pack/packages/observability/alert_details/src/hooks/use_alerts_history.test.tsx index 4c47f82237cb8..9e059d61753f4 100644 --- a/x-pack/packages/observability/alert_details/src/hooks/use_alerts_history.test.tsx +++ b/x-pack/packages/observability/alert_details/src/hooks/use_alerts_history.test.tsx @@ -150,4 +150,61 @@ describe('useAlertsHistory', () => { expect(result.current.data.histogramTriggeredAlerts?.length).toEqual(31); expect(result.current.data.totalTriggeredAlerts).toEqual(32); }); + + it('calls http post including term queries', async () => { + const controller = new AbortController(); + const signal = controller.signal; + const mockedHttpPost = jest.fn(); + const http = { + post: mockedHttpPost.mockResolvedValue({ + hits: { total: { value: 32, relation: 'eq' }, max_score: null, hits: [] }, + aggregations: { + avgTimeToRecoverUS: { doc_count: 28, recoveryTime: { value: 134959464.2857143 } }, + histogramTriggeredAlerts: { + buckets: [ + { key_as_string: '2023-04-10T00:00:00.000Z', key: 1681084800000, doc_count: 0 }, + ], + }, + }, + }), + } as unknown as HttpSetup; + + const { result, waitFor } = renderHook( + () => + useAlertsHistory({ + http, + featureIds: [AlertConsumers.APM], + ruleId, + dateRange: { from: start, to: end }, + queries: [ + { + term: { + 'kibana.alert.group.value': { + value: 'host=1', + }, + }, + }, + ], + }), + { + wrapper, + } + ); + + await act(async () => { + await waitFor(() => result.current.isSuccess); + }); + expect(mockedHttpPost).toBeCalledWith('/internal/rac/alerts/find', { + body: + '{"size":0,"feature_ids":["apm"],"query":{"bool":{"must":[' + + '{"term":{"kibana.alert.rule.uuid":"cfd36e60-ef22-11ed-91eb-b7893acacfe2"}},' + + '{"term":{"kibana.alert.group.value":{"value":"host=1"}}},' + + '{"range":{"kibana.alert.time_range":{"from":"2023-04-10T00:00:00.000Z","to":"2023-05-10T00:00:00.000Z"}}}]}},' + + '"aggs":{"histogramTriggeredAlerts":{"date_histogram":{"field":"kibana.alert.start","fixed_interval":"1d",' + + '"extended_bounds":{"min":"2023-04-10T00:00:00.000Z","max":"2023-05-10T00:00:00.000Z"}}},' + + '"avgTimeToRecoverUS":{"filter":{"term":{"kibana.alert.status":"recovered"}},' + + '"aggs":{"recoveryTime":{"avg":{"field":"kibana.alert.duration.us"}}}}}}', + signal, + }); + }); }); diff --git a/x-pack/packages/observability/alert_details/src/hooks/use_alerts_history.ts b/x-pack/packages/observability/alert_details/src/hooks/use_alerts_history.ts index cc555bfb10b27..7c53001eade82 100644 --- a/x-pack/packages/observability/alert_details/src/hooks/use_alerts_history.ts +++ b/x-pack/packages/observability/alert_details/src/hooks/use_alerts_history.ts @@ -5,8 +5,9 @@ * 2.0. */ -import { AggregationsDateHistogramBucketKeys } from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import { type HttpSetup } from '@kbn/core/public'; +import { QueryDslQueryContainer } from '@elastic/elasticsearch/lib/api/types'; +import { AggregationsDateHistogramBucketKeys } from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import { ALERT_DURATION, ALERT_RULE_UUID, @@ -26,6 +27,7 @@ export interface Props { from: string; to: string; }; + queries?: QueryDslQueryContainer[]; } interface FetchAlertsHistory { @@ -45,7 +47,13 @@ export const EMPTY_ALERTS_HISTORY = { histogramTriggeredAlerts: [] as AggregationsDateHistogramBucketKeys[], avgTimeToRecoverUS: 0, }; -export function useAlertsHistory({ featureIds, ruleId, dateRange, http }: Props): UseAlertsHistory { +export function useAlertsHistory({ + featureIds, + ruleId, + dateRange, + http, + queries, +}: Props): UseAlertsHistory { const { isInitialLoading, isLoading, isError, isSuccess, isRefetching, data } = useQuery({ queryKey: ['useAlertsHistory'], queryFn: async ({ signal }) => { @@ -58,6 +66,7 @@ export function useAlertsHistory({ featureIds, ruleId, dateRange, http }: Props) ruleId, dateRange, signal, + queries, }); }, refetchOnWindowFocus: false, @@ -87,12 +96,14 @@ interface AggsESResponse { }; }; } + export async function fetchTriggeredAlertsHistory({ featureIds, http, ruleId, dateRange, signal, + queries = [], }: { featureIds: ValidFeatureId[]; http: HttpSetup; @@ -102,6 +113,7 @@ export async function fetchTriggeredAlertsHistory({ to: string; }; signal?: AbortSignal; + queries?: QueryDslQueryContainer[]; }): Promise { try { const responseES = await http.post(`${BASE_RAC_ALERTS_API_PATH}/find`, { @@ -117,6 +129,7 @@ export async function fetchTriggeredAlertsHistory({ [ALERT_RULE_UUID]: ruleId, }, }, + ...queries, { range: { [ALERT_TIME_RANGE]: dateRange, diff --git a/x-pack/plugins/observability/public/components/custom_threshold/components/alert_details_app_section/__snapshots__/alert_details_app_section.test.tsx.snap b/x-pack/plugins/observability/public/components/custom_threshold/components/alert_details_app_section/__snapshots__/alert_details_app_section.test.tsx.snap index a9a77b477f1a2..b589b0ed7bd26 100644 --- a/x-pack/plugins/observability/public/components/custom_threshold/components/alert_details_app_section/__snapshots__/alert_details_app_section.test.tsx.snap +++ b/x-pack/plugins/observability/public/components/custom_threshold/components/alert_details_app_section/__snapshots__/alert_details_app_section.test.tsx.snap @@ -3,6 +3,18 @@ exports[`AlertDetailsAppSection should render annotations 1`] = ` Array [ Object { + "additionalFilters": Array [ + Object { + "meta": Object {}, + "query": Object { + "term": Object { + "host.name": Object { + "value": "host-1", + }, + }, + }, + }, + ], "annotations": Array [ Object { "color": "#BD271E", @@ -27,6 +39,9 @@ Array [ "type": "manual", }, ], + "chartOptions": Object { + "seriesType": "bar_stacked", + }, "dataView": undefined, "groupBy": Array [ "host.hostname", @@ -52,7 +67,6 @@ Array [ "query": "host.hostname: Users-System.local and service.type: system", }, }, - "seriesType": "bar_stacked", "timeRange": Object { "from": "2023-03-28T10:43:13.802Z", "to": "2023-03-29T13:14:09.581Z", diff --git a/x-pack/plugins/observability/public/components/custom_threshold/components/alert_details_app_section/alert_details_app_section.test.tsx b/x-pack/plugins/observability/public/components/custom_threshold/components/alert_details_app_section/alert_details_app_section.test.tsx index ab100bf98bd48..fe61d56cfb9bd 100644 --- a/x-pack/plugins/observability/public/components/custom_threshold/components/alert_details_app_section/alert_details_app_section.test.tsx +++ b/x-pack/plugins/observability/public/components/custom_threshold/components/alert_details_app_section/alert_details_app_section.test.tsx @@ -19,7 +19,8 @@ import { } from '../../mocks/custom_threshold_rule'; import { CustomThresholdAlertFields } from '../../types'; import { RuleConditionChart } from '../rule_condition_chart/rule_condition_chart'; -import AlertDetailsAppSection, { CustomThresholdAlert } from './alert_details_app_section'; +import { CustomThresholdAlert } from '../types'; +import AlertDetailsAppSection from './alert_details_app_section'; import { Groups } from './groups'; import { Tags } from './tags'; @@ -28,6 +29,17 @@ const mockedChartStartContract = chartPluginMock.createStartContract(); jest.mock('@kbn/observability-alert-details', () => ({ AlertAnnotation: () => {}, AlertActiveTimeRangeAnnotation: () => {}, + useAlertsHistory: () => ({ + data: { + histogramTriggeredAlerts: [ + { key_as_string: '2023-04-10T00:00:00.000Z', key: 1681084800000, doc_count: 2 }, + ], + avgTimeToRecoverUS: 0, + totalTriggeredAlerts: 2, + }, + isLoading: false, + isError: false, + }), })); jest.mock('@kbn/observability-get-padded-alert-time-range-util', () => ({ diff --git a/x-pack/plugins/observability/public/components/custom_threshold/components/alert_details_app_section/alert_details_app_section.tsx b/x-pack/plugins/observability/public/components/custom_threshold/components/alert_details_app_section/alert_details_app_section.tsx index f07a6ddac4501..71061a75cde4f 100644 --- a/x-pack/plugins/observability/public/components/custom_threshold/components/alert_details_app_section/alert_details_app_section.tsx +++ b/x-pack/plugins/observability/public/components/custom_threshold/components/alert_details_app_section/alert_details_app_section.tsx @@ -5,6 +5,7 @@ * 2.0. */ +import chroma from 'chroma-js'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n-react'; import React, { useEffect, useState } from 'react'; @@ -20,7 +21,7 @@ import { useEuiTheme, transparentize, } from '@elastic/eui'; -import { Rule, RuleTypeParams } from '@kbn/alerting-plugin/common'; +import { RuleTypeParams } from '@kbn/alerting-plugin/common'; import { getPaddedAlertTimeRange } from '@kbn/observability-get-padded-alert-time-range-util'; import { ALERT_END, @@ -30,34 +31,27 @@ import { TAGS, } from '@kbn/rule-data-utils'; import { DataView } from '@kbn/data-views-plugin/common'; -import chroma from 'chroma-js'; import type { EventAnnotationConfig, PointInTimeEventAnnotationConfig, RangeEventAnnotationConfig, } from '@kbn/event-annotation-common'; import moment from 'moment'; +import { AlertHistoryChart } from './alert_history'; import { useLicense } from '../../../../hooks/use_license'; import { useKibana } from '../../../../utils/kibana_react'; import { metricValueFormatter } from '../../../../../common/custom_threshold_rule/metric_value_formatter'; -import { AlertSummaryField, TopAlert } from '../../../..'; -import { - AlertParams, - CustomThresholdAlertFields, - CustomThresholdRuleTypeParams, - MetricExpression, -} from '../../types'; +import { AlertSummaryField } from '../../../..'; +import { AlertParams, MetricExpression } from '../../types'; import { TIME_LABELS } from '../criterion_preview_chart/criterion_preview_chart'; import { Threshold } from '../custom_threshold'; +import { getGroupFilters } from '../helpers/get_group'; +import { CustomThresholdRule, CustomThresholdAlert } from '../types'; import { LogRateAnalysis } from './log_rate_analysis'; import { Groups } from './groups'; import { Tags } from './tags'; import { RuleConditionChart } from '../rule_condition_chart/rule_condition_chart'; -// TODO Use a generic props for app sections https://github.com/elastic/kibana/issues/152690 -export type CustomThresholdRule = Rule; -export type CustomThresholdAlert = TopAlert; - interface AppSectionProps { alert: CustomThresholdAlert; rule: CustomThresholdRule; @@ -261,14 +255,18 @@ export default function AlertDetailsAppSection({ @@ -278,6 +276,7 @@ export default function AlertDetailsAppSection({ {hasLogRateAnalysisLicense && ( )} + ) : null; diff --git a/x-pack/plugins/observability/public/components/custom_threshold/components/alert_details_app_section/alert_history.tsx b/x-pack/plugins/observability/public/components/custom_threshold/components/alert_details_app_section/alert_history.tsx new file mode 100644 index 0000000000000..3e95877da7e59 --- /dev/null +++ b/x-pack/plugins/observability/public/components/custom_threshold/components/alert_details_app_section/alert_history.tsx @@ -0,0 +1,198 @@ +/* + * 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 moment from 'moment'; +import React from 'react'; +import { v4 as uuidv4 } from 'uuid'; +import { RuleTypeParams } from '@kbn/alerting-plugin/common'; +import { EventAnnotationConfig } from '@kbn/event-annotation-common'; +import { DataView } from '@kbn/data-views-plugin/common'; +import { + EuiPanel, + EuiFlexGroup, + EuiFlexItem, + EuiTitle, + EuiText, + EuiSpacer, + EuiLoadingSpinner, + useEuiTheme, +} from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { ALERT_GROUP, ALERT_GROUP_VALUE, type AlertConsumers } from '@kbn/rule-data-utils'; +import { useAlertsHistory } from '@kbn/observability-alert-details'; +import { convertTo } from '../../../../../common/utils/formatters'; +import { useKibana } from '../../../../utils/kibana_react'; +import { AlertParams } from '../../types'; +import { getGroupFilters, getGroupQueries } from '../helpers/get_group'; +import { RuleConditionChart } from '../rule_condition_chart/rule_condition_chart'; +import { CustomThresholdAlert, CustomThresholdRule } from '../types'; + +const DEFAULT_INTERVAL = '1d'; +const SERIES_TYPE = 'bar_stacked'; + +interface Props { + alert: CustomThresholdAlert; + rule: CustomThresholdRule; + dataView?: DataView; +} + +const dateRange = { + from: 'now-30d', + to: 'now', +}; + +export function AlertHistoryChart({ rule, dataView, alert }: Props) { + const { http, notifications } = useKibana().services; + const { euiTheme } = useEuiTheme(); + const ruleParams = rule.params as RuleTypeParams & AlertParams; + const criterion = rule.params.criteria[0]; + const groups = alert.fields[ALERT_GROUP]; + const featureIds = [rule.consumer as AlertConsumers]; + + const { + data: { histogramTriggeredAlerts, avgTimeToRecoverUS, totalTriggeredAlerts }, + isLoading, + isError, + } = useAlertsHistory({ + http, + featureIds, + ruleId: rule.id, + dateRange, + queries: getGroupQueries(groups, ALERT_GROUP_VALUE), + }); + + // Only show alert history chart if there is only one condition + if (rule.params.criteria.length > 1) { + return null; + } + + if (isError) { + notifications?.toasts.addDanger({ + title: i18n.translate('xpack.observability.customThreshold.alertHistory.error.toastTitle', { + defaultMessage: 'Alerts history chart error', + }), + text: i18n.translate( + 'xpack.observability.customThreshold.alertHistory.error.toastDescription', + { + defaultMessage: `An error occurred when fetching alert history chart data`, + } + ), + }); + } + + const annotations: EventAnnotationConfig[] = + histogramTriggeredAlerts + ?.filter((annotation) => annotation.doc_count > 0) + .map((annotation) => { + return { + type: 'manual', + id: uuidv4(), + label: String(annotation.doc_count), + key: { + type: 'point_in_time', + timestamp: moment(new Date(annotation.key_as_string!)).toISOString(), + }, + lineWidth: 2, + color: euiTheme.colors.danger, + icon: 'alert', + textVisibility: true, + }; + }) || []; + + return ( + + + + +

+ {i18n.translate('xpack.observability.customThreshold.alertHistory.chartTitle', { + defaultMessage: 'Alerts history', + })} +

+
+
+ + + {i18n.translate('xpack.observability.customThreshold.alertHistory.last30days', { + defaultMessage: 'Last 30 days', + })} + + +
+ + + + + + + +

+ {isLoading ? : totalTriggeredAlerts || '-'} +

+
+
+
+ + + {i18n.translate( + 'xpack.observability.customThreshold.alertHistory.alertsTriggered', + { + defaultMessage: 'Alerts triggered', + } + )} + + +
+
+ + + + +

+ {isLoading ? ( + + ) : avgTimeToRecoverUS ? ( + convertTo({ + unit: 'minutes', + microseconds: avgTimeToRecoverUS, + extended: true, + }).formatted + ) : ( + '-' + )} +

+
+
+
+ + + {i18n.translate('xpack.observability.customThreshold.alertHistory.avgTimeToRecover', { + defaultMessage: 'Avg time to recover', + })} + + +
+
+ + +
+ ); +} diff --git a/x-pack/plugins/observability/public/components/custom_threshold/components/alert_details_app_section/helpers/get_filter_query.test.ts b/x-pack/plugins/observability/public/components/custom_threshold/components/alert_details_app_section/helpers/get_filter_query.test.ts deleted file mode 100644 index 91cfa4f7abc01..0000000000000 --- a/x-pack/plugins/observability/public/components/custom_threshold/components/alert_details_app_section/helpers/get_filter_query.test.ts +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { getFilterQuery } from './get_filter_query'; - -describe('getFilterQuery', () => { - it('should generate correct filter query when original query is not empty', () => { - const query = 'container.id: container-1'; - const groups = [ - { field: 'container.id', value: 'container-0' }, - { field: 'host.name', value: 'host-0' }, - ]; - - expect(getFilterQuery(query, groups)).toBe( - '(container.id: container-1) and container.id: container-0 and host.name: host-0' - ); - }); - - it('should generate correct filter query when original query is empty', () => { - const query = ''; - const groups = [ - { field: 'container.id', value: 'container-0' }, - { field: 'host.name', value: 'host-0' }, - ]; - - expect(getFilterQuery(query, groups)).toBe('container.id: container-0 and host.name: host-0'); - }); - - it('should generate correct filter query when original query and groups both are empty', () => { - const query = ''; - const groups = undefined; - - expect(getFilterQuery(query, groups)).toBe(''); - }); -}); diff --git a/x-pack/plugins/observability/public/components/custom_threshold/components/alert_details_app_section/helpers/get_filter_query.ts b/x-pack/plugins/observability/public/components/custom_threshold/components/alert_details_app_section/helpers/get_filter_query.ts deleted file mode 100644 index 2e1dd2ee0e543..0000000000000 --- a/x-pack/plugins/observability/public/components/custom_threshold/components/alert_details_app_section/helpers/get_filter_query.ts +++ /dev/null @@ -1,21 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -export const getFilterQuery = ( - filter: string, - groups?: Array<{ - field: string; - value: string; - }> -) => { - let query = filter; - if (groups) { - const groupQueries = groups?.map(({ field, value }) => `${field}: ${value}`).join(' and '); - query = query ? `(${query}) and ${groupQueries}` : groupQueries; - } - return query; -}; diff --git a/x-pack/plugins/observability/public/components/custom_threshold/components/helpers/get_group.test.ts b/x-pack/plugins/observability/public/components/custom_threshold/components/helpers/get_group.test.ts new file mode 100644 index 0000000000000..586bf5124831e --- /dev/null +++ b/x-pack/plugins/observability/public/components/custom_threshold/components/helpers/get_group.test.ts @@ -0,0 +1,88 @@ +/* + * 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 { getGroupQueries, getGroupFilters } from './get_group'; + +describe('getGroup', () => { + describe('getGroupQueries', () => { + it('should generate correct query with default field name', () => { + const groups = [ + { field: 'container.id', value: 'container-0' }, + { field: 'host.name', value: 'host-0' }, + ]; + + expect(getGroupQueries(groups)).toEqual([ + { term: { 'container.id': { value: 'container-0' } } }, + { term: { 'host.name': { value: 'host-0' } } }, + ]); + }); + + it('should generate correct query with custom field name', () => { + const groups = [ + { field: 'container.id', value: 'container-0' }, + { field: 'host.name', value: 'host-0' }, + ]; + const fieldName = 'custom.field'; + + expect(getGroupQueries(groups, fieldName)).toEqual([ + { term: { 'custom.field': { value: 'container-0' } } }, + { term: { 'custom.field': { value: 'host-0' } } }, + ]); + }); + + it('should return empty array when groups is empty', () => { + const groups = undefined; + + expect(getGroupQueries(groups)).toEqual([]); + }); + }); + + describe('getGroupFilters', () => { + it('should generate correct filter with default field name', () => { + const groups = [ + { field: 'container.id', value: 'container-0' }, + { field: 'host.name', value: 'host-0' }, + ]; + + expect(getGroupFilters(groups)).toEqual([ + { + meta: {}, + query: { term: { 'container.id': { value: 'container-0' } } }, + }, + { + meta: {}, + query: { term: { 'host.name': { value: 'host-0' } } }, + }, + ]); + }); + + it('should generate correct filter with custom field name', () => { + const groups = [ + { field: 'container.id', value: 'container-0' }, + { field: 'host.name', value: 'host-0' }, + ]; + const fieldName = 'custom.field'; + + expect(getGroupFilters(groups, fieldName)).toEqual([ + { + meta: {}, + query: { term: { 'custom.field': { value: 'container-0' } } }, + }, + { + meta: {}, + query: { term: { 'custom.field': { value: 'host-0' } } }, + }, + ]); + }); + + it('should return empty array when groups is empty', () => { + const groups = undefined; + + expect(getGroupFilters(groups)).toEqual([]); + }); + }); +}); diff --git a/x-pack/plugins/observability/public/components/custom_threshold/components/helpers/get_group.ts b/x-pack/plugins/observability/public/components/custom_threshold/components/helpers/get_group.ts new file mode 100644 index 0000000000000..f0842bdf51028 --- /dev/null +++ b/x-pack/plugins/observability/public/components/custom_threshold/components/helpers/get_group.ts @@ -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 { Filter } from '@kbn/es-query'; +import { QueryDslQueryContainer } from '@elastic/elasticsearch/lib/api/types'; +import { Group } from '../types'; + +/* + * groupFieldName + * In some cases, like AAD indices, the field name for group value is differen from group.field, + * in AAD case, it is ALERT_GROUP_VALUE (`kibana.alert.group.value`). groupFieldName allows + * passing a different field name to be used in the query. + */ +export const getGroupQueries = ( + groups?: Group[], + groupFieldName?: string +): QueryDslQueryContainer[] => { + return ( + (groups && + groups.map((group) => ({ + term: { + [groupFieldName || group.field]: { + value: group.value, + }, + }, + }))) || + [] + ); +}; + +export const getGroupFilters = (groups?: Group[], groupFieldName?: string): Filter[] => { + return getGroupQueries(groups, groupFieldName).map((query) => ({ meta: {}, query })); +}; diff --git a/x-pack/plugins/observability/public/components/custom_threshold/components/rule_condition_chart/rule_condition_chart.tsx b/x-pack/plugins/observability/public/components/custom_threshold/components/rule_condition_chart/rule_condition_chart.tsx index 3cf10659ee66a..405de186d472d 100644 --- a/x-pack/plugins/observability/public/components/custom_threshold/components/rule_condition_chart/rule_condition_chart.tsx +++ b/x-pack/plugins/observability/public/components/custom_threshold/components/rule_condition_chart/rule_condition_chart.tsx @@ -8,7 +8,7 @@ import React, { useState, useEffect } from 'react'; import { SerializedSearchSourceFields } from '@kbn/data-plugin/common'; import { EuiEmptyPrompt, useEuiTheme } from '@elastic/eui'; -import { Query } from '@kbn/es-query'; +import { Query, Filter } from '@kbn/es-query'; import { FillStyle, SeriesType } from '@kbn/lens-plugin/public'; import { DataView } from '@kbn/data-views-plugin/common'; import { FormattedMessage } from '@kbn/i18n-react'; @@ -39,6 +39,11 @@ import { LensFieldFormat, } from './helpers'; +interface ChartOptions { + seriesType?: SeriesType; + interval?: string; +} + interface RuleConditionChartProps { metricExpression: MetricExpression; searchConfiguration: SerializedSearchSourceFields; @@ -47,7 +52,8 @@ interface RuleConditionChartProps { error?: IErrorObject; timeRange: TimeRange; annotations?: EventAnnotationConfig[]; - seriesType?: SeriesType; + chartOptions?: ChartOptions; + additionalFilters?: Filter[]; } const defaultQuery: Query = { @@ -63,7 +69,8 @@ export function RuleConditionChart({ error, annotations, timeRange, - seriesType, + chartOptions: { seriesType, interval } = {}, + additionalFilters = [], }: RuleConditionChartProps) { const { services: { lens }, @@ -76,6 +83,7 @@ export function RuleConditionChart({ const [thresholdReferenceLine, setThresholdReferenceLine] = useState(); const [alertAnnotation, setAlertAnnotation] = useState(); const [chartLoading, setChartLoading] = useState(false); + const filters = [...(searchConfiguration.filter || []), ...additionalFilters]; const formulaAsync = useAsync(() => { return lens.stateHelperApi(); }, [lens]); @@ -231,7 +239,7 @@ export function RuleConditionChart({ buckets: { type: 'date_histogram', params: { - interval: `${timeSize}${timeUnit}`, + interval: interval || `${timeSize}${timeUnit}`, }, }, seriesType: seriesType ? seriesType : 'bar', @@ -295,6 +303,7 @@ export function RuleConditionChart({ formula, formulaAsync.value, groupBy, + interval, metrics, threshold, thresholdReferenceLine, @@ -336,6 +345,7 @@ export function RuleConditionChart({ ); } + return (
); diff --git a/x-pack/plugins/observability/public/components/custom_threshold/components/types.ts b/x-pack/plugins/observability/public/components/custom_threshold/components/types.ts new file mode 100644 index 0000000000000..3662a3f5ecec0 --- /dev/null +++ b/x-pack/plugins/observability/public/components/custom_threshold/components/types.ts @@ -0,0 +1,19 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { Rule } from '@kbn/alerting-plugin/common'; +import { TopAlert } from '../../..'; +import { CustomThresholdAlertFields, CustomThresholdRuleTypeParams } from '../types'; + +// TODO Use a generic props for app sections https://github.com/elastic/kibana/issues/152690 +export type CustomThresholdRule = Rule; +export type CustomThresholdAlert = TopAlert; + +export interface Group { + field: string; + value: string; +} diff --git a/x-pack/plugins/observability/public/components/custom_threshold/mocks/custom_threshold_rule.ts b/x-pack/plugins/observability/public/components/custom_threshold/mocks/custom_threshold_rule.ts index 4961943f79712..8d79ddd225ba5 100644 --- a/x-pack/plugins/observability/public/components/custom_threshold/mocks/custom_threshold_rule.ts +++ b/x-pack/plugins/observability/public/components/custom_threshold/mocks/custom_threshold_rule.ts @@ -10,10 +10,7 @@ import { ParsedTechnicalFields } from '@kbn/rule-registry-plugin/common'; import { CustomThresholdAlertFields } from '../types'; import { Aggregators, Comparator } from '../../../../common/custom_threshold_rule/types'; -import { - CustomThresholdAlert, - CustomThresholdRule, -} from '../components/alert_details_app_section/alert_details_app_section'; +import { CustomThresholdAlert, CustomThresholdRule } from '../components/types'; export const buildCustomThresholdRule = ( rule: Partial = {} From dd06f81811d7f816a73203792605d513215da9d1 Mon Sep 17 00:00:00 2001 From: Marco Antonio Ghiani Date: Tue, 13 Feb 2024 13:17:14 +0100 Subject: [PATCH 58/83] [Logs Explorer] Support logs data views (#176078) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 📓 Summary Closes #175767 🧪 You can access a live deployment of this PR [here](https://issue-deploy-kibana-pr176078.kb.us-west2.gcp.elastic-cloud.com/app/observability-logs-explorer/). There has been a lot of talking around supporting selection for data views and staying on the Logs Explorer for those concerning logs data streams. This work supports selecting and exploring Discover data views on Logs Explorer. This is currently limited to logs data views. https://github.com/elastic/kibana/assets/34506779/cccd6863-e1c1-4fa6-a530-9aed5d8d97c1 ## Next steps We had already an offline conversation with the team about how naming the selector, selection modes and related entities is becoming inconsistent and more difficult to maintain. To keep this PR narrowed to the data views support, an upcoming PR will focus on renaming according to the new selector's responsibilities. ## Core changes ### DataViewDescriptor The `DataViewDescriptor` instance is a way to describe a data view in the context of Logs Explorer. This does not represent a DataView object complete of fields and all the details provided with a DataView instance, but it instead encapsulates the logic around identifying what data type a data view is about, as well as defining the logic to use it with the new `dataView` selection mode. It creates a new instance starting from a `DataViewListItem` object, which is a minimal object provided by the dataViews service to list existing data views. ### LogExplorerController The `LogExplorerController` state machine now handles the selected entry depending on its type, triggering different flows. There are 3 different journeys depending on the selected entry: - For a data view entry which is not about logs, the page redirects to Discover with the data view selected - For a data view entry about logs, the data loads in the Logs Explorer, switching to the persisted DataView. - For a dataset entry, an ad-hoc data view loads in the Logs Explorer. To avoid updating twice the data view (once during initialization, and immediately after during selection validation), the validation flow has been anticipated and restructured to follow different flows, depending on the selection type. Screenshot 2024-02-09 at 12 02 10 ### Dataset selector The selector state machine unifies the selection handler and expands the selection modes, adding a new `dataView` mode which handles logs data view selections. Screenshot 2024-02-05 at 16 24 31 --------- Co-authored-by: Marco Antonio Ghiani Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> --- .../models/data_view_descriptor.test.ts | 83 +++++++++++ .../data_views/models/data_view_descriptor.ts | 102 ++++++++++++++ .../logs_explorer/common/data_views/types.ts | 29 ++++ .../logs_explorer/common/data_views/utils.ts | 15 ++ .../dataset_selection/data_view_selection.ts | 47 +++++++ .../hydrate_dataset_selection.ts.ts | 3 + .../common/dataset_selection/index.ts | 15 +- .../single_dataset_selection.ts | 2 +- .../common/dataset_selection/types.ts | 12 ++ .../dataset_selector.stories.tsx | 16 +-- .../dataset_selector/dataset_selector.tsx | 16 +-- .../state_machine/state_machine.ts | 66 +++++++-- .../dataset_selector/state_machine/types.ts | 21 ++- .../state_machine/use_dataset_selector.ts | 10 +- .../sub_components/data_view_menu_item.tsx | 36 +++++ .../sub_components/data_views_panel_title.tsx | 21 --- ...asets_popover.tsx => selector_popover.tsx} | 68 ++++++--- .../components/dataset_selector/types.ts | 14 +- .../public/controller/create_controller.ts | 5 +- .../custom_dataset_selector.tsx | 7 +- .../customizations/logs_explorer_profile.tsx | 4 +- .../public/hooks/use_data_views.tsx | 15 +- .../public/hooks/use_dataset_selection.ts | 4 +- .../logs_explorer/public/hooks/use_esql.tsx | 14 +- .../data_views/src/state_machine.ts | 22 +-- .../state_machines/data_views/src/types.ts | 14 +- .../datasets/src/state_machine.ts | 2 +- .../src/notifications.ts | 18 ++- .../src/services/data_view_service.ts | 23 +++- .../src/services/discover_service.ts | 33 +++++ .../src/services/selection_service.ts | 110 ++++++++++----- .../src/state_machine.ts | 130 +++++++++++++----- .../logs_explorer_controller/src/types.ts | 57 +++++--- .../public/utils/parse_data_view_list_item.ts | 15 -- 34 files changed, 793 insertions(+), 256 deletions(-) create mode 100644 x-pack/plugins/observability_solution/logs_explorer/common/data_views/models/data_view_descriptor.test.ts create mode 100644 x-pack/plugins/observability_solution/logs_explorer/common/data_views/models/data_view_descriptor.ts create mode 100644 x-pack/plugins/observability_solution/logs_explorer/common/data_views/types.ts create mode 100644 x-pack/plugins/observability_solution/logs_explorer/common/data_views/utils.ts create mode 100644 x-pack/plugins/observability_solution/logs_explorer/common/dataset_selection/data_view_selection.ts create mode 100644 x-pack/plugins/observability_solution/logs_explorer/public/components/dataset_selector/sub_components/data_view_menu_item.tsx delete mode 100644 x-pack/plugins/observability_solution/logs_explorer/public/components/dataset_selector/sub_components/data_views_panel_title.tsx rename x-pack/plugins/observability_solution/logs_explorer/public/components/dataset_selector/sub_components/{datasets_popover.tsx => selector_popover.tsx} (55%) delete mode 100644 x-pack/plugins/observability_solution/logs_explorer/public/utils/parse_data_view_list_item.ts diff --git a/x-pack/plugins/observability_solution/logs_explorer/common/data_views/models/data_view_descriptor.test.ts b/x-pack/plugins/observability_solution/logs_explorer/common/data_views/models/data_view_descriptor.test.ts new file mode 100644 index 0000000000000..c5eb32a3c4f5e --- /dev/null +++ b/x-pack/plugins/observability_solution/logs_explorer/common/data_views/models/data_view_descriptor.test.ts @@ -0,0 +1,83 @@ +/* + * 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 { DataViewDescriptor } from './data_view_descriptor'; + +describe('DataViewDescriptor', () => { + it('should correctly assert whether a data view has "logs" type', () => { + const id = 'test-id'; + + // Assert truthy cases + expect(DataViewDescriptor.create({ id, title: 'auditbeat*' }).isLogsDataType()).toBeTruthy(); + expect(DataViewDescriptor.create({ id, title: 'auditbeat-*' }).isLogsDataType()).toBeTruthy(); + expect(DataViewDescriptor.create({ id, title: 'logs*' }).isLogsDataType()).toBeTruthy(); + expect(DataViewDescriptor.create({ id, title: 'logs-*' }).isLogsDataType()).toBeTruthy(); + expect(DataViewDescriptor.create({ id, title: 'logs-*-*' }).isLogsDataType()).toBeTruthy(); + expect( + DataViewDescriptor.create({ id, title: 'logs-system.syslog-*' }).isLogsDataType() + ).toBeTruthy(); + expect( + DataViewDescriptor.create({ id, title: 'logs-system.syslog-default' }).isLogsDataType() + ).toBeTruthy(); + expect( + DataViewDescriptor.create({ id, title: 'cluster1:logs-*' }).isLogsDataType() + ).toBeTruthy(); + expect( + DataViewDescriptor.create({ id, title: 'cluster1:logs-*-*' }).isLogsDataType() + ).toBeTruthy(); + expect( + DataViewDescriptor.create({ id, title: 'cluster1:logs-system.syslog-*' }).isLogsDataType() + ).toBeTruthy(); + expect( + DataViewDescriptor.create({ + id, + title: 'cluster1:logs-system.syslog-default', + }).isLogsDataType() + ).toBeTruthy(); + expect( + DataViewDescriptor.create({ id, title: 'logs-*,cluster1:logs-*' }).isLogsDataType() + ).toBeTruthy(); + expect( + DataViewDescriptor.create({ id, title: 'logs-*,cluster1:logs-*,' }).isLogsDataType() + ).toBeTruthy(); + expect( + DataViewDescriptor.create({ id, title: 'cluster1:logs-*,cluster2:logs-*' }).isLogsDataType() + ).toBeTruthy(); + expect( + DataViewDescriptor.create({ id, title: 'cluster1:logs-*,cluster2:logs-*' }).isLogsDataType() + ).toBeTruthy(); + expect( + DataViewDescriptor.create({ + id, + title: '*:logs-system.syslog-*,*:logs-system.errors-*', + }).isLogsDataType() + ).toBeTruthy(); + + // Assert falsy cases + expect(DataViewDescriptor.create({ id, title: 'auditbeats*' }).isLogsDataType()).toBeFalsy(); + expect(DataViewDescriptor.create({ id, title: 'auditbeats-*' }).isLogsDataType()).toBeFalsy(); + expect(DataViewDescriptor.create({ id, title: 'logss*' }).isLogsDataType()).toBeFalsy(); + expect(DataViewDescriptor.create({ id, title: 'logss-*' }).isLogsDataType()).toBeFalsy(); + expect(DataViewDescriptor.create({ id, title: 'metrics*' }).isLogsDataType()).toBeFalsy(); + expect(DataViewDescriptor.create({ id, title: 'metrics-*' }).isLogsDataType()).toBeFalsy(); + expect( + DataViewDescriptor.create({ + id, + title: '*:metrics-system.syslog-*,logs-system.errors-*', + }).isLogsDataType() + ).toBeFalsy(); + expect( + DataViewDescriptor.create({ id, title: 'cluster1:logs-*,clust,er2:logs-*' }).isLogsDataType() + ).toBeFalsy(); + expect( + DataViewDescriptor.create({ + id, + title: 'cluster1:logs-*, cluster2:logs-*', + }).isLogsDataType() + ).toBeFalsy(); + }); +}); diff --git a/x-pack/plugins/observability_solution/logs_explorer/common/data_views/models/data_view_descriptor.ts b/x-pack/plugins/observability_solution/logs_explorer/common/data_views/models/data_view_descriptor.ts new file mode 100644 index 0000000000000..9b9652301dddd --- /dev/null +++ b/x-pack/plugins/observability_solution/logs_explorer/common/data_views/models/data_view_descriptor.ts @@ -0,0 +1,102 @@ +/* + * 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 { DataViewListItem } from '@kbn/data-views-plugin/common'; +import { DataViewSpecWithId } from '../../dataset_selection'; +import { DataViewDescriptorType } from '../types'; +import { buildIndexPatternRegExp } from '../utils'; + +type Allowlist = Array; + +const LOGS_ALLOWLIST: Allowlist = [ + buildIndexPatternRegExp(['logs', 'auditbeat', 'filebeat', 'winbeat']), + // Add more strings or regex patterns as needed +]; + +export class DataViewDescriptor { + id: DataViewDescriptorType['id']; + dataType: DataViewDescriptorType['dataType']; + kibanaSpaces: DataViewDescriptorType['kibanaSpaces']; + name: DataViewDescriptorType['name']; + title: DataViewDescriptorType['title']; + type: DataViewDescriptorType['type']; + + private constructor(dataViewDescriptor: DataViewDescriptorType) { + this.id = dataViewDescriptor.id; + this.dataType = dataViewDescriptor.dataType; + this.kibanaSpaces = dataViewDescriptor.kibanaSpaces; + this.name = dataViewDescriptor.name; + this.title = dataViewDescriptor.title; + this.type = dataViewDescriptor.type; + } + + getFullTitle() { + return this.name; + } + + toDataviewSpec(): DataViewSpecWithId { + return { + id: this.id, + name: this.name, + title: this.title, + }; + } + + toPlain() { + return { + id: this.id, + dataType: this.dataType, + name: this.name, + title: this.title, + }; + } + + public static create({ id, namespaces, title, type, name }: DataViewListItem) { + const nameWithFallbackTitle = name ?? title; + const dataType = DataViewDescriptor.#extractDataType(title); + const kibanaSpaces = namespaces; + + return new DataViewDescriptor({ + id, + dataType, + kibanaSpaces, + name: nameWithFallbackTitle, + title, + type, + }); + } + + static #extractDataType(title: string): DataViewDescriptorType['dataType'] { + if (isAllowed(title, LOGS_ALLOWLIST)) { + return 'logs'; + } + + return 'unknown'; + } + + public isLogsDataType() { + return this.dataType === 'logs'; + } + + public isUnknownDataType() { + return this.dataType === 'unknown'; + } +} + +function isAllowed(value: string, allowList: Allowlist) { + for (const allowedItem of allowList) { + if (typeof allowedItem === 'string') { + return value === allowedItem; + } + if (allowedItem instanceof RegExp) { + return allowedItem.test(value); + } + } + + // If no match is found in the allowList, return false + return false; +} diff --git a/x-pack/plugins/observability_solution/logs_explorer/common/data_views/types.ts b/x-pack/plugins/observability_solution/logs_explorer/common/data_views/types.ts new file mode 100644 index 0000000000000..82030bcbef318 --- /dev/null +++ b/x-pack/plugins/observability_solution/logs_explorer/common/data_views/types.ts @@ -0,0 +1,29 @@ +/* + * 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 * as rt from 'io-ts'; + +const dataTypeRT = rt.keyof({ + logs: null, + unknown: null, +}); + +export const dataViewDescriptorRT = rt.exact( + rt.intersection([ + rt.type({ + id: rt.string, + name: rt.string, + title: rt.string, + dataType: dataTypeRT, + }), + rt.partial({ + kibanaSpaces: rt.array(rt.string), + type: rt.string, + }), + ]) +); + +export type DataViewDescriptorType = rt.TypeOf; diff --git a/x-pack/plugins/observability_solution/logs_explorer/common/data_views/utils.ts b/x-pack/plugins/observability_solution/logs_explorer/common/data_views/utils.ts new file mode 100644 index 0000000000000..08a68b0d46c94 --- /dev/null +++ b/x-pack/plugins/observability_solution/logs_explorer/common/data_views/utils.ts @@ -0,0 +1,15 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export const buildIndexPatternRegExp = (basePatterns: string[]) => { + // Create the base patterns union with strict boundaries + const basePatternGroup = `\\b(${basePatterns.join('|')})\\b[^,\\s]+`; + // Apply base patterns union for local and remote clusters + const localAndRemotePatternGroup = `((${basePatternGroup})|([^:,\\s]+:${basePatternGroup}))`; + // Handle trailing comma and multiple pattern concatenation + return new RegExp(`^${localAndRemotePatternGroup}(,${localAndRemotePatternGroup})*(,$|$)`, 'i'); +}; diff --git a/x-pack/plugins/observability_solution/logs_explorer/common/dataset_selection/data_view_selection.ts b/x-pack/plugins/observability_solution/logs_explorer/common/dataset_selection/data_view_selection.ts new file mode 100644 index 0000000000000..4886270a30ad7 --- /dev/null +++ b/x-pack/plugins/observability_solution/logs_explorer/common/dataset_selection/data_view_selection.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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { DataViewDescriptor } from '../data_views/models/data_view_descriptor'; +import { DatasetSelectionStrategy, DataViewSelectionPayload } from './types'; + +export class DataViewSelection implements DatasetSelectionStrategy { + selectionType: 'dataView'; + selection: { + dataView: DataViewDescriptor; + }; + + private constructor(dataViewDescriptor: DataViewDescriptor) { + this.selectionType = 'dataView'; + this.selection = { + dataView: dataViewDescriptor, + }; + } + + toDataviewSpec() { + return this.selection.dataView.toDataviewSpec(); + } + + toPlainSelection() { + return { + selectionType: this.selectionType, + selection: { + dataView: this.selection.dataView.toPlain(), + }, + }; + } + + public static fromSelection(selection: DataViewSelectionPayload) { + const { dataView } = selection; + + const dataViewDescriptor = DataViewDescriptor.create(dataView); + return DataViewSelection.create(dataViewDescriptor); + } + + public static create(dataViewDescriptor: DataViewDescriptor) { + return new DataViewSelection(dataViewDescriptor); + } +} diff --git a/x-pack/plugins/observability_solution/logs_explorer/common/dataset_selection/hydrate_dataset_selection.ts.ts b/x-pack/plugins/observability_solution/logs_explorer/common/dataset_selection/hydrate_dataset_selection.ts.ts index f881e90723e14..fbef397ebfb19 100644 --- a/x-pack/plugins/observability_solution/logs_explorer/common/dataset_selection/hydrate_dataset_selection.ts.ts +++ b/x-pack/plugins/observability_solution/logs_explorer/common/dataset_selection/hydrate_dataset_selection.ts.ts @@ -6,6 +6,7 @@ */ import { AllDatasetSelection } from './all_dataset_selection'; +import { DataViewSelection } from './data_view_selection'; import { SingleDatasetSelection } from './single_dataset_selection'; import { DatasetSelectionPlain } from './types'; import { UnresolvedDatasetSelection } from './unresolved_dataset_selection'; @@ -15,6 +16,8 @@ export const hydrateDatasetSelection = (datasetSelection: DatasetSelectionPlain) return AllDatasetSelection.create(); } else if (datasetSelection.selectionType === 'single') { return SingleDatasetSelection.fromSelection(datasetSelection.selection); + } else if (datasetSelection.selectionType === 'dataView') { + return DataViewSelection.fromSelection(datasetSelection.selection); } else { return UnresolvedDatasetSelection.fromSelection(datasetSelection.selection); } diff --git a/x-pack/plugins/observability_solution/logs_explorer/common/dataset_selection/index.ts b/x-pack/plugins/observability_solution/logs_explorer/common/dataset_selection/index.ts index 26fd974d0f0ac..a5b1f1cebd7e2 100644 --- a/x-pack/plugins/observability_solution/logs_explorer/common/dataset_selection/index.ts +++ b/x-pack/plugins/observability_solution/logs_explorer/common/dataset_selection/index.ts @@ -5,8 +5,8 @@ * 2.0. */ -import { DataViewListItem } from '@kbn/data-views-plugin/common'; import { AllDatasetSelection } from './all_dataset_selection'; +import { DataViewSelection } from './data_view_selection'; import { SingleDatasetSelection } from './single_dataset_selection'; import { UnresolvedDatasetSelection } from './unresolved_dataset_selection'; @@ -14,8 +14,7 @@ export type DatasetSelection = | AllDatasetSelection | SingleDatasetSelection | UnresolvedDatasetSelection; -export type DatasetSelectionChange = (datasetSelection: DatasetSelection) => void; -export type DataViewSelection = (dataView: DataViewListItem) => void; +export type SelectionChange = (selection: DatasetSelection | DataViewSelection) => void; export const isDatasetSelection = (input: any): input is DatasetSelection => { return ( @@ -25,7 +24,17 @@ export const isDatasetSelection = (input: any): input is DatasetSelection => { ); }; +export const isUnresolvedDatasetSelection = (input: any): input is UnresolvedDatasetSelection => { + return input instanceof UnresolvedDatasetSelection; +}; + +export const isDataViewSelection = (input: any): input is DataViewSelection => { + return input instanceof DataViewSelection; +}; + export * from './all_dataset_selection'; +export * from './data_view_selection'; +export * from './single_dataset_selection'; export * from './single_dataset_selection'; export * from './unresolved_dataset_selection'; export * from './errors'; diff --git a/x-pack/plugins/observability_solution/logs_explorer/common/dataset_selection/single_dataset_selection.ts b/x-pack/plugins/observability_solution/logs_explorer/common/dataset_selection/single_dataset_selection.ts index 6667dd55f3abe..f9eecab1feaff 100644 --- a/x-pack/plugins/observability_solution/logs_explorer/common/dataset_selection/single_dataset_selection.ts +++ b/x-pack/plugins/observability_solution/logs_explorer/common/dataset_selection/single_dataset_selection.ts @@ -50,7 +50,7 @@ export class SingleDatasetSelection implements DatasetSelectionStrategy { const integration = name && version ? { name, title, version } : undefined; const datasetInstance = Dataset.create(dataset, integration); - return new SingleDatasetSelection(datasetInstance); + return SingleDatasetSelection.create(datasetInstance); } public static create(dataset: Dataset) { diff --git a/x-pack/plugins/observability_solution/logs_explorer/common/dataset_selection/types.ts b/x-pack/plugins/observability_solution/logs_explorer/common/dataset_selection/types.ts index db3638aff6331..0f32211f2888c 100644 --- a/x-pack/plugins/observability_solution/logs_explorer/common/dataset_selection/types.ts +++ b/x-pack/plugins/observability_solution/logs_explorer/common/dataset_selection/types.ts @@ -7,6 +7,7 @@ import { DataViewSpec } from '@kbn/data-views-plugin/common'; import * as rt from 'io-ts'; import { datasetRT } from '../datasets'; +import { dataViewDescriptorRT } from '../data_views/types'; export const allDatasetSelectionPlainRT = rt.type({ selectionType: rt.literal('all'), @@ -33,6 +34,10 @@ const singleDatasetSelectionPayloadRT = rt.intersection([ }), ]); +const dataViewSelectionPayloadRT = rt.type({ + dataView: dataViewDescriptorRT, +}); + const unresolvedDatasetSelectionPayloadRT = rt.intersection([ integrationNameRT, rt.type({ @@ -45,6 +50,11 @@ export const singleDatasetSelectionPlainRT = rt.type({ selection: singleDatasetSelectionPayloadRT, }); +export const dataViewSelectionPlainRT = rt.type({ + selectionType: rt.literal('dataView'), + selection: dataViewSelectionPayloadRT, +}); + export const unresolvedDatasetSelectionPlainRT = rt.type({ selectionType: rt.literal('unresolved'), selection: unresolvedDatasetSelectionPayloadRT, @@ -52,11 +62,13 @@ export const unresolvedDatasetSelectionPlainRT = rt.type({ export const datasetSelectionPlainRT = rt.union([ allDatasetSelectionPlainRT, + dataViewSelectionPlainRT, singleDatasetSelectionPlainRT, unresolvedDatasetSelectionPlainRT, ]); export type SingleDatasetSelectionPayload = rt.TypeOf; +export type DataViewSelectionPayload = rt.TypeOf; export type UnresolvedDatasetSelectionPayload = rt.TypeOf< typeof unresolvedDatasetSelectionPayloadRT >; diff --git a/x-pack/plugins/observability_solution/logs_explorer/public/components/dataset_selector/dataset_selector.stories.tsx b/x-pack/plugins/observability_solution/logs_explorer/public/components/dataset_selector/dataset_selector.stories.tsx index 04c0c1ddedac9..ce172f45211da 100644 --- a/x-pack/plugins/observability_solution/logs_explorer/public/components/dataset_selector/dataset_selector.stories.tsx +++ b/x-pack/plugins/observability_solution/logs_explorer/public/components/dataset_selector/dataset_selector.stories.tsx @@ -11,11 +11,12 @@ import React, { useState } from 'react'; import { I18nProvider } from '@kbn/i18n-react'; import type { Meta, Story } from '@storybook/react'; import { IndexPattern } from '@kbn/io-ts-utils'; -import { DataViewListItem } from '@kbn/data-views-plugin/common'; +import { DataViewDescriptor } from '../../../common/data_views/models/data_view_descriptor'; import { AllDatasetSelection, DatasetSelection, - DatasetSelectionChange, + DataViewSelection, + SelectionChange, } from '../../../common/dataset_selection'; import { Dataset, Integration } from '../../../common/datasets'; import { DatasetSelector } from './dataset_selector'; @@ -43,8 +44,8 @@ const meta: Meta = { export default meta; const DatasetSelectorTemplate: Story = (args) => { - const [datasetSelection, setDatasetSelection] = useState(() => - AllDatasetSelection.create() + const [datasetSelection, setDatasetSelection] = useState( + () => AllDatasetSelection.create() ); const [search, setSearch] = useState({ @@ -59,7 +60,7 @@ const DatasetSelectorTemplate: Story = (args) => { } }; - const onSelectionChange: DatasetSelectionChange = (newSelection) => { + const onSelectionChange: SelectionChange = (newSelection) => { setDatasetSelection(newSelection); }; @@ -123,7 +124,6 @@ Basic.args = { isLoadingUncategorized: false, isSearchingIntegrations: false, onDataViewsReload: () => alert('Reload data views...'), - onDataViewSelection: (dataView) => alert(`Navigate to data view "${dataView.name}"`), onDataViewsTabClick: () => console.log('Load data views...'), onIntegrationsReload: () => alert('Reload integrations...'), onUncategorizedTabClick: () => console.log('Load uncategorized streams...'), @@ -508,7 +508,7 @@ const mockDatasets: Dataset[] = [ { name: 'data-scaling-logs-*' as IndexPattern }, ].map((dataset) => Dataset.create(dataset)); -const mockDataViews: DataViewListItem[] = [ +const mockDataViews: DataViewDescriptor[] = [ { id: 'logs-*', namespaces: ['default'], @@ -528,4 +528,4 @@ const mockDataViews: DataViewListItem[] = [ typeMeta: {}, name: 'synthetics-dashboard', }, -]; +].map((dataView) => DataViewDescriptor.create(dataView)); diff --git a/x-pack/plugins/observability_solution/logs_explorer/public/components/dataset_selector/dataset_selector.tsx b/x-pack/plugins/observability_solution/logs_explorer/public/components/dataset_selector/dataset_selector.tsx index b4ccd66a42cca..507f4d83f8957 100644 --- a/x-pack/plugins/observability_solution/logs_explorer/public/components/dataset_selector/dataset_selector.tsx +++ b/x-pack/plugins/observability_solution/logs_explorer/public/components/dataset_selector/dataset_selector.tsx @@ -23,8 +23,8 @@ import { UNCATEGORIZED_TAB_ID, } from './constants'; import { useDatasetSelector } from './state_machine/use_dataset_selector'; -import { DatasetsPopover } from './sub_components/datasets_popover'; -import { DataViewsPanelTitle } from './sub_components/data_views_panel_title'; +import { SelectorPopover } from './sub_components/selector_popover'; +import { DataViewMenuItem } from './sub_components/data_view_menu_item'; import { SearchControls } from './sub_components/search_controls'; import { ESQLButton, SelectorFooter, ShowAllLogsButton } from './sub_components/selector_footer'; import { DatasetSelectorProps } from './types'; @@ -49,7 +49,6 @@ export function DatasetSelector({ isLoadingIntegrations, isLoadingUncategorized, isSearchingIntegrations, - onDataViewSelection, onDataViewsReload, onDataViewsSearch, onDataViewsSort, @@ -86,7 +85,6 @@ export function DatasetSelector({ togglePopover, } = useDatasetSelector({ initialContext: { selection: datasetSelection }, - onDataViewSelection, onDataViewsSearch, onDataViewsSort, onIntegrationsLoadMore, @@ -164,7 +162,7 @@ export function DatasetSelector({ return dataViews.map((dataView) => ({ 'data-test-subj': getDataViewTestSubj(dataView.title), - name: dataView.name, + name: , onClick: () => selectDataView(dataView), })); }, [dataViews, dataViewsError, isLoadingDataViews, selectDataView, onDataViewsReload]); @@ -208,8 +206,8 @@ export function DatasetSelector({ )); return ( - , + title: dataViewsLabel, width: DATA_VIEW_POPOVER_CONTENT_WIDTH, items: dataViewsItems, }, @@ -282,7 +280,7 @@ export function DatasetSelector({ {isEsqlEnabled && } - + ); } diff --git a/x-pack/plugins/observability_solution/logs_explorer/public/components/dataset_selector/state_machine/state_machine.ts b/x-pack/plugins/observability_solution/logs_explorer/public/components/dataset_selector/state_machine/state_machine.ts index 5e5df10504ec3..3982933e96fb0 100644 --- a/x-pack/plugins/observability_solution/logs_explorer/public/components/dataset_selector/state_machine/state_machine.ts +++ b/x-pack/plugins/observability_solution/logs_explorer/public/components/dataset_selector/state_machine/state_machine.ts @@ -6,7 +6,13 @@ */ import { actions, assign, createMachine, raise } from 'xstate'; -import { AllDatasetSelection, SingleDatasetSelection } from '../../../../common/dataset_selection'; +import { + AllDatasetSelection, + DataViewSelection, + isDatasetSelection, + isDataViewSelection, + SingleDatasetSelection, +} from '../../../../common/dataset_selection'; import { DATA_VIEWS_TAB_ID, INTEGRATIONS_TAB_ID, UNCATEGORIZED_TAB_ID } from '../constants'; import { defaultSearch, DEFAULT_CONTEXT } from './defaults'; import { @@ -20,7 +26,7 @@ import { export const createPureDatasetsSelectorStateMachine = ( initialContext: Partial = DEFAULT_CONTEXT ) => - /** @xstate-layout N4IgpgJg5mDOIC5QBECGAXVsztgZTABswBjdAewCcA6AB3PoDcwaTDzsIBiAFQHkA4gIAyAUQDaABgC6iUPVgBLdIvIA7OSAAeiAIy6ATAGZqBgBwAWMwHYzdgwE4zD6wBoQAT0RGj16gDYzIwBWKwdJC2CDawMAX1j3NExsXAJiMio6BnJmGgYwNS4AYWE+PAkZTQVlVQ0kbUQbP0kDf0l-IwddSUkXYPcvBH99ah6jNqMzFrMDXSN4xIwsHHwiUgoaeiYWanzC-iExKVl66pV1TR0EO11qI0MLDosLXWsHWYHEUOCAmJ6u3QWBxWawLEBJZapNYZTbZXK7WgFLjlMRFHgAfQAgsJhOjSgI8OjkJieJjyjxjlUODULvUrmZ-H5rLopp0jO1rMEHP5PkMZndOT5JJ1ggy4glwUsUqt0hssts8oi1NRFGp0GAoJQMLVYDxUAAjZEAdQAkjwigAJdH8dEAVQAckUSaIBHwAEomgBaomQ1sxACFKadqec6qArgBaXT+H4GZ4RSS6YIxfwWSRuTx6OaSajWfwGVrRcIRIzixbJFZpdaZLY5HZ7FVqjVa0O6g3Gs2W618IkkzHogBqJtERsJpMDlWDSlDl0QswM1AsrTTDnuSZFFl5+nZufzxmeMSTorBEOlVZh8rrioKjfVmu16jb+uohEUsBUaigJqb99bxQtmL2gIojogACoBojCEG8ghrUs4IN0DimPmoRBP4-hOO0Zi8pytwOMEnRAtEbSzGYJ5SpW0JyrW8INqqd4tjqerPq+76ql+P6MY+yJFG6fA4t26ImvaPAum6JImnw9qEv6fA8PwACy0EgGccF0ogEYFj8yZGBY9yPCELIOLyyYLhhuirjpHSzP45EVlCso1nC9ZKrezYPmoT4vm+H4cQxHmwMioiYm6Xb+gAmui9qYgpFQnDB05qeGGl6T87SkZEYzWJEm6ZkMQKmMEgQEbYjLMnZkIytWsIKgiN70e5rbMd5bGft+-l-ng7oYhF6Lusgohuspqm0slCARrpJhRI8MRvLMSa5YMZjBNpLTptywwuL4FVnlRTm1XRnEBc1rG+e1jW1Hg6CUGAqAALaBZagHAWBEFQZOCU0mGDQIb0yEGKh4wYVMMY4YmozDJyJUGMKeY7ZRjk1VedXKg1v5MQaLVnUdoZXTd92BeUIVhZF0WxcNsGjT9wQsgEwTCpYKbWJyBgmVypjro8q5vHpDjww51WXrRrlo1xnknT57Hnej6h47dD3It16K9f1g0U4lVNXMYzTOO8XQONylis3lK0LsC+jzpMDJLvzVUXjRLn1TjGMsZLbXO7L13y4TkGiGivakuS6tffBXK3GYFncnp7LssCOGTIu4zWC09P6PmtkSqeCOCw717KgArmoJAYBqVCKAAXpAzEduaVo2sJokCOJPCSdJfoTvFKmU99kaOEhpGTIKdhdHmW7tCYJHjFM7TRrMtvntRzl59QhfF3eZeVxA1d4KateCcSpKDsOo7t8HM7qQgcYBOncYMxZRXWEYW6OD8BszEuq4uBYtjz3tSPCzeVeJcoAbyru2ImoUrS9TJnFKkGse4pSmAEKYBF0IFmyvhLcpYLDUFeMnYE7ReaMl-ojIWjsC5F2AaAre4ClYqzdANIaH0u7wPghGb+Zk8z5lmrHem-Q8rbn8NQIq612TrhjPMTOFEBb2yXijFelD16UArmAw0KI-YYgPmSUQFJmEjQQeNOMOC5hxnpq0IqrQtx6SEamF4S5SKOAsCQnOciGwQCWAORQYAADuT4a5dnriJMSEkpJjgDGfJKP1xhm3vn0bknRWRj2TMImGQQ0yhEMLoZxsiDquXcZgTxPi-E707HXHsDonSN3dF6H0p89Hd3goCO4bRIjJ2TjTFakhsICNMYuQIQ8lyzwzuWSqC99rIzcR4rxvjt7BUgcrUmMVYFThDhfYYOZ3hWEyT0FanIsGODuFMecycZhRDLJKeydtF65JvPk1AhSZm0LdD1SKqsmGd30fBdZdxHiBANsCTk7wtxzFuC8NBBskwuEcNk65Ey8lTKKbM1Emi+xHxHBEzWiAR4cycARLky0IiLT0HGBcekZiWFCOyGYMLMjYFlLUagShPzECCsirEAl8SEi0UHeprCL5RlXKMdk5hOhf0mO0LczMmR8OBPhdkjwnFSMuWMmgdL1gMqZVAFl6j-bcp0RigxEYohIQjoEbojgfDhGNoMaM4RcEvALKmEIQQug0tVVRBlqBCCEFZRogO2jdEfIafy6MPx0Lj1TEEdo3Itypj8M4VMDNHjT1BGCNQ5AIBwE0FnGRGw4GrLGhGbhEMMr01hpEeO1BVwhF8HmRMI9ghurIZQfN59C3JiQulFkmVy38JtcMRONabB6RaM8PmSrRl-2bdQNgHBICtsiVcNOJhojpheJZHwHw8pLhweMXwqZzB6Qtk23OKMF2YvGgRW4XaKVZQrXlOMZgq0+AIlC0UpkT2uNcgACx8uegxjIn1OABq8V42CYi8h8EI6OoR0xdL+eOkZu1SGnsOh1F2-62FWBMDentvgcq8heE+543RcLjEkFEVNSHs45LhU7dDj5mq-vfJhi+zNcE03Se0IqcwgS8neBPRMsMxHpjhhO5DLibmow9uLTGp0pYyfgCsttP1NLtFGN0ZwYLlrLQBryRkxjMq2FFYzLJ4maOwoAdJhjsnXatT8hdT2+MHqsbGo4BcwouQxCsJ50U+m7DPsZDGOt9NOifqkwotepdlGb2Yq51T5ghERzZAe5MGFME9NpuYA2HR8KwcfuFujyo7kPKfPFq4pZbjDCBCyZaPn8zPyTFWwE+EpjW2FE2tVZBImfP5cmeN0YI4tGrVarcIi7jxJBN-EFVGLmTtIV10MjL2LEHKxpXSnbIif3uBhaI0YtwgwCDDXw+hy3QvM7m2lHr1DUC9YQNbCEmu+GHZ-Dh+g+16Ajjg47zJjDLU6ZI+IQA */ + /** @xstate-layout N4IgpgJg5mDOIC5QBECGAXVsztgZTABswBjdAewCcA6AB3PoDcwaTDzsIBiAFQHkA4gIAyAUQDaABgC6iUPVgBLdIvIA7OSACeiACwAmXdQDMANmMB2UwYAcu4wEYH+gDQgAHogdmj5yU9MATmMAVmNjfQcAXyi3NExsXAJiMio6BnJmGgYwNS4AYWE+PAkZTQVlVQ0kD0QbCwtqSX1TSXNAh0lJQIsQtx0EUycmyTNRm2abSOMYuIwsHHwiUgoaeiYWahy8-iExKVkaipV1TU8EGxsHE2ddc11dBwtAyP7EEN0Q6lMLfS6Ohy6QK6eqzEDxBZJZapNYZLJbWi5LglMT5HgAfQAgsJhOiigI8OjkJieJiSjwDuUOJVTjVzjYftQLA4JsZgm1eoFTG9BlMTL1wqNAiEGfowRDEksUqt0htsoi1NRFGp0GAoJQMFVYDxUAAjZEAdQAkjx8gAJdH8dEAVQAcvkSaIBHwAEpGgBaomQlsxACFKUdqSdqqBzgBaJ6SajCwLA5n+YxTYw8pzGKNWfT6YwPX4OEIi8XzSXJFZpdaZTbbJUqtUa4PavWGk3my18IkkzHogBqRtEBsJpP9ZUDSmDZ0QkX01AM1m6jjzbI+Ke86dMmezulz+ZshYSixLMNlFfluWrqvVmvUDd11EIilgKjUUCNNYv9YKZsxtoEonRAAUv1EYQA3kIMqnHBBOkCagWn0D4bDMIIJlMGweV6a5hTZIF9CsZoWV3SEpVLWE5QRU9lXPOstR1G87wfZVn1fKir2RfIXT4HFW3RI1bR4J0XRJI0+FtQlfT4Hh+AAWRAkBjnAulEDDTMvhCCxs0cO5QhZQIeVUqcggcYJVLMCIHFMAji2hGVy3hKsKNrS81GvW970fRjKMc2BkVETEXRbX0AE10VtTFJNKQ5QNHeTQ0U+wvjaSI7BCLpLE+XQeWsaC4JQsILAZBpolicEi33KyyzhSsFTPBz6xolz6KfF8PPfPBXQxQL0VdZBRBdGS5NpGKEDDdTqBCQwfhwl4nCXbQ6nzUbmkkZ5TCGHpLAs0rpXK0i7KYzy6rotympqqo8HQSgwFQABbLzzS-H9-0A4Dh0imkQ1qSDuhgtd4MQwJkL6WaEAsfwmiGXpcr+SxzKKiVNuIo9bKq+y32ovV6qOvbgzOi7rq8kpfP8oKQrCvqwIGj6QhZb5ksTTcWgaMbdJCLKFzuYJnnsQINqhLaSOPMjFRR5inIO1yGOO1H1Bxy6buRNr0Q6rqerJqKKfOLNGkkGxY30DpYxQgxmanYEnEnRMGQMHmiMPGzKvIrG0do8XGsd6Xztl-GgNENF21JclVbeiCGRsb5-vqSQRVGbp0qBqxrhQyafoCFprYPayKpPRUAFc1BIDA1SoRQAC9IBoptTQtK0eL4gQBJ4ISRJ9IcItk8n3vDcIpyzFpujgkGQYZFM2iMUJWgZ-6swZNOyv5pHT1z-PzyL0uIHLvBjUrrjiVJbte37ZvA7HBSEEMb41zXXRxkMkIfmTIHnGFaNJ4MYIek3HdYZK3mEbtrPqEXgXKAK8y6NgJn5C0HUSbhSpGrDusUJjfAmGEFamYLBAkBgMbwZ8ngg2BCPNkPwZ580RvbHOecgEgLXmAhWSsXTdV6i9NucCIJhnpt8Kwa5fhqW6MlTBXhvCmFGq0Z4aYFymDCMQ3+mdBYAIocvSgJdQH6hRD7DEO8ySiApEw-q8CLgoTDpcR4QQ9ZDBTPYUO3R-rBB+J8FCMM5h7h-rbGRVYIDzC7IoMAAB3a8FcWzV14vxQSwkBx+iPtFD6ZgTY3x6CzdorJh6qVGn8BCV8PjOEKo4wi6dtoCzcR4rxvj16bwCW2O0Dpa6ug9F6Q+Oj24QUBCYVonwB6RzzMlVCD9DBfDuPUMIdhIgXykS4naVV3GYE8T4vx4CibBVCjAkcQcT5DCjC8EEmSuj5l6CmCI0FEx4RwtrOCmYRkZzGaeCZqApnFJoS6dqQVlaMNbroiCqyTB3BQrGOMLNXAP28NcYxa5Yx5h6HrM5eT56KiuTcmZ3tfYaL3n2CJ6s6jWGjICLoSYr71FjlgrM1xwg-HqFcewvQxRfycTbGU2BpRVGoIwVAd4JkMXTlULgKK9FhmSgncGLw1IvBwk8FM6CjD9LMDYFmeYswhAhTQWlKx6WMuZZqJ8bL1AcocC8hpJ9uV-GoGZLkkcsyCt+CmVk0YeGGDsNK0IcrqAKrIEqpligWVqqsuy8Q+htUsN1WEKMXzBEglNX8rBoomSJkyjasadrKU5Nng6j16gHUMWIN5VEGJsS4nxISDRAd6m+sGk4UOjg8orSpvYUIeURVsmaZ8Eli4hj2sdcGFNT402qIRR2fNPrlmDSUpIIwBtMo4UnMKFMEiTZjUCEtfQlxko4WbUmxUSh21gHTWov2nYezIoLX2j6SlgTTlCFmZobI0HCofh0IR2twhpjUhsuCS66XJqZYQDdXb-ZaM5RBSw0Fb4vEjihUYVhJUTsZMGzWdg7BLQccVKluT5XLuoG+j96iOxIoND+k+lhjDRi5iDE1EwDD3wGIlIRC7lJJTaM+xVyaYVFLQ1iTiOat09tgfu84xaTAin5cyemm5uRA1SUIrSeZcE-EHbRp19HCk+KY3m79e7j79rytQS4kwJVX2zJHFM16mgIUFA+q4T642WRIS2+lDH5OdvQ7vHdWHlORPDIZa4t6Z2gbypEHZwmnhCJ1hp1oHQY2yrBGocgEA4CaDhs41YHGVMHq4WDRKnwUroP4cDPDwRQhX0Hl8l49q-7xec4pVS0EEoslSyBz4E7AXhA+EtbW+WKXZPM9I0ibAOCQGK6iyCZk8NHPQYZUIXcHA8gMKPcwVhbD2DNoV1xCoetcrCG5tclXkrVYy9a6M9XctNZ1gVsz8NRn5KqgAC1cktt5anJ5U2ZGpQwZqgZEuPTlxrIdYzzYuULN2os9RXd1SCPDFXqObbxXoamDwzbOAttYFr8H40kL-rI4W+10YXYfADotzgTDTCSitTcN8eTBFDg0cIeUyeAmZF907DtmpOwxhLX78AlkJfDH8G9nQdbGMlZKuCGV0EGtS3lNk-1Cc06hdVKWf3nYNXcidd2uMbpY4+nrKcowWa-BBBrkUGVLg7Z+BI3CtNuZHdi5Cshcil6F0UavGiKv2cMnU94GxBhVJBHHd06mc6DaLga2pCXlvrO3N1A7xAplviAg6PO7Xa4UyRC+B0DBExLajGkwl15fq1OBp8DrNBoavC316dO2d87mgWHT86lVbl1XvUz-2oEeGVp5klYOqbM0sHlujPmUI2tUtwZi9StIlnk2rqgMQMPQ19V3sTNmdcykcIit6M7hodgCeTgr2bofSGX2KjfZP7MjQzANGaBIwdAG9MGCZJJro5+Vp5Ur7JyZRTJ-DSnHwiIDWWQi892R5wU5iM9Z6Z+kKUYggA */ createMachine( { context: { ...DEFAULT_CONTEXT, ...initialContext }, @@ -134,7 +140,7 @@ export const createPureDatasetsSelectorStateMachine = ( }, SELECT_DATA_VIEW: { target: '#closed', - actions: ['selectDataView'], + actions: ['storeDataViewSelection'], }, }, }, @@ -143,8 +149,15 @@ export const createPureDatasetsSelectorStateMachine = ( }, }, selection: { - initial: 'single', + initial: 'validatingSelection', states: { + validatingSelection: { + always: [ + { cond: 'isDataViewSelection', target: 'dataView' }, + { cond: 'isAllDatasetSelection', target: 'all' }, + { cond: 'isSingleDatasetSelection', target: 'single' }, + ], + }, single: { on: { SELECT_ALL_LOGS_DATASET: { @@ -154,6 +167,10 @@ export const createPureDatasetsSelectorStateMachine = ( SELECT_DATASET: { actions: ['storeSingleSelection', 'notifySelectionChanged'], }, + SELECT_DATA_VIEW: { + actions: ['storeDataViewSelection', 'notifySelectionChanged'], + target: 'dataView', + }, }, }, all: { @@ -162,6 +179,25 @@ export const createPureDatasetsSelectorStateMachine = ( actions: ['storeSingleSelection', 'notifySelectionChanged'], target: 'single', }, + SELECT_DATA_VIEW: { + actions: ['storeDataViewSelection', 'notifySelectionChanged'], + target: 'dataView', + }, + }, + }, + dataView: { + on: { + SELECT_ALL_LOGS_DATASET: { + actions: ['storeAllSelection', 'notifySelectionChanged'], + target: 'all', + }, + SELECT_DATASET: { + actions: ['storeSingleSelection', 'notifySelectionChanged'], + target: 'single', + }, + SELECT_DATA_VIEW: { + actions: ['storeDataViewSelection', 'notifySelectionChanged'], + }, }, }, }, @@ -191,7 +227,14 @@ export const createPureDatasetsSelectorStateMachine = ( selection: AllDatasetSelection.create(), })), storeSingleSelection: assign((_context, event) => - 'dataset' in event ? { selection: SingleDatasetSelection.create(event.dataset) } : {} + event.type === 'SELECT_DATASET' + ? { selection: SingleDatasetSelection.create(event.selection) } + : {} + ), + storeDataViewSelection: assign((_context, event) => + event.type === 'SELECT_DATA_VIEW' + ? { selection: DataViewSelection.create(event.selection) } + : {} ), retrieveSearchFromCache: assign((context, event) => { if (event.type === 'CHANGE_PANEL' && 'panelId' in event) { @@ -228,12 +271,18 @@ export const createPureDatasetsSelectorStateMachine = ( } }), }, + guards: { + isDataViewSelection: (context) => isDataViewSelection(context.selection), + isAllDatasetSelection: (context) => + isDatasetSelection(context.selection) && context.selection.selectionType === 'all', + isSingleDatasetSelection: (context) => + isDatasetSelection(context.selection) && context.selection.selectionType === 'single', + }, } ); export const createDatasetsSelectorStateMachine = ({ initialContext, - onDataViewSelection, onDataViewsSearch, onDataViewsSort, onIntegrationsLoadMore, @@ -255,11 +304,6 @@ export const createDatasetsSelectorStateMachine = ({ loadMoreIntegrations: onIntegrationsLoadMore, relaodIntegrations: onIntegrationsReload, reloadUncategorized: onUncategorizedReload, - selectDataView: (_context, event) => { - if (event.type === 'SELECT_DATA_VIEW' && 'dataView' in event) { - return onDataViewSelection(event.dataView); - } - }, // Search actions searchIntegrations: (_context, event) => { if ('search' in event) { diff --git a/x-pack/plugins/observability_solution/logs_explorer/public/components/dataset_selector/state_machine/types.ts b/x-pack/plugins/observability_solution/logs_explorer/public/components/dataset_selector/state_machine/types.ts index 6ca09c9c2de07..0dd6f199c8fc7 100644 --- a/x-pack/plugins/observability_solution/logs_explorer/public/components/dataset_selector/state_machine/types.ts +++ b/x-pack/plugins/observability_solution/logs_explorer/public/components/dataset_selector/state_machine/types.ts @@ -4,12 +4,12 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import { DataViewListItem } from '@kbn/data-views-plugin/common'; +import { DataViewDescriptor } from '../../../../common/data_views/models/data_view_descriptor'; import { SearchDataViews } from '../../../hooks/use_data_views'; import { DatasetSelection, - DatasetSelectionChange, DataViewSelection, + SelectionChange, } from '../../../../common/dataset_selection'; import { Dataset } from '../../../../common/datasets/models/dataset'; import { ReloadDatasets, SearchDatasets } from '../../../hooks/use_datasets'; @@ -22,7 +22,7 @@ import type { IHashedCache } from '../../../../common/hashed_cache'; import { DatasetsSelectorSearchParams, PanelId, TabId } from '../types'; export interface DefaultDatasetsSelectorContext { - selection: DatasetSelection; + selection: DatasetSelection | DataViewSelection; tabId: TabId; panelId: PanelId; searchCache: IHashedCache; @@ -70,10 +70,18 @@ export type DatasetsSelectorTypestate = value: 'selection'; context: DefaultDatasetsSelectorContext; } + | { + value: 'selection.validatingSelection'; + context: DefaultDatasetsSelectorContext; + } | { value: 'selection.single'; context: DefaultDatasetsSelectorContext; } + | { + value: 'selection.dataView'; + context: DefaultDatasetsSelectorContext; + } | { value: 'selection.all'; context: DefaultDatasetsSelectorContext; @@ -103,11 +111,11 @@ export type DatasetsSelectorEvent = } | { type: 'SELECT_DATASET'; - dataset: Dataset; + selection: Dataset; } | { type: 'SELECT_DATA_VIEW'; - dataView: DataViewListItem; + selection: DataViewDescriptor; } | { type: 'SELECT_ALL_LOGS_DATASET'; @@ -126,7 +134,6 @@ export type DatasetsSelectorEvent = export interface DatasetsSelectorStateMachineDependencies { initialContext?: Partial; - onDataViewSelection: DataViewSelection; onDataViewsSearch: SearchDataViews; onDataViewsSort: SearchDataViews; onIntegrationsLoadMore: LoadMoreIntegrations; @@ -135,7 +142,7 @@ export interface DatasetsSelectorStateMachineDependencies { onIntegrationsSort: SearchIntegrations; onIntegrationsStreamsSearch: SearchIntegrations; onIntegrationsStreamsSort: SearchIntegrations; - onSelectionChange: DatasetSelectionChange; + onSelectionChange: SelectionChange; onUncategorizedReload: ReloadDatasets; onUncategorizedSearch: SearchDatasets; onUncategorizedSort: SearchDatasets; diff --git a/x-pack/plugins/observability_solution/logs_explorer/public/components/dataset_selector/state_machine/use_dataset_selector.ts b/x-pack/plugins/observability_solution/logs_explorer/public/components/dataset_selector/state_machine/use_dataset_selector.ts index 125217e8decdd..e1db03600a143 100644 --- a/x-pack/plugins/observability_solution/logs_explorer/public/components/dataset_selector/state_machine/use_dataset_selector.ts +++ b/x-pack/plugins/observability_solution/logs_explorer/public/components/dataset_selector/state_machine/use_dataset_selector.ts @@ -19,7 +19,6 @@ import { DatasetsSelectorStateMachineDependencies } from './types'; export const useDatasetSelector = ({ initialContext, - onDataViewSelection, onDataViewsSearch, onDataViewsSort, onIntegrationsLoadMore, @@ -36,7 +35,6 @@ export const useDatasetSelector = ({ const datasetsSelectorStateService = useInterpret(() => createDatasetsSelectorStateMachine({ initialContext, - onDataViewSelection, onDataViewsSearch, onDataViewsSort, onIntegrationsLoadMore, @@ -101,12 +99,16 @@ export const useDatasetSelector = ({ ); const selectDataset = useCallback( - (dataset) => datasetsSelectorStateService.send({ type: 'SELECT_DATASET', dataset }), + (dataset) => datasetsSelectorStateService.send({ type: 'SELECT_DATASET', selection: dataset }), [datasetsSelectorStateService] ); const selectDataView = useCallback( - (dataView) => datasetsSelectorStateService.send({ type: 'SELECT_DATA_VIEW', dataView }), + (dataViewDescriptor) => + datasetsSelectorStateService.send({ + type: 'SELECT_DATA_VIEW', + selection: dataViewDescriptor, + }), [datasetsSelectorStateService] ); diff --git a/x-pack/plugins/observability_solution/logs_explorer/public/components/dataset_selector/sub_components/data_view_menu_item.tsx b/x-pack/plugins/observability_solution/logs_explorer/public/components/dataset_selector/sub_components/data_view_menu_item.tsx new file mode 100644 index 0000000000000..04388f4f479ec --- /dev/null +++ b/x-pack/plugins/observability_solution/logs_explorer/public/components/dataset_selector/sub_components/data_view_menu_item.tsx @@ -0,0 +1,36 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { EuiIcon, EuiToolTip } from '@elastic/eui'; +import { css } from '@emotion/react'; +import { euiThemeVars } from '@kbn/ui-theme'; +import { DataViewDescriptor } from '../../../../common/data_views/models/data_view_descriptor'; +import { openDiscoverLabel } from '../constants'; + +interface DataViewMenuItemProps { + dataView: DataViewDescriptor; +} + +const rightSpacing = css` + margin-right: ${euiThemeVars.euiSizeS}; +`; + +export const DataViewMenuItem = ({ dataView }: DataViewMenuItemProps) => { + if (dataView.dataType === 'logs') { + return {dataView.name}; + } + + return ( + <> + {dataView.name} + + + + + ); +}; diff --git a/x-pack/plugins/observability_solution/logs_explorer/public/components/dataset_selector/sub_components/data_views_panel_title.tsx b/x-pack/plugins/observability_solution/logs_explorer/public/components/dataset_selector/sub_components/data_views_panel_title.tsx deleted file mode 100644 index 401a1728f9a7c..0000000000000 --- a/x-pack/plugins/observability_solution/logs_explorer/public/components/dataset_selector/sub_components/data_views_panel_title.tsx +++ /dev/null @@ -1,21 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import React from 'react'; -import { EuiBadge, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; -import { dataViewsLabel, openDiscoverLabel } from '../constants'; - -export const DataViewsPanelTitle = () => { - return ( - - {dataViewsLabel} - - {openDiscoverLabel} - - - ); -}; diff --git a/x-pack/plugins/observability_solution/logs_explorer/public/components/dataset_selector/sub_components/datasets_popover.tsx b/x-pack/plugins/observability_solution/logs_explorer/public/components/dataset_selector/sub_components/selector_popover.tsx similarity index 55% rename from x-pack/plugins/observability_solution/logs_explorer/public/components/dataset_selector/sub_components/datasets_popover.tsx rename to x-pack/plugins/observability_solution/logs_explorer/public/components/dataset_selector/sub_components/selector_popover.tsx index 6324ffe0fe85e..15ac903bb7581 100644 --- a/x-pack/plugins/observability_solution/logs_explorer/public/components/dataset_selector/sub_components/datasets_popover.tsx +++ b/x-pack/plugins/observability_solution/logs_explorer/public/components/dataset_selector/sub_components/selector_popover.tsx @@ -15,29 +15,29 @@ import { useIsWithinBreakpoints, } from '@elastic/eui'; import { PackageIcon } from '@kbn/fleet-plugin/public'; -import { DatasetSelection } from '../../../../common/dataset_selection'; +import { + DatasetSelection, + DataViewSelection, + isDataViewSelection, +} from '../../../../common/dataset_selection'; import { DATA_VIEW_POPOVER_CONTENT_WIDTH, POPOVER_ID } from '../constants'; import { getPopoverButtonStyles } from '../utils'; const panelStyle = { width: DATA_VIEW_POPOVER_CONTENT_WIDTH }; -interface DatasetsPopoverProps extends Omit { +interface SelectorPopoverProps extends Omit { children: React.ReactNode; onClick: () => void; - selection: DatasetSelection['selection']; + selection: DatasetSelection | DataViewSelection; } -export const DatasetsPopover = ({ +export const SelectorPopover = ({ children, onClick, selection, ...props -}: DatasetsPopoverProps) => { - const { iconType, parentIntegration } = selection.dataset; - const title = selection.dataset.getFullTitle(); +}: SelectorPopoverProps) => { const isMobile = useIsWithinBreakpoints(['xs', 's']); - const buttonStyles = getPopoverButtonStyles({ fullWidth: isMobile }); - const hasIntegration = typeof parentIntegration === 'object'; return ( - {iconType ? ( - - ) : hasIntegration ? ( - - ) : null} - {title} + {isDataViewSelection(selection) ? ( + + ) : ( + + )} } panelPaddingSize="none" @@ -83,3 +76,36 @@ export const DatasetsPopover = ({ ); }; + +const DataViewPopoverContent = ({ + dataViewSelection, +}: { + dataViewSelection: DataViewSelection; +}) => { + const { name } = dataViewSelection.selection.dataView; + + return {name}; +}; + +const DatasetPopoverContent = ({ datasetSelection }: { datasetSelection: DatasetSelection }) => { + const { iconType, parentIntegration } = datasetSelection.selection.dataset; + const title = datasetSelection.selection.dataset.getFullTitle(); + const hasIntegration = typeof parentIntegration === 'object'; + + return ( + <> + {iconType ? ( + + ) : hasIntegration ? ( + + ) : null} + {title} + + ); +}; diff --git a/x-pack/plugins/observability_solution/logs_explorer/public/components/dataset_selector/types.ts b/x-pack/plugins/observability_solution/logs_explorer/public/components/dataset_selector/types.ts index ebd0a802caf52..5076f382f2fcb 100644 --- a/x-pack/plugins/observability_solution/logs_explorer/public/components/dataset_selector/types.ts +++ b/x-pack/plugins/observability_solution/logs_explorer/public/components/dataset_selector/types.ts @@ -6,14 +6,14 @@ */ import { EuiContextMenuPanelId } from '@elastic/eui/src/components/context_menu/context_menu'; -import { DataViewListItem } from '@kbn/data-views-plugin/common'; import type { DatasetSelection, - DatasetSelectionChange, + SelectionChange, DataViewSelection, } from '../../../common/dataset_selection'; import { SortOrder } from '../../../common/latest'; import { Dataset, Integration, IntegrationId } from '../../../common/datasets'; +import { DataViewDescriptor } from '../../../common/data_views/models/data_view_descriptor'; import { LoadDatasets, ReloadDatasets, SearchDatasets } from '../../hooks/use_datasets'; import { LoadMoreIntegrations, @@ -35,9 +35,9 @@ export interface DatasetSelectorProps { /* Any error occurred to show when the user preview the generic data streams */ datasetsError: Error | null; /* The current selection instance */ - datasetSelection: DatasetSelection; + datasetSelection: DatasetSelection | DataViewSelection; /* The available data views list */ - dataViews: DataViewListItem[] | null; + dataViews: DataViewDescriptor[] | null; /* Any error occurred to show when the user preview the data views */ dataViewsError: Error | null; /* url props to navigate to discover ES|QL */ @@ -55,8 +55,6 @@ export interface DatasetSelectorProps { isEsqlEnabled: boolean; /* Triggered when retrying to load the data views */ onDataViewsReload: ReloadDataViews; - /* Triggered when selecting a data view */ - onDataViewSelection: DataViewSelection; /* Triggered when the data views tab is selected */ onDataViewsTabClick: LoadDataViews; /* Triggered when we reach the bottom of the integration list and want to load more */ @@ -77,7 +75,7 @@ export interface DatasetSelectorProps { /* Triggered when the uncategorized tab is selected */ onUncategorizedTabClick: LoadDatasets; /* Triggered when the selection is updated */ - onSelectionChange: DatasetSelectionChange; + onSelectionChange: SelectionChange; } export type PanelId = typeof INTEGRATIONS_PANEL_ID | IntegrationId; @@ -101,4 +99,4 @@ export type ChangePanelHandler = ({ panelId }: { panelId: EuiContextMenuPanelId export type DatasetSelectionHandler = (dataset: Dataset) => void; -export type DataViewSelectionHandler = (dataView: DataViewListItem) => void; +export type DataViewSelectionHandler = (dataView: DataViewDescriptor) => void; diff --git a/x-pack/plugins/observability_solution/logs_explorer/public/controller/create_controller.ts b/x-pack/plugins/observability_solution/logs_explorer/public/controller/create_controller.ts index cf7a9c2a07f8a..59aad01a0b388 100644 --- a/x-pack/plugins/observability_solution/logs_explorer/public/controller/create_controller.ts +++ b/x-pack/plugins/observability_solution/logs_explorer/public/controller/create_controller.ts @@ -35,7 +35,7 @@ interface Dependencies { type InitialState = LogsExplorerPublicStateUpdate; export const createLogsExplorerControllerFactory = - ({ core, plugins: { data } }: Dependencies) => + ({ core, plugins }: Dependencies) => async ({ customizations = {}, initialState, @@ -43,6 +43,8 @@ export const createLogsExplorerControllerFactory = customizations?: LogsExplorerCustomizations; initialState?: InitialState; }): Promise => { + const { data, dataViews, discover } = plugins; + const datasetsClient = new DatasetsService().start({ http: core.http, }).client; @@ -68,6 +70,7 @@ export const createLogsExplorerControllerFactory = const machine = createLogsExplorerControllerStateMachine({ datasetsClient, + plugins: { dataViews, discover }, initialContext, query: discoverServices.data.query, toasts: core.notifications.toasts, diff --git a/x-pack/plugins/observability_solution/logs_explorer/public/customizations/custom_dataset_selector.tsx b/x-pack/plugins/observability_solution/logs_explorer/public/customizations/custom_dataset_selector.tsx index 302f551fa5890..b298a2363a983 100644 --- a/x-pack/plugins/observability_solution/logs_explorer/public/customizations/custom_dataset_selector.tsx +++ b/x-pack/plugins/observability_solution/logs_explorer/public/customizations/custom_dataset_selector.tsx @@ -6,7 +6,6 @@ */ import { DataViewsPublicPluginStart } from '@kbn/data-views-plugin/public'; -import { DiscoverStart } from '@kbn/discover-plugin/public'; import React from 'react'; import { DatasetSelector } from '../components/dataset_selector'; import { DatasetsProvider, useDatasetsContext } from '../hooks/use_datasets'; @@ -55,7 +54,6 @@ export const CustomDatasetSelector = withProviders(({ logsExplorerControllerStat isLoading: isLoadingDataViews, loadDataViews, reloadDataViews, - selectDataView, searchDataViews, sortDataViews, } = useDataViewsContext(); @@ -77,7 +75,6 @@ export const CustomDatasetSelector = withProviders(({ logsExplorerControllerStat isLoadingIntegrations={isLoadingIntegrations} isLoadingUncategorized={isLoadingUncategorized} isSearchingIntegrations={isSearchingIntegrations} - onDataViewSelection={selectDataView} onDataViewsReload={reloadDataViews} onDataViewsSearch={searchDataViews} onDataViewsSort={sortDataViews} @@ -103,20 +100,18 @@ export default CustomDatasetSelector; export type CustomDatasetSelectorBuilderProps = CustomDatasetSelectorProps & { datasetsClient: IDatasetsClient; dataViews: DataViewsPublicPluginStart; - discover: DiscoverStart; }; function withProviders(Component: React.FunctionComponent) { return function ComponentWithProviders({ datasetsClient, dataViews, - discover, logsExplorerControllerStateService, }: CustomDatasetSelectorBuilderProps) { return ( - + diff --git a/x-pack/plugins/observability_solution/logs_explorer/public/customizations/logs_explorer_profile.tsx b/x-pack/plugins/observability_solution/logs_explorer/public/customizations/logs_explorer_profile.tsx index faa8c8d4a51ee..ac5eb2d6d81c6 100644 --- a/x-pack/plugins/observability_solution/logs_explorer/public/customizations/logs_explorer_profile.tsx +++ b/x-pack/plugins/observability_solution/logs_explorer/public/customizations/logs_explorer_profile.tsx @@ -43,8 +43,7 @@ export const createLogsExplorerProfileCustomizations = ...plugins, ...discoverServices, }; - const { data, dataViews, discover, navigation, unifiedSearch } = pluginsWithOverrides; - + const { data, dataViews, navigation, unifiedSearch } = pluginsWithOverrides; service.send('RECEIVED_STATE_CONTAINER', { discoverStateContainer: stateContainer }); /** @@ -67,7 +66,6 @@ export const createLogsExplorerProfileCustomizations = diff --git a/x-pack/plugins/observability_solution/logs_explorer/public/hooks/use_data_views.tsx b/x-pack/plugins/observability_solution/logs_explorer/public/hooks/use_data_views.tsx index c6283aca4b7f9..a71375ff2ea25 100644 --- a/x-pack/plugins/observability_solution/logs_explorer/public/hooks/use_data_views.tsx +++ b/x-pack/plugins/observability_solution/logs_explorer/public/hooks/use_data_views.tsx @@ -9,13 +9,11 @@ import { useCallback } from 'react'; import createContainer from 'constate'; import { useInterpret, useSelector } from '@xstate/react'; import { DataViewListItem, DataViewsPublicPluginStart } from '@kbn/data-views-plugin/public'; -import { DiscoverStart } from '@kbn/discover-plugin/public'; import { SortOrder } from '../../common/latest'; import { createDataViewsStateMachine } from '../state_machines/data_views'; interface DataViewsContextDeps { dataViewsService: DataViewsPublicPluginStart; - discoverService: DiscoverStart; } export interface SearchDataViewsParams { @@ -28,11 +26,10 @@ export type SearchDataViews = (params: SearchDataViewsParams) => void; export type LoadDataViews = () => void; export type ReloadDataViews = () => void; -const useDataViews = ({ dataViewsService, discoverService }: DataViewsContextDeps) => { +const useDataViews = ({ dataViewsService }: DataViewsContextDeps) => { const dataViewsStateService = useInterpret(() => createDataViewsStateMachine({ dataViews: dataViewsService, - discover: discoverService, }) ); @@ -61,15 +58,6 @@ const useDataViews = ({ dataViewsService, discoverService }: DataViewsContextDep [dataViewsStateService] ); - const selectDataView: DataViewSelectionHandler = useCallback( - (dataView) => - dataViewsStateService.send({ - type: 'SELECT_DATA_VIEW', - dataView, - }), - [dataViewsStateService] - ); - const sortDataViews: SearchDataViews = useCallback( (searchParams) => dataViewsStateService.send({ @@ -96,7 +84,6 @@ const useDataViews = ({ dataViewsService, discoverService }: DataViewsContextDep loadDataViews, reloadDataViews, searchDataViews, - selectDataView, sortDataViews, }; }; diff --git a/x-pack/plugins/observability_solution/logs_explorer/public/hooks/use_dataset_selection.ts b/x-pack/plugins/observability_solution/logs_explorer/public/hooks/use_dataset_selection.ts index 68a14ad838f2c..67610cfa6e85c 100644 --- a/x-pack/plugins/observability_solution/logs_explorer/public/hooks/use_dataset_selection.ts +++ b/x-pack/plugins/observability_solution/logs_explorer/public/hooks/use_dataset_selection.ts @@ -7,7 +7,7 @@ import { useSelector } from '@xstate/react'; import { useCallback } from 'react'; -import { DatasetSelectionChange } from '../../common/dataset_selection'; +import { SelectionChange } from '../../common/dataset_selection'; import { LogsExplorerControllerStateService } from '../state_machines/logs_explorer_controller'; export const useDatasetSelection = ( @@ -17,7 +17,7 @@ export const useDatasetSelection = ( return state.context.datasetSelection; }); - const handleDatasetSelectionChange: DatasetSelectionChange = useCallback( + const handleDatasetSelectionChange: SelectionChange = useCallback( (data) => { logsExplorerControllerStateService.send({ type: 'UPDATE_DATASET_SELECTION', data }); }, diff --git a/x-pack/plugins/observability_solution/logs_explorer/public/hooks/use_esql.tsx b/x-pack/plugins/observability_solution/logs_explorer/public/hooks/use_esql.tsx index d7b4689b7194d..54b7c5e975066 100644 --- a/x-pack/plugins/observability_solution/logs_explorer/public/hooks/use_esql.tsx +++ b/x-pack/plugins/observability_solution/logs_explorer/public/hooks/use_esql.tsx @@ -5,7 +5,11 @@ * 2.0. */ -import { DatasetSelection } from '../../common/dataset_selection'; +import { + DatasetSelection, + DataViewSelection, + isDatasetSelection, +} from '../../common/dataset_selection'; import { useKibanaContextForPlugin } from '../utils/use_kibana'; export interface DiscoverEsqlUrlProps { @@ -19,7 +23,7 @@ export interface UseEsqlResult { } interface EsqlContextDeps { - datasetSelection: DatasetSelection; + datasetSelection: DatasetSelection | DataViewSelection; } export const useEsql = ({ datasetSelection }: EsqlContextDeps): UseEsqlResult => { @@ -31,7 +35,11 @@ export const useEsql = ({ datasetSelection }: EsqlContextDeps): UseEsqlResult => const discoverLinkParams = { query: { - esql: `from ${datasetSelection.selection.dataset.name} | limit 10`, + esql: `from ${ + isDatasetSelection(datasetSelection) + ? datasetSelection.selection.dataset.name + : datasetSelection.selection.dataView.title + } | limit 10`, }, }; diff --git a/x-pack/plugins/observability_solution/logs_explorer/public/state_machines/data_views/src/state_machine.ts b/x-pack/plugins/observability_solution/logs_explorer/public/state_machines/data_views/src/state_machine.ts index 60bc3e21e9ff6..fb13f55a598f8 100644 --- a/x-pack/plugins/observability_solution/logs_explorer/public/state_machines/data_views/src/state_machine.ts +++ b/x-pack/plugins/observability_solution/logs_explorer/public/state_machines/data_views/src/state_machine.ts @@ -5,11 +5,10 @@ * 2.0. */ -import { DataViewListItem, DataViewsPublicPluginStart } from '@kbn/data-views-plugin/public'; +import { DataViewsPublicPluginStart } from '@kbn/data-views-plugin/public'; import { isError } from 'lodash'; import { assign, createMachine } from 'xstate'; -import { DiscoverStart } from '@kbn/discover-plugin/public'; -import { parseDataViewListItem } from '../../../utils/parse_data_view_list_item'; +import { DataViewDescriptor } from '../../../../common/data_views/models/data_view_descriptor'; import { createComparatorByField } from '../../../utils/comparator_by_field'; import { createDefaultContext } from './defaults'; import type { @@ -25,7 +24,7 @@ export const createPureDataViewsStateMachine = ( ) => createMachine( { - /** @xstate-layout N4IgpgJg5mDOIC5QBECGAXVA1AlmA7rAHQCuAdjhejqgDY4BekAxADIDyAgsgPrKcAVTjywBJAKIB1AMoBtAAwBdRKAAOAe1g5q6sipAAPRABYAzKaIB2a6bOWArABoQAT0QBaAJzyi9y8YAmAEYADgcAX3DnNExcAmJadVQISihmCF0wIkoAN3UAayyY7DxCIkTk1IRc9QBjDBxdBUVm-Q0tHT0kQxNjSyJPP3lTIICnV0RLTyJ5eUsQ0ftI6IwS+PKklLI0sAAnXfVdolVaDAAzQ4BbImK4soqtqGqyPPrO5tbu9u1GrtAjBAANgcRFMDmcbgQIPsIQCw0WyxAt1KCU2kGyEFoYGY0nEnAASgBhAASfEEwjEUjkSjamh+un0AOsgN8nksgPsgLGEJMHKIgNCgRC9mMC2siOR6we6JwmOx0nY+IEZKEIgkMk+ajpnUZJk8ISIAQCHK540hpgCBoWgOM8lNEtWd1RyRlcpx4lY4kJyv4qspkk1IG+Ou6AMB8gNfkCoXBEwQ9gtM1GgzZ+qC4qiSMdKI2LogRAgYAARupyLUwNIwKhdrUABapSWEd0Ekkqinq6nKL7a366hBBeT2ez8zncuMJ6ZzEX2VNiywO2I56X5wslssVqs1+vbRuwZgGWCYdBZVBnY+7AAUplmAEpmLvc4WV8XS2Ry5Xq3WG9n4oHg73Q0mEIDXDYwwLMexLUBMEeQQcwLE8II0wCYxARCcMpwXNZ7k2VIADFUBwLEIGYfEPS4XhfXbKk-x7BlAKBaDfACSxbDBM0PACUFPGMBMEURMh1ELeBul3WkOgA-5EFMYUrBsOwOIQdwUK4oYRjGLCnVICgqBoegmAgcT6T+HoEDAlkxnmRZYPcIIhxmSx4Q0zMHweVIjJDKSzNtIgQlmKyx0hZThxCHi+OclZFylNFDO7CT6K83ifCNE1AsQFCglBIIbTtCKsyinC8wxLEPMk0zARtUF7Ds01YN44dAj8OFglFBTNKXGKCxfdcPy3b8CpErV4pMgEBztUEVJjRSzEyzxLOaoJWvY9rosqbYCKIyBSoS0y5q4nLarjZT+j8BZnMiIA */ + /** @xstate-layout N4IgpgJg5mDOIC5QBECGAXVA1AlmA7rAHQCuAdjhejqgDY4BekAxADIDyAgsgPrKcAVTjywBJAKIB1AMoBtAAwBdRKAAOAe1g5q6sipAAPRACYALMaIA2AIwB2awA5bAVgA0IAJ6JrzgMxFfeQdnAE5fFwBfCPc0TFwCYlp1VAhKKGYIXTAiSgA3dQBrbNjsPEIiJJS0hDz1AGMMHF0FRRb9DS0dPSRDRFNneSJnY1tfU3C3T29jEKJ5AdsHa2NnKJiMUoSK5NSydLAAJwP1A6JVWgwAMxOAWyIS+PLK3agasnyGrpa2no7tJu6oCMCDMzjmIzGE3cXgQYwcREcvlCEzWIAeZUSO0gOQgtDAzGk4k4ACUAMIACT4gmEYikciU7U0-10+mBIUWCPkPksK2hiF8jiIDmF8mMQV8EsltlR6K2z2xOFx+Ok7GJAipQhEEhkPzUTK6rMQliWRFsIWNLj5IIcFnGpgcIWcDklUplG0emJSCqVBPErHEpPV-E1tMkupAfwNPWBtntRBC5vkYUtUwQ1iTCNM1hCI3FLul0TR7ox2y9ECIEDAACN1OQ6mBpGBUAc6gALNKywi+kkUjU07X05S-fUAw1piGmxwpmG+EZWZzprOWZcryy+N1xEvy8uVmt1htNlvtvad2DMAywTDobKoS7Xg4ACkC8gAlMxT6XKzvq7WyPXG82bYdsWCThpGo7RogITCkKITyDykwwsY1j+KMljsiE1irsu66Fh+zxpAAYqgOB4hAzDEn6XC8MG-Z0mBI4spBCDjNYRArL4cEIVapguPGLjGJY8hLiuBaFmQ6iVvAPSnoynQQUC3g2kK4zTogAC0lhgsEDjyLYMxYauuHrJuWzkJQ-x0IwkBycygK9CCQmmksvKpsEQwbpsTw7GktlRopIKBCpUKpsYxj+OmopCSJK4hJ5HqfjZw7yUxAVrqh5pOIhJhsZYWY5rYeaunhIHeWWOJ4n5CkOcEpimuhfjLNlIJzpx2a5s6+bxVuWLfnuf4HoBx5QLJyV2WO1iIqaphCcmzXIWx9hIjFRndXKPl7MRpFJXqKX2cCZh1YJzjcam5i2EQ9qqasUQREAA */ context: initialContext, preserveActionOrder: true, predictableActionArguments: true, @@ -125,22 +124,13 @@ export const createPureDataViewsStateMachine = ( export interface DataViewsStateMachineDependencies { initialContext?: DefaultDataViewsContext; dataViews: DataViewsPublicPluginStart; - discover: DiscoverStart; } export const createDataViewsStateMachine = ({ initialContext, dataViews, - discover, }: DataViewsStateMachineDependencies) => createPureDataViewsStateMachine(initialContext).withConfig({ - actions: { - navigateToDiscoverDataView: (_context, event) => { - if (event.type === 'SELECT_DATA_VIEW' && 'dataView' in event) { - discover.locator?.navigate({ dataViewId: event.dataView.id }); - } - }, - }, services: { loadDataViews: (context) => { const searchParams = context.search; @@ -148,16 +138,16 @@ export const createDataViewsStateMachine = ({ ? Promise.resolve(context.cache.get(searchParams)) : dataViews .getIdsWithTitle() - .then((views) => views.map(parseDataViewListItem)) + .then((views) => views.map(DataViewDescriptor.create)) .then((views) => searchDataViews(views, searchParams)); }, }, }); -const searchDataViews = (dataViews: DataViewListItem[], search: DataViewsSearchParams) => { +const searchDataViews = (dataViews: DataViewDescriptor[], search: DataViewsSearchParams) => { const { name, sortOrder } = search; return dataViews .filter((dataView) => Boolean(dataView.name?.includes(name ?? ''))) - .sort(createComparatorByField('name', sortOrder)); + .sort(createComparatorByField('name', sortOrder)); }; diff --git a/x-pack/plugins/observability_solution/logs_explorer/public/state_machines/data_views/src/types.ts b/x-pack/plugins/observability_solution/logs_explorer/public/state_machines/data_views/src/types.ts index a2f369133521c..da065115f4a3a 100644 --- a/x-pack/plugins/observability_solution/logs_explorer/public/state_machines/data_views/src/types.ts +++ b/x-pack/plugins/observability_solution/logs_explorer/public/state_machines/data_views/src/types.ts @@ -5,7 +5,7 @@ * 2.0. */ import { DoneInvokeEvent } from 'xstate'; -import type { DataViewListItem } from '@kbn/data-views-plugin/common'; +import { DataViewDescriptor } from '../../../../common/data_views/models/data_view_descriptor'; import type { IHashedCache } from '../../../../common/hashed_cache'; import { SortOrder } from '../../../../common/latest'; @@ -15,7 +15,7 @@ export interface DataViewsSearchParams { } export interface WithCache { - cache: IHashedCache; + cache: IHashedCache; } export interface WithSearch { @@ -23,8 +23,8 @@ export interface WithSearch { } export interface WithDataViews { - dataViewsSource: DataViewListItem[]; - dataViews: DataViewListItem[]; + dataViewsSource: DataViewDescriptor[]; + dataViews: DataViewDescriptor[]; } export interface WithNullishDataViews { @@ -86,10 +86,6 @@ export type DataViewsEvent = | { type: 'RELOAD_DATA_VIEWS'; } - | { - type: 'SELECT_DATA_VIEW'; - dataView: DataViewListItem; - } | { type: 'SEARCH_DATA_VIEWS'; search: DataViewsSearchParams; @@ -98,4 +94,4 @@ export type DataViewsEvent = type: 'SORT_DATA_VIEWS'; search: DataViewsSearchParams; } - | DoneInvokeEvent; + | DoneInvokeEvent; diff --git a/x-pack/plugins/observability_solution/logs_explorer/public/state_machines/datasets/src/state_machine.ts b/x-pack/plugins/observability_solution/logs_explorer/public/state_machines/datasets/src/state_machine.ts index 083c4e8720490..0f0bad0e6b2f7 100644 --- a/x-pack/plugins/observability_solution/logs_explorer/public/state_machines/datasets/src/state_machine.ts +++ b/x-pack/plugins/observability_solution/logs_explorer/public/state_machines/datasets/src/state_machine.ts @@ -20,7 +20,7 @@ import type { export const createPureDatasetsStateMachine = ( initialContext: DefaultDatasetsContext = createDefaultContext() ) => - /** @xstate-layout N4IgpgJg5mDOIC5QBECGAXVsztgOgFcA7AS1PRNQBsSAvSAYgBkB5AQWQH1k2AVNgMoBRXgIDaABgC6iUAAcA9rBIUFRWSAAeiAGwBmAJx4DAdh0BWPXvMAWAIzn9BgDQgAnogAcdvDYP+dACYdfQk9e08AX0jXNExsXDwqBVQIMigGCDUwPDIANwUAaxy4rBx8ZNT0hHyFAGMMEjVJKRaNRWVVdSQtXUNjM0tre0dDVw8EG09A339PHU8wwMWw6NiMMsTKtKIMsAAnfYV9vDkqDAAzY4BbPFKEipSdqBqiAoaulraejpUm7tA2gQdhBMwkEhMJnMjhseh0Ek8nnGiEC5iM5hBJmm+k8BimwTWIHu5SST3SDGEbAASgBhAAS3D4ghE4mk7SUfzUGiBOhMNjwJnC4Vh8J0eJsyOBnnMxjsFmW5m8JjsnhshOJWzJuwpLCpvEZ-GEom+8g5XW5iAMEiMsL5NnhWMChkCksxeB0NkC4MVCz0nj5OnVGwepNSjEptIZPENLJNIF+5p6QLlEl8ljsCvCEjlC0lXr0sosiNhqokapiRODJO24d1+ujzONbJ+Zv+FuB8LTegz0qzOaR7kQVk8AsFgQM03HJitUQrGvwEDAACMFMQ6mABGBUPs6gALdLzilCan0g2N1kyFudNtJxA2cF4QJ2KbdsKQq0uwfA++zfwihxYgigZzlWiSLiua4bluO77rsh6aLAmDoDkqAXMh+wABR6OCACUDDzng4GrkQ66btue4HqBsBxgmN6An0RimBYVi2A4Th5nCvhyrY8yeOEKoEiB8TVlqUAAGKoCQVCMFSQisBwZ5Ghe7LXlyt7AqCeDgpC0IenCCIDhMXpGNmdhmKiegmIEL56NEFZEAoi7wD084qZyAK9AgAC0OiSj5QbCYkxBkH81B0JAbmJvRkwGAWcr3jYJjWjYGJ2C4X7TFp4LgiqIIToKJgBZsjxVLskV0Z5uL8kEfgpYY-o2BKX5PiZBgYiliWNRIHqzusgUlYuEDlWp0WepK5hOlptjTcs07deWfXFYRy7EaR0EUXBVHDR5QKWamQpWW1VndUErptcY-gGPoyp8osfJFSG2zpBJUkRVe7ntnY3YjsqCJTAYcqovMrpingaIYo4V0pXx912UAA */ + /** @xstate-layout N4IgpgJg5mDOIC5QBECGAXVsztgOgFcA7AS1PRNQBsSAvSAYgBkB5AQWQH1k2AVNgMoBRXgIDaABgC6iUAAcA9rBIUFRWSAAeiAGwBmAJx4DAdh0BWHQCZzAGhABPRAA4AjHnMBfT-bSZsuHhUCqgQZFAMEGpgeGQAbgoA1jF+WDj4waHhCPEKAMYYJGqSUiUaisqq6khauobGZpY29k4IACzOVh7evhhpgZlhRBFgAE6jCqN4clQYAGaTALZ4qQEZIUNQOUQJBVUlZTUVKkXVoNoIrlddEhImbs2OiDZG5lcGEm1mrh9fPSCrdJBDbhBjCNgAJQAwgAJbh8QQicTScpKE5qDQXHQmNp4Ex6Np6Ex2J6XZzmYyuHTUmm0-T-QEDEHDMEsCG8eH8YSiQ7yNFVTGID5GQnElqIK4mPA6NpWK56CQGcxWPTOO4MvprYGhRjg6FwnhcpG8kDHAU1C5UiR4NrmPSuR6tKwSPSUunuvQa-xAwa6tkcw2Inkoo7806Cy46a22+2OxB6VV4-Girw+AGaoEQMAAIwUxDyYAEYFQozyAAtwozYGChJDYZyg8iZKHKuGLYg2rc8HKOinxZdO9001W8Fnc-nC8XSxXhlWGJpYJh0DFUHNl6MABQKiQASgYI7HeaIBaLJfLlYzuBNZrb5zqRlMFmsJKdeh0Nqp7rpXv66yywwAMVQEgqEYCEhFYDgG25JtUVbDF20ua48Fue4HRfZ5FRQ95Pm+X4TG8NMiAULN4BqKs4PRM5agQABaHR+3on8tWIMgTmoOhIEo8073aAxXSpTtrDaAxRLEtp+06ZifWZKBuNvGjnAMXFn37OUjEVZVVU0lU1QI4dLz-LMIHkhDeNlfstJQ8w2h0ZxiWkwJDwnU9pwvb1cFM6iLiJa0CSJDDLgMHRHKM8IgJAriWyoiNXHtZw8VcRUAv7KlXiVXSdO0-TvCAA */ createMachine( { context: initialContext, diff --git a/x-pack/plugins/observability_solution/logs_explorer/public/state_machines/logs_explorer_controller/src/notifications.ts b/x-pack/plugins/observability_solution/logs_explorer/public/state_machines/logs_explorer_controller/src/notifications.ts index f1c6240aaa40e..1a9e287ee50eb 100644 --- a/x-pack/plugins/observability_solution/logs_explorer/public/state_machines/logs_explorer_controller/src/notifications.ts +++ b/x-pack/plugins/observability_solution/logs_explorer/public/state_machines/logs_explorer_controller/src/notifications.ts @@ -22,10 +22,16 @@ export const createDatasetSelectionRestoreFailedNotifier = (toasts: IToasts) => export const createCreateDataViewFailedNotifier = (toasts: IToasts) => () => toasts.addWarning({ - title: i18n.translate('xpack.logsExplorer.datasetSelection.createDataViewFailedToastTitle', { - defaultMessage: "We couldn't create a data view for your selection.", - }), - text: i18n.translate('xpack.logsExplorer.datasetSelection.createDataViewFailedToastMessage', { - defaultMessage: 'We switched to "All log datasets" as the default selection.', - }), + title: i18n.translate( + 'xpack.logsExplorer.datasetSelection.createAdHocDataViewFailedToastTitle', + { + defaultMessage: "We couldn't create a data view for your selection.", + } + ), + text: i18n.translate( + 'xpack.logsExplorer.datasetSelection.createAdHocDataViewFailedToastMessage', + { + defaultMessage: 'We switched to "All log datasets" as the default selection.', + } + ), }); diff --git a/x-pack/plugins/observability_solution/logs_explorer/public/state_machines/logs_explorer_controller/src/services/data_view_service.ts b/x-pack/plugins/observability_solution/logs_explorer/public/state_machines/logs_explorer_controller/src/services/data_view_service.ts index 2d16297eb3d1a..0caa5659ba4b7 100644 --- a/x-pack/plugins/observability_solution/logs_explorer/public/state_machines/logs_explorer_controller/src/services/data_view_service.ts +++ b/x-pack/plugins/observability_solution/logs_explorer/public/state_machines/logs_explorer_controller/src/services/data_view_service.ts @@ -5,10 +5,11 @@ * 2.0. */ +import { DataViewsPublicPluginStart } from '@kbn/data-views-plugin/public'; import { InvokeCreator } from 'xstate'; import { LogsExplorerControllerContext, LogsExplorerControllerEvent } from '../types'; -export const createAndSetDataView = +export const createAdHocDataView = (): InvokeCreator => async (context) => { if (!('discoverStateContainer' in context)) return; @@ -26,3 +27,23 @@ export const createAndSetDataView = */ discoverStateContainer.actions.setDataView(dataView); }; + +export const changeDataView = + ({ + dataViews, + }: { + dataViews: DataViewsPublicPluginStart; + }): InvokeCreator => + async (context) => { + if (!('discoverStateContainer' in context)) return; + const { discoverStateContainer } = context; + + // We need to manually retrieve the data view and force a set and change on the state container + // to guarantee the correct update on the data view selection and avoid a race condition + // when updating the control panels. + const nextDataView = await dataViews.get(context.datasetSelection.toDataviewSpec().id, false); + if (nextDataView.id) { + await discoverStateContainer.actions.onChangeDataView(nextDataView.id); + } + discoverStateContainer.actions.setDataView(nextDataView); + }; diff --git a/x-pack/plugins/observability_solution/logs_explorer/public/state_machines/logs_explorer_controller/src/services/discover_service.ts b/x-pack/plugins/observability_solution/logs_explorer/public/state_machines/logs_explorer_controller/src/services/discover_service.ts index b4644cb635d4a..d67c74097d701 100644 --- a/x-pack/plugins/observability_solution/logs_explorer/public/state_machines/logs_explorer_controller/src/services/discover_service.ts +++ b/x-pack/plugins/observability_solution/logs_explorer/public/state_machines/logs_explorer_controller/src/services/discover_service.ts @@ -5,8 +5,11 @@ * 2.0. */ +import { DiscoverStart } from '@kbn/discover-plugin/public'; import { isEmpty } from 'lodash'; import { ActionFunction, actions, InvokeCallback } from 'xstate'; +import { getDiscoverColumnsWithFallbackFieldsFromDisplayOptions } from '../../../../utils/convert_discover_app_state'; +import { DataViewSelection, isDataViewSelection } from '../../../../../common/dataset_selection'; import { getChartDisplayOptionsFromDiscoverAppState, getDiscoverAppStateFromContext, @@ -106,3 +109,33 @@ export const updateDiscoverAppStateFromContext: ActionFunction< context.discoverStateContainer.appState.update(getDiscoverAppStateFromContext(context)); }; + +export const redirectToDiscoverAction = + ( + discover: DiscoverStart + ): ActionFunction => + (context, event) => { + if (event.type === 'UPDATE_DATASET_SELECTION' && isDataViewSelection(event.data)) { + return redirectToDiscover({ context, datasetSelection: event.data, discover }); + } + }; + +export const redirectToDiscover = ({ + context, + datasetSelection, + discover, +}: { + discover: DiscoverStart; + context: LogsExplorerControllerContext; + datasetSelection: DataViewSelection; +}) => { + return discover.locator?.navigate({ + breakdownField: context.chart.breakdownField ?? undefined, + columns: getDiscoverColumnsWithFallbackFieldsFromDisplayOptions(context), + dataViewSpec: datasetSelection.selection.dataView.toDataviewSpec(), + filters: context.filters, + query: context.query, + refreshInterval: context.refreshInterval, + timeRange: context.time, + }); +}; diff --git a/x-pack/plugins/observability_solution/logs_explorer/public/state_machines/logs_explorer_controller/src/services/selection_service.ts b/x-pack/plugins/observability_solution/logs_explorer/public/state_machines/logs_explorer_controller/src/services/selection_service.ts index caf51c5d015b6..281b446869276 100644 --- a/x-pack/plugins/observability_solution/logs_explorer/public/state_machines/logs_explorer_controller/src/services/selection_service.ts +++ b/x-pack/plugins/observability_solution/logs_explorer/public/state_machines/logs_explorer_controller/src/services/selection_service.ts @@ -5,59 +5,103 @@ * 2.0. */ +import { DiscoverStart } from '@kbn/discover-plugin/public'; import { InvokeCreator } from 'xstate'; import { Dataset } from '../../../../../common/datasets'; -import { SingleDatasetSelection } from '../../../../../common/dataset_selection'; +import { + isDataViewSelection, + isUnresolvedDatasetSelection, + SingleDatasetSelection, + UnresolvedDatasetSelection, +} from '../../../../../common/dataset_selection'; import { IDatasetsClient } from '../../../../services/datasets'; import { LogsExplorerControllerContext, LogsExplorerControllerEvent } from '../types'; +import { redirectToDiscover } from './discover_service'; -interface LogsExplorerControllerUrlStateDependencies { +interface LogsExplorerControllerSelectionServiceDeps { datasetsClient: IDatasetsClient; + discover: DiscoverStart; } -export const validateSelection = +export const initializeSelection = ({ datasetsClient, - }: LogsExplorerControllerUrlStateDependencies): InvokeCreator< + discover, + }: LogsExplorerControllerSelectionServiceDeps): InvokeCreator< LogsExplorerControllerContext, LogsExplorerControllerEvent > => (context) => async (send) => { - const unresolvedIntegrationName = - context.datasetSelection.selection.dataset.parentIntegration?.name; - const unresolvedDatasetName = context.datasetSelection.selection.dataset.name; + /** + * First validation. + * If the selection is a data view which is not of logs type, redirect to Discover. + */ + if ( + isDataViewSelection(context.datasetSelection) && + context.datasetSelection.selection.dataView.isUnknownDataType() + ) { + return redirectToDiscover({ context, datasetSelection: context.datasetSelection, discover }); + } - if (context.datasetSelection.selectionType !== 'unresolved' || !unresolvedIntegrationName) { - return send('LISTEN_TO_CHANGES'); + /** + * Second validation. + * If the selection is a data view, initialize it. + */ + if (isDataViewSelection(context.datasetSelection)) { + return send('INITIALIZE_DATA_VIEW'); } - try { - const { items } = await datasetsClient.findIntegrations({ - nameQuery: unresolvedIntegrationName, - }); + /** + * Third validation. + * If the selection is an unresolved dataset, perform a look up against integrations.. + */ + if (isUnresolvedDatasetSelection(context.datasetSelection)) { + try { + const selection = await lookupUnresolvedDatasetSelection(context.datasetSelection, { + datasetsClient, + }); - // There should only be one matching integration with the given name - // If no integration matches, skip the update and listen for user changes - const installedIntegration = items[0]; - if (!installedIntegration) { - return send('LISTEN_TO_CHANGES'); + if (selection !== null) { + return send({ type: 'INITIALIZE_DATASET', data: selection }); + } + } catch { + return send('DATASET_SELECTION_RESTORE_FAILURE'); } + } - // If no dataset matches the passed name for the retrieved integration, - // skip the update and listen for user changes - const targetDataset = installedIntegration.datasets.find( - (d) => d.name === unresolvedDatasetName - ); - if (!targetDataset) { - return send('LISTEN_TO_CHANGES'); - } + /** + * For any remaining case, initialize the current dataset selection + */ + return send('INITIALIZE_DATASET'); + }; - const dataset = Dataset.create(targetDataset, installedIntegration); - const datasetSelection = SingleDatasetSelection.create(dataset); +const lookupUnresolvedDatasetSelection = async ( + datasetSelection: UnresolvedDatasetSelection, + { datasetsClient }: Pick +) => { + const nameQuery = datasetSelection.selection.dataset.parentIntegration?.name; - send({ type: 'UPDATE_DATASET_SELECTION', data: datasetSelection }); - } catch (error) { - return send('DATASET_SELECTION_RESTORE_FAILURE'); - } - }; + if (nameQuery) { + return null; + } + + const { items } = await datasetsClient.findIntegrations({ nameQuery }); + + // There should only be one matching integration with the given name + // If no integration matches, skip the update and listen for user changes + const installedIntegration = items[0]; + if (!installedIntegration) { + return null; + } + + // If no dataset matches the passed name for the retrieved integration, + // skip the update and listen for user changes + const targetDataset = installedIntegration.datasets.find((d) => d.name === nameQuery); + if (!targetDataset) { + return null; + } + + const dataset = Dataset.create(targetDataset, installedIntegration); + return SingleDatasetSelection.create(dataset); +}; diff --git a/x-pack/plugins/observability_solution/logs_explorer/public/state_machines/logs_explorer_controller/src/state_machine.ts b/x-pack/plugins/observability_solution/logs_explorer/public/state_machines/logs_explorer_controller/src/state_machine.ts index 3c244a73bbd5b..3354fbbcd7a02 100644 --- a/x-pack/plugins/observability_solution/logs_explorer/public/state_machines/logs_explorer_controller/src/state_machine.ts +++ b/x-pack/plugins/observability_solution/logs_explorer/public/state_machines/logs_explorer_controller/src/state_machine.ts @@ -8,8 +8,13 @@ import { IToasts } from '@kbn/core/public'; import { QueryStart } from '@kbn/data-plugin/public'; import { actions, createMachine, interpret, InterpreterFrom, raise } from 'xstate'; +import { LogsExplorerStartDeps } from '../../../types'; import { ControlPanelRT } from '../../../../common/control_panels'; -import { isDatasetSelection } from '../../../../common/dataset_selection'; +import { + AllDatasetSelection, + isDatasetSelection, + isDataViewSelection, +} from '../../../../common/dataset_selection'; import { IDatasetsClient } from '../../../services/datasets'; import { DEFAULT_CONTEXT } from './defaults'; import { @@ -21,14 +26,15 @@ import { subscribeControlGroup, updateControlPanels, } from './services/control_panels'; -import { createAndSetDataView } from './services/data_view_service'; +import { changeDataView, createAdHocDataView } from './services/data_view_service'; import { + redirectToDiscoverAction, subscribeToDiscoverState, updateContextFromDiscoverAppState, updateContextFromDiscoverDataState, updateDiscoverAppStateFromContext, } from './services/discover_service'; -import { validateSelection } from './services/selection_service'; +import { initializeSelection } from './services/selection_service'; import { subscribeToTimefilterService, updateContextFromTimefilter, @@ -43,7 +49,7 @@ import { export const createPureLogsExplorerControllerStateMachine = ( initialContext: LogsExplorerControllerContext ) => - /** @xstate-layout N4IgpgJg5mDOIC5QBkD2UCiAPADgG1QCcxCBhVAOwBdDU88SA6AVwoEt2q2BDPNgL0gBiAEoZSGAJIA1DABEA+gGUAKgEEVGBaQDyAOXWS9GEQG0ADAF1EoHKlhsulGyCyIAtACYAbIwDsAIzmAJwAzH4ArAA0IACeiJ5+AByMwZ6hnokALD6e5kneAL6FMWiYuATEZJQ0dAyEjByOPHz8HFBy3FTc0mxgAO5CEJRgjRQAbqgA1qNl2PhEJOTUtPRMTVy8Au2d3b0DCByTAMZdbJQWlpcudg5OFC5uCAGejObentFxiBHBwYyeLIBX5JfLeX7BCLFUroeaVJY1Vb1MbNLZtCgdLo9PqDEi0Br4LoAMyIAFtGHMKotqis6utOC1thjdtiDkdUKd7pdrkgQLdms5eU9sowAqEkn8IuLIUDvN4YvEEFksqE3hFQaEglkperPNCQJSFlVlrU1g0Noz0VATasAArcChgPCwIYjMaTGYU2FU42Iunmhlo9o2uj2x3Ow4TDlnC5WHm2ewCh5Cjy+YLeJJ+cGfTMS96fBWIJJJAKMULa8wZbzmPyfbxZfWG+E003Ii1BjEhvBhp0uvFERiEqgkwjkpvUrttwOtYN+7sO3uRk4xijcqw3RP3R4JPyqgJZDM14sygJywsIXf-TV+LLBEIFXc5Rveo0I2lmlGbVrCMQSGRaORJCUXRZBEBQ1FtW1lHUTR4z5TdzmTUBhVCdNRX3QEQmSbUknPCJAn8IJawiTxgm1dIoRKA0X2bSd6VRb8IFEcQpFkBQAEUAFUTAATWgjQMDg-ktxTBBMIiRhiyzcUpSzSJz0fRhvACPws0rEsIgietn3KV8WyReivwESBGAgLFYDAKglCdMBjnuRhxi2MyuAxayGDsxChGQIDND0BQVB0bQAAk1D0ABxDAlCEhDBWQxAyN8W8wkCJIfDSIFzzlLJ0KyfIwmVAIklCUIdLhCc5ynBjjIgUzzMstzbPsxy+Gc9oGo8yghE4205AEhRevUJQMBUZQMGQcQVEkfRoruRDtwQB9RXBVCIn3cwQnBc8S1VcEVQ0yIPgCAJSp9N9W0My0TOc7gLKsmyOooBynLOVz7vuIQBrUIaRqG8bSEm-QFDEVQdDEBQADE1EkZBOLEGak3m8FS1COUAmCNS5Wvc80hSYtPFS4rvDvJJNJOvS6IDKrBBq67bva+y2AgBgup6vrPu+0a-oBvR4ZEuLnh8FJCaK6t8drcstrFJSIj24EDs8I6ydoiqLrRK66ru9yGaZsAPo0L7hs5iapr84GArByHodhwT115YS5tE4FwUYFUifMdJlQid2sklnaZfFOWtIV46qPHX130qozqdq7o6bexCWBwVrmSxfZBmGR13WmWYaPKiPVcYmObvq+PKET5PMT2HEl2jLk41thNZti1xEEK4qlKBPIUfVHJQmxtCIVS9MvewopQ9z8PzspqP1djkutYT5gk5eyvWVxQh8UHPBiTJL1dOV-Pp8ummNfpxfl5c1e05rzlELXaw7ZipCW+edMUi9kIwh77xNXPIJITeGkfKy0ayniVnnKen5j6MGOHOMKtAl6wBYOwac1UhBGEkJNNQ3kABaWhdAGBEDoZACgwpEO6uBW0kheYO35nmAEgI0afGVAHXC3xnjlgkrlSUwQmHuzHjCfeECDJHzVjVWB754GoEQY0HWet1AKGkJIDAAB1BQ3UBryBoc3J4LwDwAk1Ojcih5wh-ylNlIqljEj4wopRQRZVJ4iKgWImBcCEE4CQYzZmGi+oEJUEQkhtpQpjSig3eCTdn66JRn4VImRqx+DCLwzIAQzFkRdvuDMqk-D5FvHY6iQjHH+mcYXCRpopEyKXhXLsPZnSukzuyT0YczpOPbCUtx0iPHlxXtUhcEZ2S31jFcMJ9sdGt3RuYN4GRAShG-r-dhKkMzSzvFpXcRNEgh3sadfSRTWnVVcZI9xSDKndLnDUvsG8BxDhHGOCezSdmoOjqU1Y5TOnHMvj08MsAb4rnvhuCJ80VJpKSNqLIoJ+FpBvH-QIEl1rlhJumImaRKJUQoKgCAcAXBNO2WaP5CNRLuBvIREI4QviKgSf4CUwQSYkQPOkMi4DCkflYLs6muK+YvxyH-bwMT8Z-D5fyv4Aj8kOLuR+FlOxU44jZbQl+7hSye01AWdhyoJnYV+DM7UMzUoMtFZHS0s53xnOlaMhAXgJlZCzEHJK-KCgKX3GWFSt57zcvLHqceBTdUF2qsayJPwSKpA+JWBWvwgTanPKhV4CteEkTlCEHIwQdXYr1S42m89GoypGb6hA1ZVQo2UujasmMVJ4QIoENIGZgS5MSImimxS9mps1umsuzVGYrzPs3TN81Ah7hybC3aeQ-CZV+ACJhqUf5BoKDWlWojC4NvbY9LxYAfUAu1DyksCSSb1nMAeNhioso5TyuWIERUSrupFUmr10c52l0em8iVVcBjLsdqeGJwJULig3WRIq-dspVgjcCI8B4p2HzrY89piCn382rBM9MXdg1kX3KS1unxX0oyBO+9U6M8lYtrSykyTy6AvKOSgqmkBIMv3rCkWDQbgQIbDfMlGKQxTZlPGkfhwHIF4fEeBzpi7yO6KlP8UiylbzZLyPjP+R6lLdwVms9jZ6tm4YefhnjRyL4GtNEax+-zHZYQBH8EInxNLrsHfMt2bxKygt+HKP4nxijFCAA */ + /** @xstate-layout N4IgpgJg5mDOIC5QBkD2VYFEAeAHANqgE5hEDCqAdgC5Gr76kB0ArpQJYfXsCG+7AL0gBiAEqYymAJIA1TABEA+gGUAKgEFVmRWQDyAOQ1T9mUQG0ADAF1EoXKljtuVWyGyIATBYtMAbB4AOABYAZgsAdnCg8N8AVliAGhAAT0QARjSPJhCQjyC0iO9YgE4PEOKAXwqktAwcAmJSCho6BmZOJ15+AU4oZTBGAGNnSmFjKVUpdWQpAC1teU11RRkpTAB1SxskEHtHEdd3BCCIv2KwtOK4gO9zpNTjnKYLL3P-Eo9i2JCqmvQsPCEEjkKi0eiMIhMDrcPiCXr9IYjMb6CZTGbzRSLDTKTCqLauPadFw7I6+IJMYoBTIfNIhXzFIJle6IE7FJgBYoWL5BAI3cJeOm-EC1AENYHNMFtSHQrpwyh9AZgYbsKjCLHqHGqFSYZASSYGRTiNS6cSKABi6ikyAAquJ8TtCQcSYgALRpAKxJh5XKc4qc2L0oKJFKeXwhJixXl+sPu87fXxCkX1IFNUGtCFQriynry+Q8ag8GTsMAAd2EECoYEzADdUABrKtJwGNEEtcHtLOwnNQPMFoulhCcWuDfMqyhbe12BxEyiHRAhcLhiwnXzBcIBk7xZkIcLeZ5R4ppUK73wWH7VYX-ZMtiXpjudLu9XuF4tl0h0SEEfMAM2IAFsmCbMVUzbKVMwfbon3zF8ByHVARxGCdrAJacnVAUkAnCJhGV8DIqXCL4rmDB5Yg8LIuS5cI0nXfxQnPP46mbcU03baVO0g3NoNgMBqHLSsa3rRsryYkDJQzGVH04gtuOoQdKGHUcqCQ7Yp32Mc5wQF0yKYGILG+KiQndMj8nCbddx8CwOQselOUXXJYkTYTgNbMT7xhDiey4njhHfYgmC-ahfyIACgJTFy7zYiC5U86SeLkhTEOsSddlQ9TnU02NsL5SkTkiTk6W3AIF2eWJqOKKiPF8d4EwvUKbxYsCJI8296AABR4SgBlgPjOoEhtAKcsKWsa9jouG9rOvwWB4vgxTxyS5CHVS4l0MQAyKXKor8g8eJ+VMkMEACMlnmXXlylCXwT0cxjnOG8TRu7caOq6nyiA-fz8B-f8BpuoaGvuqLHoaiaupmhCx2UlC1JWtw1qPPxrkyQybipDxCr07CohyXDDK5Dw0mu0U-tAgH3MEERxEkWQFikZQ9DkURFHUVrWpUDQtGSx00tWhBIx8fIuUM09KvOAJt1pXJsNCWkCnOmIPEJ69mJJtzswpiRpDkRRJgAWUwM0rS0RndcwTnltndLTzZMpMNifI0kui5xfdcNYmsj1FxOB2aoYon6pVyKyaECAxA16ntakPWDeQI3DX1o0AAlFGMI2ZGmM3oYtnn4nDBc7dPAiLG9-aHgKHl2SpYIj0+eIPHCRWRPC1jwKDyAmAgLzqARJURihCBGGEa1WqxBYlk1bVdTIfV9AzmcNMyKqIxKMk9M+Hbom3Bc2SuDIQmiQ9i4b27-tVrs2472Ku8VZUqD7geh5HzEx9xCe9SkAwzDSFSUsz+fDNd2kpEqpfGolVbcbssgEVynXLkcZKi1UGv7Vygc1YQHbp3buN9KB3zAIPYemhR7YhfjiSe08zAeG-lzGGRw0ikTSH4Iq7pSrY1KGjA6kQfCHhuCEEolk6HwN9krUSEUW6oPQZfTBvdBgAAsOpQCgn2V8PUqxwX6nVZWyDRFnzQRfHgMlJFjiYDIuRCiYIljBnNSGS1f7pSRmyf+VJ6RF1KhvA67xsi7nOLuS4QR6QEwQb9JBIimrkx0Rg6+UjZHylMf2N8b0-IBSCiFRBGjgkPXPuExEhjjHRKkmYixiUrCzzQrDBAdjsKrnyG7M8+MghmS8EwXeVIF48g9A5AJftUnNxCcHcReieIGNvoMEgo55TqAgAneCz5YnKL6kJQJXSRqAwyRIiJ2SRncDGRMqZ0FYkFIhgtSh5s-7FQ+DEXIDs6JsIeJyAIEZAjY18d7Q8R9iaaJ6Ss-pV8slDI2b0cZkzBjTKUb5T8n1ArfXUcI7p6SwmrJ+dg4ZYBRlQABTsxRsF5KzUKcU7mpSJZ3MqhEXIVI3YcjFgdAopxeRcn8IXVeQRXlBJhcstBgwGoAHE6AsFwLAVgHBYXIlRNMOY2g9CGFELoZAigOWSqHkzVqUhcXUPSGETh+MjxFXiJdcIFLS5lE9LyS6NxVwmvxkyxZpMxHstAly1APK+XsH7rg9UKw1jrEUA-Ah8hlVZ3xZkNksRdV7R5LuUIer0guOwqRUlQQ415BiBa6FSzW5ss5dy3lOC8GP3FaoSV0rWrqBMMgZQvq-7+AjBcMMkRIwRrKVGoMgRaFxsZNEeil4FnJqtdoox6b7WZp5RfXoT1JrdQrL1VR8zOldtPt0NuNrJR2odawXAQ75QjtBnBcGSlDlQznrY8o4Z-DBACDtKikYCqUpCMECkvIdrBC+Ne-ISam4putX25dg6UUbqmq9d6iTIUpJnSgntC7WhLoHau79wNnpTX2Tuopi1VL7p5rSBkXoriNuoi8E4dSr2VXZIESyR00NRAVkKSgqAIBwFcFC19EI90lKOFpYqPIIg5QiIXS9Dw8jslyJuai5RqKBBfXdZgbAPkQEY3i5jwmspUW8AUHap5MLix4dkSy5V+R0j3uVdtdGxMgY8oMv1VC-VHDIlhdey4chUSuEdXw24670JyMuMM1lSpUiDKJk+RnorAtLNJlVxwXgMNpJROkcbIzgOiM8LwVVsanqDPXDpQj6Ozv853IL5n5zkndMeaI-JLJVOIogfO7JLqVS858K4+mgPpb80DUCIMprZY0te3wfGaJ7xyM065iASO3qPEEL4Z4RsOx8wHLRc6pPWJQ-il44Y9OVR4UXCIp7nZZB3hLKIR5-CXUm+82FfT9FrJhmZ+eEQsgFFXJ8a9646Ql08KUCpGQ15knGz7Dt06GvTdCSdgZZ3sFOsYG12xmQfA3dPaLB7l1twnGu8uINtCdo8IyIdtJrKAffJ7tkqJ8i8mxLB6h5LFIeFPtKKUfI25-C52iKVRcNLOT+MEY3Qzf3em6NOwioxfytmAoCyWYn+LIzki8adCIVVhbbk5J1t2n2iovGZxjllqbe22ozfAObTH0gRHoctsMbsCi6v62UzV2QHPeGc4ydprPj5Tck+rxdmv+WSeFzQzIdztWwLDGGfIIRxb3oq7Z9cdtyj8hV2+0DH7M0g7AO79IZJPSlDs-SwTanT0UjJGdG7lUdqR+7TNp34GXdfs2VAH9WvkM67N5cCki4VtG-W6b2kNwvTeHwpZfkgR4FVCAA */ createMachine< LogsExplorerControllerContext, LogsExplorerControllerEvent, @@ -58,14 +64,47 @@ export const createPureLogsExplorerControllerStateMachine = ( uninitialized: { on: { RECEIVED_STATE_CONTAINER: { - target: 'initializingDataView', + target: 'initializingSelection', actions: ['storeDiscoverStateContainer'], }, }, }, + initializingSelection: { + invoke: { + src: 'initializeSelection', + }, + on: { + INITIALIZE_DATA_VIEW: 'initializingDataView', + INITIALIZE_DATASET: { + target: 'initializingDataset', + actions: ['storeDatasetSelection'], + }, + DATASET_SELECTION_RESTORE_FAILURE: { + target: 'initializingDataset', + actions: ['storeDefaultSelection', 'notifyDatasetSelectionRestoreFailed'], + }, + }, + }, initializingDataView: { invoke: { - src: 'createDataView', + src: 'changeDataView', + onDone: { + target: 'initializingControlPanels', + actions: ['updateDiscoverAppStateFromContext', 'updateTimefilterFromContext'], + }, + onError: { + target: 'initialized', + actions: [ + 'notifyCreateDataViewFailed', + 'updateDiscoverAppStateFromContext', + 'updateTimefilterFromContext', + ], + }, + }, + }, + initializingDataset: { + invoke: { + src: 'createAdHocDataView', onDone: { target: 'initializingControlPanels', actions: ['updateDiscoverAppStateFromContext', 'updateTimefilterFromContext'], @@ -107,41 +146,43 @@ export const createPureLogsExplorerControllerStateMachine = ( entry: ['resetRows'], states: { datasetSelection: { - initial: 'validatingSelection', + initial: 'idle', states: { - validatingSelection: { - invoke: { - src: 'validateSelection', - }, + idle: { on: { - LISTEN_TO_CHANGES: { - target: 'idle', - }, - UPDATE_DATASET_SELECTION: { - target: 'updatingDataView', - actions: ['storeDatasetSelection'], - }, - DATASET_SELECTION_RESTORE_FAILURE: { - target: 'updatingDataView', - actions: ['notifyDatasetSelectionRestoreFailed'], - }, + UPDATE_DATASET_SELECTION: [ + { + cond: 'isUnknownDataViewDescriptor', + actions: ['redirectToDiscover'], + }, + { + cond: 'isLogsDataViewDescriptor', + target: 'changingDataView', + actions: ['storeDatasetSelection'], + }, + { + target: 'creatingAdHocDataView', + actions: ['storeDatasetSelection'], + }, + ], }, }, - idle: { - on: { - UPDATE_DATASET_SELECTION: { - target: 'updatingDataView', - actions: ['storeDatasetSelection'], + changingDataView: { + invoke: { + src: 'changeDataView', + onDone: { + target: 'idle', + actions: ['notifyDataViewUpdate'], }, - DATASET_SELECTION_RESTORE_FAILURE: { - target: 'updatingDataView', - actions: ['notifyDatasetSelectionRestoreFailed'], + onError: { + target: 'idle', + actions: ['notifyCreateDataViewFailed'], }, }, }, - updatingDataView: { + creatingAdHocDataView: { invoke: { - src: 'createDataView', + src: 'createAdHocDataView', onDone: { target: 'idle', actions: ['notifyDataViewUpdate'], @@ -216,8 +257,11 @@ export const createPureLogsExplorerControllerStateMachine = ( }, { actions: { + storeDefaultSelection: actions.assign((_context) => ({ + datasetSelection: AllDatasetSelection.create(), + })), storeDatasetSelection: actions.assign((_context, event) => - 'data' in event && isDatasetSelection(event.data) + 'data' in event && (isDatasetSelection(event.data) || isDataViewSelection(event.data)) ? { datasetSelection: event.data, } @@ -257,12 +301,25 @@ export const createPureLogsExplorerControllerStateMachine = ( controlGroupAPIExists: (_context, event) => { return 'controlGroupAPI' in event && event.controlGroupAPI != null; }, + isLogsDataViewDescriptor: (_context, event) => { + if (event.type === 'UPDATE_DATASET_SELECTION' && isDataViewSelection(event.data)) { + return event.data.selection.dataView.isLogsDataType(); + } + return false; + }, + isUnknownDataViewDescriptor: (_context, event) => { + if (event.type === 'UPDATE_DATASET_SELECTION' && isDataViewSelection(event.data)) { + return event.data.selection.dataView.isUnknownDataType(); + } + return false; + }, }, } ); export interface LogsExplorerControllerStateMachineDependencies { datasetsClient: IDatasetsClient; + plugins: Pick; initialContext?: LogsExplorerControllerContext; query: QueryStart; toasts: IToasts; @@ -270,6 +327,7 @@ export interface LogsExplorerControllerStateMachineDependencies { export const createLogsExplorerControllerStateMachine = ({ datasetsClient, + plugins: { dataViews, discover }, initialContext = DEFAULT_CONTEXT, query, toasts, @@ -278,14 +336,16 @@ export const createLogsExplorerControllerStateMachine = ({ actions: { notifyCreateDataViewFailed: createCreateDataViewFailedNotifier(toasts), notifyDatasetSelectionRestoreFailed: createDatasetSelectionRestoreFailedNotifier(toasts), + redirectToDiscover: redirectToDiscoverAction(discover), updateTimefilterFromContext: updateTimefilterFromContext(query), }, services: { - createDataView: createAndSetDataView(), + changeDataView: changeDataView({ dataViews }), + createAdHocDataView: createAdHocDataView(), initializeControlPanels: initializeControlPanels(), + initializeSelection: initializeSelection({ datasetsClient, discover }), subscribeControlGroup: subscribeControlGroup(), updateControlPanels: updateControlPanels(), - validateSelection: validateSelection({ datasetsClient }), discoverStateService: subscribeToDiscoverState(), timefilterService: subscribeToTimefilterService(query), }, diff --git a/x-pack/plugins/observability_solution/logs_explorer/public/state_machines/logs_explorer_controller/src/types.ts b/x-pack/plugins/observability_solution/logs_explorer/public/state_machines/logs_explorer_controller/src/types.ts index 5e7d617a1cd4e..a72b1700fce43 100644 --- a/x-pack/plugins/observability_solution/logs_explorer/public/state_machines/logs_explorer_controller/src/types.ts +++ b/x-pack/plugins/observability_solution/logs_explorer/public/state_machines/logs_explorer_controller/src/types.ts @@ -15,10 +15,15 @@ import type { import { DoneInvokeEvent } from 'xstate'; import type { DataTableRecord } from '@kbn/discover-utils/src/types'; import { ControlPanels, DisplayOptions } from '../../../../common'; -import type { DatasetEncodingError, DatasetSelection } from '../../../../common/dataset_selection'; +import type { + DatasetEncodingError, + DatasetSelection, + DataViewSelection, + SingleDatasetSelection, +} from '../../../../common/dataset_selection'; export interface WithDatasetSelection { - datasetSelection: DatasetSelection; + datasetSelection: DatasetSelection | DataViewSelection; } export interface WithControlPanelGroupAPI { @@ -52,28 +57,40 @@ export type LogsExplorerControllerTypeState = context: WithDatasetSelection & WithControlPanels & WithQueryState & WithDisplayOptions; } | { - value: 'initializingDataView'; - context: WithDatasetSelection & WithControlPanels & WithQueryState & WithDisplayOptions; + value: 'initializingSelection'; + context: WithDatasetSelection & + WithControlPanels & + WithQueryState & + WithDisplayOptions & + WithDataTableRecord & + WithDiscoverStateContainer; } | { - value: 'initializingControlPanels'; - context: WithDatasetSelection & WithControlPanels & WithQueryState & WithDisplayOptions; + value: 'initializingDataset'; + context: WithDatasetSelection & + WithControlPanels & + WithQueryState & + WithDisplayOptions & + WithDiscoverStateContainer; } | { - value: 'initializingStateContainer'; - context: WithDatasetSelection & WithControlPanels & WithQueryState & WithDisplayOptions; + value: 'initializingDataView'; + context: WithDatasetSelection & + WithControlPanels & + WithQueryState & + WithDisplayOptions & + WithDiscoverStateContainer; } | { - value: 'initialized'; + value: 'initializingControlPanels'; context: WithDatasetSelection & WithControlPanels & WithQueryState & WithDisplayOptions & - WithDataTableRecord & WithDiscoverStateContainer; } | { - value: 'initialized.datasetSelection.validatingSelection'; + value: 'initialized'; context: WithDatasetSelection & WithControlPanels & WithQueryState & @@ -91,7 +108,7 @@ export type LogsExplorerControllerTypeState = WithDiscoverStateContainer; } | { - value: 'initialized.datasetSelection.updatingDataView'; + value: 'initialized.datasetSelection.changingDataView'; context: WithDatasetSelection & WithControlPanels & WithQueryState & @@ -100,7 +117,7 @@ export type LogsExplorerControllerTypeState = WithDiscoverStateContainer; } | { - value: 'initialized.datasetSelection.updatingStateContainer'; + value: 'initialized.datasetSelection.creatingAdHocDataView'; context: WithDatasetSelection & WithControlPanels & WithQueryState & @@ -148,14 +165,18 @@ export type LogsExplorerControllerEvent = discoverStateContainer: DiscoverStateContainer; } | { - type: 'LISTEN_TO_CHANGES'; + type: 'DATASET_SELECTION_RESTORE_FAILURE'; } | { - type: 'UPDATE_DATASET_SELECTION'; - data: DatasetSelection; + type: 'INITIALIZE_DATA_VIEW'; } | { - type: 'DATASET_SELECTION_RESTORE_FAILURE'; + type: 'INITIALIZE_DATASET'; + data?: SingleDatasetSelection; + } + | { + type: 'UPDATE_DATASET_SELECTION'; + data: DatasetSelection | DataViewSelection; } | { type: 'INITIALIZE_CONTROL_GROUP_API'; @@ -181,7 +202,7 @@ export type LogsExplorerControllerEvent = type: 'RECEIVE_TIMEFILTER_REFRESH_INTERVAL'; refreshInterval: RefreshInterval; } - | DoneInvokeEvent + | DoneInvokeEvent | DoneInvokeEvent | DoneInvokeEvent | DoneInvokeEvent diff --git a/x-pack/plugins/observability_solution/logs_explorer/public/utils/parse_data_view_list_item.ts b/x-pack/plugins/observability_solution/logs_explorer/public/utils/parse_data_view_list_item.ts deleted file mode 100644 index ecf509c0945d9..0000000000000 --- a/x-pack/plugins/observability_solution/logs_explorer/public/utils/parse_data_view_list_item.ts +++ /dev/null @@ -1,15 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { DataViewListItem } from '@kbn/data-views-plugin/common'; - -export const parseDataViewListItem = (dataViewListItem: DataViewListItem) => { - return { - ...dataViewListItem, - name: dataViewListItem.name ?? dataViewListItem.title, - }; -}; From 98536eba48d63e4b95ddfd1b347c2162e977d319 Mon Sep 17 00:00:00 2001 From: mohamedhamed-ahmed Date: Tue, 13 Feb 2024 12:41:32 +0000 Subject: [PATCH 59/83] [Dataset quality] Implement Summary Panel (#175994) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit closes https://github.com/elastic/kibana/issues/170247 ## 📝 Summary This PR introduces a new state machine for controlling the new Dataset Quality Summary Panel. As part of this work, we had to introduce a new endpoint to fetch and calculate the Estimated Data in last 24h. ## 💡For Reviewers ### State Machine The new state machine introduces 3 parallel states to fetch the values displayed in the summary panel. In case of failures in any of them, a retry mechanism is introduced to try the fetch 1 more time after 5 seconds interval. If the fetch fails again, we display an error toast notification. ![Screenshot 2024-02-08 at 09 01 34](https://github.com/elastic/kibana/assets/11225826/f1db05f7-fd68-41f5-a950-533fd73aec27) ### New Endpoint A new endpoint `GET /internal/dataset_quality/data_streams/estimated_data` has been introduced to calculate the Estimated Data in last 24h. The endpoint first retrieves the doc count and total size in bytes for `logs-*` and uses them to calculate the average size per doc which is then multiplied by the number of total doc in the last 24h to get an estimate of data in last 24h. ## ✅ Testing 1) Navigate to /app/observability-logs-explorer/dataset-quality 2) The summary panel is displayed at the top of the table 3) Filterations shouldn't affect the data displayed as the panel is completely isolated ## 🎥 Demos - Normal Scenario https://github.com/elastic/kibana/assets/11225826/c88c3e73-973e-4dd2-babe-63e2c6ae2dda - Retry On Failures https://github.com/elastic/kibana/assets/11225826/b952963a-5d67-472a-bd69-9cd9e49b0ed1 - Failing Again After Max Retries https://github.com/elastic/kibana/assets/11225826/31cb2e4c-cb90-4490-8bcc-ccb11994f9fa --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> --- .github/CODEOWNERS | 1 + package.json | 1 + packages/kbn-formatters/REASDME.md | 3 + packages/kbn-formatters/index.ts | 9 + packages/kbn-formatters/jest.config.js | 13 + packages/kbn-formatters/kibana.jsonc | 5 + packages/kbn-formatters/package.json | 6 + .../src/bytes_formatter/index.test.ts | 36 +++ .../src/bytes_formatter/index.ts | 19 ++ packages/kbn-formatters/tsconfig.json | 17 ++ tsconfig.base.json | 2 + .../dataset_quality/common/api_types.ts | 10 + .../common/data_streams_stats/types.ts | 5 + .../dataset_quality/common/translations.ts | 74 ++++++ .../public/components/common/index.ts | 1 + .../public/components/common/types.ts | 9 + .../dataset_quality/dataset_quality.tsx | 28 +- .../summary_panel/datasets_activity.tsx | 30 +++ .../datasets_quality_indicators.tsx | 110 ++++++++ .../summary_panel/estimated_data.tsx | 29 +++ .../last_day_data_placeholder.tsx | 58 +++++ .../summary_panel/summary_panel.tsx | 27 ++ .../components/quality_indicator/helpers.ts | 26 ++ .../components/quality_indicator/index.ts | 1 + .../quality_indicator/indicator.tsx | 5 +- .../percentage_indicator.tsx | 12 +- .../public/controller/create_controller.ts | 11 +- .../dataset_quality/public/hooks/index.ts | 1 + .../public/hooks/use_summary_panel.tsx | 82 ++++++ .../plugins/dataset_quality/public/plugin.tsx | 8 +- .../data_streams_stats_client.ts | 30 +++ .../services/data_streams_stats/types.ts | 5 + .../state_machines/summary_panel/index.ts | 8 + .../summary_panel/src/defaults.ts | 29 +++ .../state_machines/summary_panel/src/index.ts | 11 + .../summary_panel/src/notifications.ts | 37 +++ .../summary_panel/src/state_machine.ts | 241 ++++++++++++++++++ .../state_machines/summary_panel/src/types.ts | 96 +++++++ .../public/utils/filter_inactive_datasets.ts | 13 +- .../routes/data_streams/get_degraded_docs.ts | 4 +- .../get_estimated_data_in_bytes/index.ts | 33 +++ .../server/routes/data_streams/routes.ts | 31 ++- .../dataset_quality/server/services/index.ts | 1 + .../server/services/index_stats.ts | 63 +++++ x-pack/plugins/dataset_quality/tsconfig.json | 3 + .../tests/data_streams/estimated_data.spec.ts | 83 ++++++ yarn.lock | 4 + 47 files changed, 1295 insertions(+), 36 deletions(-) create mode 100644 packages/kbn-formatters/REASDME.md create mode 100644 packages/kbn-formatters/index.ts create mode 100644 packages/kbn-formatters/jest.config.js create mode 100644 packages/kbn-formatters/kibana.jsonc create mode 100644 packages/kbn-formatters/package.json create mode 100644 packages/kbn-formatters/src/bytes_formatter/index.test.ts create mode 100644 packages/kbn-formatters/src/bytes_formatter/index.ts create mode 100644 packages/kbn-formatters/tsconfig.json create mode 100644 x-pack/plugins/dataset_quality/public/components/common/types.ts create mode 100644 x-pack/plugins/dataset_quality/public/components/dataset_quality/summary_panel/datasets_activity.tsx create mode 100644 x-pack/plugins/dataset_quality/public/components/dataset_quality/summary_panel/datasets_quality_indicators.tsx create mode 100644 x-pack/plugins/dataset_quality/public/components/dataset_quality/summary_panel/estimated_data.tsx create mode 100644 x-pack/plugins/dataset_quality/public/components/dataset_quality/summary_panel/last_day_data_placeholder.tsx create mode 100644 x-pack/plugins/dataset_quality/public/components/dataset_quality/summary_panel/summary_panel.tsx create mode 100644 x-pack/plugins/dataset_quality/public/components/quality_indicator/helpers.ts create mode 100644 x-pack/plugins/dataset_quality/public/hooks/use_summary_panel.tsx create mode 100644 x-pack/plugins/dataset_quality/public/state_machines/summary_panel/index.ts create mode 100644 x-pack/plugins/dataset_quality/public/state_machines/summary_panel/src/defaults.ts create mode 100644 x-pack/plugins/dataset_quality/public/state_machines/summary_panel/src/index.ts create mode 100644 x-pack/plugins/dataset_quality/public/state_machines/summary_panel/src/notifications.ts create mode 100644 x-pack/plugins/dataset_quality/public/state_machines/summary_panel/src/state_machine.ts create mode 100644 x-pack/plugins/dataset_quality/public/state_machines/summary_panel/src/types.ts create mode 100644 x-pack/plugins/dataset_quality/server/routes/data_streams/get_estimated_data_in_bytes/index.ts create mode 100644 x-pack/plugins/dataset_quality/server/services/index_stats.ts create mode 100644 x-pack/test/dataset_quality_api_integration/tests/data_streams/estimated_data.spec.ts diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 3566a855a765f..ab54d20bb2459 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -427,6 +427,7 @@ packages/kbn-find-used-node-modules @elastic/kibana-operations x-pack/plugins/fleet @elastic/fleet packages/kbn-flot-charts @elastic/kibana-operations x-pack/test/ui_capabilities/common/plugins/foo_plugin @elastic/kibana-security +packages/kbn-formatters @elastic/obs-ux-logs-team src/plugins/ftr_apis @elastic/kibana-core packages/kbn-ftr-common-functional-services @elastic/kibana-operations @elastic/appex-qa packages/kbn-ftr-common-functional-ui-services @elastic/appex-qa diff --git a/package.json b/package.json index 191de4ea372fd..08cdcc918af57 100644 --- a/package.json +++ b/package.json @@ -463,6 +463,7 @@ "@kbn/fleet-plugin": "link:x-pack/plugins/fleet", "@kbn/flot-charts": "link:packages/kbn-flot-charts", "@kbn/foo-plugin": "link:x-pack/test/ui_capabilities/common/plugins/foo_plugin", + "@kbn/formatters": "link:packages/kbn-formatters", "@kbn/ftr-apis-plugin": "link:src/plugins/ftr_apis", "@kbn/functional-with-es-ssl-cases-test-plugin": "link:x-pack/test/functional_with_es_ssl/plugins/cases", "@kbn/gen-ai-streaming-response-example-plugin": "link:x-pack/examples/gen_ai_streaming_response_example", diff --git a/packages/kbn-formatters/REASDME.md b/packages/kbn-formatters/REASDME.md new file mode 100644 index 0000000000000..dba100fa2e3a8 --- /dev/null +++ b/packages/kbn-formatters/REASDME.md @@ -0,0 +1,3 @@ +# @kbn/formatters + +Utilities for formatting common fields and values. diff --git a/packages/kbn-formatters/index.ts b/packages/kbn-formatters/index.ts new file mode 100644 index 0000000000000..dcfbdf2d39eac --- /dev/null +++ b/packages/kbn-formatters/index.ts @@ -0,0 +1,9 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +export { formatBytes } from './src/bytes_formatter'; diff --git a/packages/kbn-formatters/jest.config.js b/packages/kbn-formatters/jest.config.js new file mode 100644 index 0000000000000..9b27b305e56b7 --- /dev/null +++ b/packages/kbn-formatters/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-formatters'], +}; diff --git a/packages/kbn-formatters/kibana.jsonc b/packages/kbn-formatters/kibana.jsonc new file mode 100644 index 0000000000000..67f2247125ee3 --- /dev/null +++ b/packages/kbn-formatters/kibana.jsonc @@ -0,0 +1,5 @@ +{ + "type": "shared-common", + "id": "@kbn/formatters", + "owner": "@elastic/obs-ux-logs-team" +} diff --git a/packages/kbn-formatters/package.json b/packages/kbn-formatters/package.json new file mode 100644 index 0000000000000..d1746ae3bbd6c --- /dev/null +++ b/packages/kbn-formatters/package.json @@ -0,0 +1,6 @@ +{ + "name": "@kbn/formatters", + "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-formatters/src/bytes_formatter/index.test.ts b/packages/kbn-formatters/src/bytes_formatter/index.test.ts new file mode 100644 index 0000000000000..a53891aefe808 --- /dev/null +++ b/packages/kbn-formatters/src/bytes_formatter/index.test.ts @@ -0,0 +1,36 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { formatBytes } from '.'; + +describe('BytesFormatter', () => { + it('should format bytes correctly', () => { + const result = formatBytes(1000); + expect(result).toBe('1000 Bytes'); + }); + + it('should format bytes correctly if 0 is sent', () => { + const result = formatBytes(0); + expect(result).toBe('0 Bytes'); + }); + + it('should format bytes correctly into KB', () => { + const result = formatBytes(10000); + expect(result).toBe('10 KB'); + }); + + it('should format bytes correctly into MB', () => { + const result = formatBytes(1048576); + expect(result).toBe('1 MB'); + }); + + it('should format bytes correctly with decimals', () => { + const result = formatBytes(10000, 3); + expect(result).toBe('9.766 KB'); + }); +}); diff --git a/packages/kbn-formatters/src/bytes_formatter/index.ts b/packages/kbn-formatters/src/bytes_formatter/index.ts new file mode 100644 index 0000000000000..4554d55bbaa4b --- /dev/null +++ b/packages/kbn-formatters/src/bytes_formatter/index.ts @@ -0,0 +1,19 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +export const formatBytes = (bytes: number, decimals = 0) => { + if (bytes === 0) return '0 Bytes'; + + const k = 1024; + const dm = decimals < 0 ? 0 : decimals; + const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']; + + const i = Math.floor(Math.log(bytes) / Math.log(k)); + + return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i]; +}; diff --git a/packages/kbn-formatters/tsconfig.json b/packages/kbn-formatters/tsconfig.json new file mode 100644 index 0000000000000..21fe7cbde89a0 --- /dev/null +++ b/packages/kbn-formatters/tsconfig.json @@ -0,0 +1,17 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "outDir": "target/types", + "types": [ + "jest", + "node", + ] + }, + "include": [ + "**/*.ts", + ], + "exclude": [ + "target/**/*" + ], + "kbn_references": [] +} diff --git a/tsconfig.base.json b/tsconfig.base.json index e496444be723e..e9775cfa9ba6f 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -848,6 +848,8 @@ "@kbn/flot-charts/*": ["packages/kbn-flot-charts/*"], "@kbn/foo-plugin": ["x-pack/test/ui_capabilities/common/plugins/foo_plugin"], "@kbn/foo-plugin/*": ["x-pack/test/ui_capabilities/common/plugins/foo_plugin/*"], + "@kbn/formatters": ["packages/kbn-formatters"], + "@kbn/formatters/*": ["packages/kbn-formatters/*"], "@kbn/ftr-apis-plugin": ["src/plugins/ftr_apis"], "@kbn/ftr-apis-plugin/*": ["src/plugins/ftr_apis/*"], "@kbn/ftr-common-functional-services": ["packages/kbn-ftr-common-functional-services"], diff --git a/x-pack/plugins/dataset_quality/common/api_types.ts b/x-pack/plugins/dataset_quality/common/api_types.ts index 0c88b26839eae..ec1dd1fd4bf28 100644 --- a/x-pack/plugins/dataset_quality/common/api_types.ts +++ b/x-pack/plugins/dataset_quality/common/api_types.ts @@ -79,3 +79,13 @@ export const getDataStreamsDegradedDocsStatsResponseRt = rt.exact( ); export const getDataStreamsDetailsResponseRt = rt.exact(dataStreamDetailsRt); + +export const dataStreamsEstimatedDataInBytesRT = rt.type({ + estimatedDataInBytes: rt.number, +}); + +export type DataStreamsEstimatedDataInBytes = rt.TypeOf; + +export const getDataStreamsEstimatedDataInBytesResponseRt = rt.exact( + dataStreamsEstimatedDataInBytesRT +); diff --git a/x-pack/plugins/dataset_quality/common/data_streams_stats/types.ts b/x-pack/plugins/dataset_quality/common/data_streams_stats/types.ts index d213e5e021292..9e7ab043b7619 100644 --- a/x-pack/plugins/dataset_quality/common/data_streams_stats/types.ts +++ b/x-pack/plugins/dataset_quality/common/data_streams_stats/types.ts @@ -32,5 +32,10 @@ export type GetDataStreamDetailsParams = export type GetDataStreamDetailsResponse = APIReturnType<`GET /internal/dataset_quality/data_streams/{dataStream}/details`>; +export type GetDataStreamsEstimatedDataInBytesParams = + APIClientRequestParamsOf<`GET /internal/dataset_quality/data_streams/estimated_data`>['params']; +export type GetDataStreamsEstimatedDataInBytesResponse = + APIReturnType<`GET /internal/dataset_quality/data_streams/estimated_data`>; + export type { DataStreamStat } from './data_stream_stat'; export type { DataStreamDetails } from '../api_types'; diff --git a/x-pack/plugins/dataset_quality/common/translations.ts b/x-pack/plugins/dataset_quality/common/translations.ts index a049dc67a1b9a..bd63f586f551a 100644 --- a/x-pack/plugins/dataset_quality/common/translations.ts +++ b/x-pack/plugins/dataset_quality/common/translations.ts @@ -84,6 +84,80 @@ export const flyoutIntegrationNameText = i18n.translate( } ); +/* +Summary Panel +*/ + +export const summaryPanelLast24hText = i18n.translate( + 'xpack.datasetQuality.summaryPanelLast24hText', + { + defaultMessage: 'Last 24h', + } +); + +export const summaryPanelQualityText = i18n.translate( + 'xpack.datasetQuality.summaryPanelQualityText', + { + defaultMessage: 'Datasets Quality', + } +); + +export const summaryPanelQualityTooltipText = i18n.translate( + 'xpack.datasetQuality.summaryPanelQualityTooltipText', + { + defaultMessage: 'Quality is based on the percentage of degraded docs in a dataset.', + } +); + +export const summaryPanelQualityPoorText = i18n.translate( + 'xpack.datasetQuality.summaryPanelQualityPoorText', + { + defaultMessage: 'Poor', + } +); + +export const summaryPanelQualityDegradedText = i18n.translate( + 'xpack.datasetQuality.summaryPanelQualityDegradedText', + { + defaultMessage: 'Degraded', + } +); + +export const summaryPanelQualityGoodText = i18n.translate( + 'xpack.datasetQuality.summaryPanelQualityGoodText', + { + defaultMessage: 'Good', + } +); + +export const summaryPanelDatasetsActivityText = i18n.translate( + 'xpack.datasetQuality.summaryPanelDatasetsActivityText', + { + defaultMessage: 'Active Datasets', + } +); + +export const summaryPanelDatasetsActivityTooltipText = i18n.translate( + 'xpack.datasetQuality.summaryPanelDatasetsActivityTooltipText', + { + defaultMessage: 'The number of datasets with activity in the last 24 hours.', + } +); + +export const summaryPanelEstimatedDataText = i18n.translate( + 'xpack.datasetQuality.summaryPanelEstimatedDataText', + { + defaultMessage: 'Estimated Data', + } +); + +export const summaryPanelEstimatedDataTooltipText = i18n.translate( + 'xpack.datasetQuality.summaryPanelEstimatedDataTooltipText', + { + defaultMessage: 'The approximate amount of data stored in the last 24 hours.', + } +); + export const inactiveDatasetsLabel = i18n.translate('xpack.datasetQuality.inactiveDatasetsLabel', { defaultMessage: 'Show inactive datasets', }); diff --git a/x-pack/plugins/dataset_quality/public/components/common/index.ts b/x-pack/plugins/dataset_quality/public/components/common/index.ts index 36c9d7f744a58..ceae5f1ed44d9 100644 --- a/x-pack/plugins/dataset_quality/public/components/common/index.ts +++ b/x-pack/plugins/dataset_quality/public/components/common/index.ts @@ -6,3 +6,4 @@ */ export * from './integration_icon'; +export * from './types'; diff --git a/x-pack/plugins/dataset_quality/public/components/common/types.ts b/x-pack/plugins/dataset_quality/public/components/common/types.ts new file mode 100644 index 0000000000000..3744e041a2468 --- /dev/null +++ b/x-pack/plugins/dataset_quality/public/components/common/types.ts @@ -0,0 +1,9 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export type QualityIndicators = 'good' | 'poor' | 'degraded'; +export type InfoIndicators = 'success' | 'danger' | 'warning'; diff --git a/x-pack/plugins/dataset_quality/public/components/dataset_quality/dataset_quality.tsx b/x-pack/plugins/dataset_quality/public/components/dataset_quality/dataset_quality.tsx index 40b07a1e1aa7e..9dcda595edf00 100644 --- a/x-pack/plugins/dataset_quality/public/components/dataset_quality/dataset_quality.tsx +++ b/x-pack/plugins/dataset_quality/public/components/dataset_quality/dataset_quality.tsx @@ -12,6 +12,7 @@ import { DatasetQualityContext, DatasetQualityContextValue } from './context'; import { useKibanaContextForPluginProvider } from '../../utils'; import { DatasetQualityStartDeps } from '../../types'; import { DatasetQualityController } from '../../controller'; +import { IDataStreamsStatsClient } from '../../services/data_streams_stats'; export interface DatasetQualityProps { controller: DatasetQualityController; @@ -20,10 +21,16 @@ export interface DatasetQualityProps { export interface CreateDatasetQualityArgs { core: CoreStart; plugins: DatasetQualityStartDeps; + dataStreamStatsClient: IDataStreamsStatsClient; } -export const createDatasetQuality = ({ core, plugins }: CreateDatasetQualityArgs) => { +export const createDatasetQuality = ({ + core, + plugins, + dataStreamStatsClient, +}: CreateDatasetQualityArgs) => { return ({ controller }: DatasetQualityProps) => { + const SummaryPanelProvider = dynamic(() => import('../../hooks/use_summary_panel')); const KibanaContextProviderForPlugin = useKibanaContextForPluginProvider(core, plugins); const datasetQualityProviderValue: DatasetQualityContextValue = useMemo( @@ -34,17 +41,23 @@ export const createDatasetQuality = ({ core, plugins }: CreateDatasetQualityArgs ); return ( - - - - - + + + + + + + ); }; }; const Header = dynamic(() => import('./header')); const Table = dynamic(() => import('./table')); +const SummaryPanel = dynamic(() => import('./summary_panel/summary_panel')); function DatasetQuality() { return ( @@ -52,6 +65,9 @@ function DatasetQuality() {
+ + + diff --git a/x-pack/plugins/dataset_quality/public/components/dataset_quality/summary_panel/datasets_activity.tsx b/x-pack/plugins/dataset_quality/public/components/dataset_quality/summary_panel/datasets_activity.tsx new file mode 100644 index 0000000000000..f269c1d4ed046 --- /dev/null +++ b/x-pack/plugins/dataset_quality/public/components/dataset_quality/summary_panel/datasets_activity.tsx @@ -0,0 +1,30 @@ +/* + * 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 { useSummaryPanelContext } from '../../../hooks'; + +import { + summaryPanelDatasetsActivityText, + summaryPanelDatasetsActivityTooltipText, + tableSummaryOfText, +} from '../../../../common/translations'; +import { LastDayDataPlaceholder } from './last_day_data_placeholder'; + +export function DatasetsActivity() { + const { datasetsActivity, isDatasetsActivityLoading } = useSummaryPanelContext(); + const text = `${datasetsActivity.active} ${tableSummaryOfText} ${datasetsActivity.total}`; + + return ( + + ); +} diff --git a/x-pack/plugins/dataset_quality/public/components/dataset_quality/summary_panel/datasets_quality_indicators.tsx b/x-pack/plugins/dataset_quality/public/components/dataset_quality/summary_panel/datasets_quality_indicators.tsx new file mode 100644 index 0000000000000..999f260bde3c9 --- /dev/null +++ b/x-pack/plugins/dataset_quality/public/components/dataset_quality/summary_panel/datasets_quality_indicators.tsx @@ -0,0 +1,110 @@ +/* + * 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 { euiThemeVars } from '@kbn/ui-theme'; +import { css } from '@emotion/react'; + +import { + EuiFlexGroup, + EuiPanel, + EuiFlexItem, + EuiTitle, + EuiText, + EuiHealth, + EuiIconTip, + EuiSkeletonTitle, +} from '@elastic/eui'; +import { useSummaryPanelContext } from '../../../hooks'; +import { + summaryPanelQualityDegradedText, + summaryPanelQualityGoodText, + summaryPanelQualityPoorText, + summaryPanelQualityText, + summaryPanelQualityTooltipText, +} from '../../../../common/translations'; +import { mapPercentagesToQualityCounts } from '../../quality_indicator'; +import { InfoIndicators } from '../../common'; + +export function DatasetsQualityIndicators() { + const { datasetsQuality, isDatasetsQualityLoading } = useSummaryPanelContext(); + const qualityCounts = mapPercentagesToQualityCounts(datasetsQuality.percentages); + + return ( + + + + + {summaryPanelQualityText} + + + + + + + + + + + + + + + ); +} + +const QualityIndicator = ({ + value, + quality, + description, + isLoading, +}: { + value: number; + quality: InfoIndicators; + description: string; + isLoading: boolean; +}) => { + return ( + + {isLoading ? ( + + ) : ( + +

+ + {value || 0} + +

+
+ )} + +
{description}
+
+
+ ); +}; + +const verticalRule = css` + width: 1px; + height: 63px; + background-color: ${euiThemeVars.euiColorLightShade}; +`; diff --git a/x-pack/plugins/dataset_quality/public/components/dataset_quality/summary_panel/estimated_data.tsx b/x-pack/plugins/dataset_quality/public/components/dataset_quality/summary_panel/estimated_data.tsx new file mode 100644 index 0000000000000..803cd6ab50a6a --- /dev/null +++ b/x-pack/plugins/dataset_quality/public/components/dataset_quality/summary_panel/estimated_data.tsx @@ -0,0 +1,29 @@ +/* + * 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 { formatBytes } from '@kbn/formatters'; +import { useSummaryPanelContext } from '../../../hooks'; +import { + summaryPanelEstimatedDataText, + summaryPanelEstimatedDataTooltipText, +} from '../../../../common/translations'; +import { LastDayDataPlaceholder } from './last_day_data_placeholder'; + +export function EstimatedData() { + const { estimatedData, isEstimatedDataLoading } = useSummaryPanelContext(); + + return ( + + ); +} diff --git a/x-pack/plugins/dataset_quality/public/components/dataset_quality/summary_panel/last_day_data_placeholder.tsx b/x-pack/plugins/dataset_quality/public/components/dataset_quality/summary_panel/last_day_data_placeholder.tsx new file mode 100644 index 0000000000000..ecdd8a7d01dc2 --- /dev/null +++ b/x-pack/plugins/dataset_quality/public/components/dataset_quality/summary_panel/last_day_data_placeholder.tsx @@ -0,0 +1,58 @@ +/* + * 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 { + EuiFlexGroup, + EuiPanel, + EuiFlexItem, + EuiTitle, + EuiText, + EuiIconTip, + EuiSkeletonTitle, +} from '@elastic/eui'; +import { summaryPanelLast24hText } from '../../../../common/translations'; + +interface LastDayDataPlaceholderParams { + title: string; + tooltip: string; + value: string | number; + isLoading: boolean; +} + +export function LastDayDataPlaceholder({ + title, + tooltip, + value, + isLoading, +}: LastDayDataPlaceholderParams) { + return ( + + + + + {title} + + + + + + {summaryPanelLast24hText} + + + {isLoading ? ( + + ) : ( + +

{value}

+
+ )} +
+
+ ); +} diff --git a/x-pack/plugins/dataset_quality/public/components/dataset_quality/summary_panel/summary_panel.tsx b/x-pack/plugins/dataset_quality/public/components/dataset_quality/summary_panel/summary_panel.tsx new file mode 100644 index 0000000000000..f1d1e4624e172 --- /dev/null +++ b/x-pack/plugins/dataset_quality/public/components/dataset_quality/summary_panel/summary_panel.tsx @@ -0,0 +1,27 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; + +import { EuiFlexGroup } from '@elastic/eui'; +import { DatasetsQualityIndicators } from './datasets_quality_indicators'; +import { DatasetsActivity } from './datasets_activity'; +import { EstimatedData } from './estimated_data'; + +// Allow for lazy loading +// eslint-disable-next-line import/no-default-export +export default function SummaryPanel() { + return ( + + + + + + + + ); +} diff --git a/x-pack/plugins/dataset_quality/public/components/quality_indicator/helpers.ts b/x-pack/plugins/dataset_quality/public/components/quality_indicator/helpers.ts new file mode 100644 index 0000000000000..33e4046920478 --- /dev/null +++ b/x-pack/plugins/dataset_quality/public/components/quality_indicator/helpers.ts @@ -0,0 +1,26 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { countBy } from 'lodash'; +import { + POOR_QUALITY_MINIMUM_PERCENTAGE, + DEGRADED_QUALITY_MINIMUM_PERCENTAGE, +} from '../../../common/constants'; +import { QualityIndicators } from '../common'; + +export const mapPercentageToQuality = (percentage: number): QualityIndicators => { + return percentage > POOR_QUALITY_MINIMUM_PERCENTAGE + ? 'poor' + : percentage > DEGRADED_QUALITY_MINIMUM_PERCENTAGE + ? 'degraded' + : 'good'; +}; + +export const mapPercentagesToQualityCounts = ( + percentages: number[] +): Record => + countBy(percentages.map(mapPercentageToQuality)) as Record; diff --git a/x-pack/plugins/dataset_quality/public/components/quality_indicator/index.ts b/x-pack/plugins/dataset_quality/public/components/quality_indicator/index.ts index 8e2628e49db0b..f895c02988f71 100644 --- a/x-pack/plugins/dataset_quality/public/components/quality_indicator/index.ts +++ b/x-pack/plugins/dataset_quality/public/components/quality_indicator/index.ts @@ -7,3 +7,4 @@ export * from './indicator'; export * from './percentage_indicator'; +export * from './helpers'; diff --git a/x-pack/plugins/dataset_quality/public/components/quality_indicator/indicator.tsx b/x-pack/plugins/dataset_quality/public/components/quality_indicator/indicator.tsx index 8344f046e0210..47570c430c2ad 100644 --- a/x-pack/plugins/dataset_quality/public/components/quality_indicator/indicator.tsx +++ b/x-pack/plugins/dataset_quality/public/components/quality_indicator/indicator.tsx @@ -7,15 +7,16 @@ import { EuiHealth } from '@elastic/eui'; import React, { ReactNode } from 'react'; +import { InfoIndicators, QualityIndicators } from '../common'; export function QualityIndicator({ quality, description, }: { - quality: 'good' | 'degraded' | 'poor'; + quality: QualityIndicators; description: string | ReactNode; }) { - const qualityColors = { + const qualityColors: Record = { poor: 'danger', degraded: 'warning', good: 'success', diff --git a/x-pack/plugins/dataset_quality/public/components/quality_indicator/percentage_indicator.tsx b/x-pack/plugins/dataset_quality/public/components/quality_indicator/percentage_indicator.tsx index ceaf1f43a9338..d40652560e926 100644 --- a/x-pack/plugins/dataset_quality/public/components/quality_indicator/percentage_indicator.tsx +++ b/x-pack/plugins/dataset_quality/public/components/quality_indicator/percentage_indicator.tsx @@ -8,19 +8,11 @@ import { EuiText } from '@elastic/eui'; import { FormattedNumber } from '@kbn/i18n-react'; import React from 'react'; -import { - DEGRADED_QUALITY_MINIMUM_PERCENTAGE, - POOR_QUALITY_MINIMUM_PERCENTAGE, -} from '../../../common/constants'; +import { mapPercentageToQuality } from './helpers'; import { QualityIndicator } from './indicator'; export function QualityPercentageIndicator({ percentage = 0 }: { percentage?: number }) { - const quality = - percentage > POOR_QUALITY_MINIMUM_PERCENTAGE - ? 'poor' - : percentage > DEGRADED_QUALITY_MINIMUM_PERCENTAGE - ? 'degraded' - : 'good'; + const quality = mapPercentageToQuality(percentage); const description = ( diff --git a/x-pack/plugins/dataset_quality/public/controller/create_controller.ts b/x-pack/plugins/dataset_quality/public/controller/create_controller.ts index 280d4092ba327..ab61302fe52f2 100644 --- a/x-pack/plugins/dataset_quality/public/controller/create_controller.ts +++ b/x-pack/plugins/dataset_quality/public/controller/create_controller.ts @@ -10,12 +10,11 @@ import { getDevToolsOptions } from '@kbn/xstate-utils'; import equal from 'fast-deep-equal'; import { distinctUntilChanged, from, map } from 'rxjs'; import { interpret } from 'xstate'; -import { DataStreamsStatsService } from '../services/data_streams_stats'; +import { IDataStreamsStatsClient } from '../services/data_streams_stats'; import { createDatasetQualityControllerStateMachine, DEFAULT_CONTEXT, } from '../state_machines/dataset_quality_controller'; -import { DatasetQualityStartDeps } from '../types'; import { getContextFromPublicState, getPublicStateFromContext } from './public_state'; import { DatasetQualityController, DatasetQualityPublicStateUpdate } from './types'; @@ -23,11 +22,11 @@ type InitialState = DatasetQualityPublicStateUpdate; interface Dependencies { core: CoreStart; - plugins: DatasetQualityStartDeps; + dataStreamStatsClient: IDataStreamsStatsClient; } export const createDatasetQualityControllerFactory = - ({ core }: Dependencies) => + ({ core, dataStreamStatsClient }: Dependencies) => async ({ initialState = DEFAULT_CONTEXT, }: { @@ -35,10 +34,6 @@ export const createDatasetQualityControllerFactory = }): Promise => { const initialContext = getContextFromPublicState(initialState ?? {}); - const dataStreamStatsClient = new DataStreamsStatsService().start({ - http: core.http, - }).client; - const machine = createDatasetQualityControllerStateMachine({ initialContext, toasts: core.notifications.toasts, diff --git a/x-pack/plugins/dataset_quality/public/hooks/index.ts b/x-pack/plugins/dataset_quality/public/hooks/index.ts index fec2164b37f73..b4f130ebd91e5 100644 --- a/x-pack/plugins/dataset_quality/public/hooks/index.ts +++ b/x-pack/plugins/dataset_quality/public/hooks/index.ts @@ -8,3 +8,4 @@ export * from './use_dataset_quality_table'; export * from './use_dataset_quality_flyout'; export * from './use_link_to_logs_explorer'; +export * from './use_summary_panel'; diff --git a/x-pack/plugins/dataset_quality/public/hooks/use_summary_panel.tsx b/x-pack/plugins/dataset_quality/public/hooks/use_summary_panel.tsx new file mode 100644 index 0000000000000..3073addf787ee --- /dev/null +++ b/x-pack/plugins/dataset_quality/public/hooks/use_summary_panel.tsx @@ -0,0 +1,82 @@ +/* + * 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 createContainer from 'constate'; +import { useInterpret, useSelector } from '@xstate/react'; +import { IToasts } from '@kbn/core-notifications-browser'; +import { IDataStreamsStatsClient } from '../services/data_streams_stats'; +import { createDatasetsSummaryPanelStateMachine } from '../state_machines/summary_panel'; + +interface SummaryPanelContextDeps { + dataStreamStatsClient: IDataStreamsStatsClient; + toasts: IToasts; +} + +const useSummaryPanel = ({ dataStreamStatsClient, toasts }: SummaryPanelContextDeps) => { + const summaryPanelStateService = useInterpret(() => + createDatasetsSummaryPanelStateMachine({ + dataStreamStatsClient, + toasts, + }) + ); + + /* + Datasets Quality + */ + const datasetsQuality = useSelector( + summaryPanelStateService, + (state) => state.context.datasetsQuality + ); + const isDatasetsQualityLoading = useSelector( + summaryPanelStateService, + (state) => + state.matches('datasetsQuality.fetching') || state.matches('datasetsQuality.retrying') + ); + + /* + Datasets Activity + */ + const datasetsActivity = useSelector( + summaryPanelStateService, + (state) => state.context.datasetsActivity + ); + const isDatasetsActivityLoading = useSelector( + summaryPanelStateService, + (state) => + state.matches('datasetsActivity.fetching') || state.matches('datasetsActivity.retrying') + ); + + /* + Estimated Data + */ + const estimatedData = useSelector( + summaryPanelStateService, + (state) => state.context.estimatedData + ); + const isEstimatedDataLoading = useSelector( + summaryPanelStateService, + (state) => state.matches('estimatedData.fetching') || state.matches('estimatedData.retrying') + ); + + return { + datasetsQuality, + isDatasetsQualityLoading, + + isEstimatedDataLoading, + estimatedData, + + isDatasetsActivityLoading, + datasetsActivity, + }; +}; + +const [SummaryPanelProvider, useSummaryPanelContext] = createContainer(useSummaryPanel); + +export { useSummaryPanelContext }; + +// eslint-disable-next-line import/no-default-export +export default SummaryPanelProvider; diff --git a/x-pack/plugins/dataset_quality/public/plugin.tsx b/x-pack/plugins/dataset_quality/public/plugin.tsx index 40c71c9faaf87..88ab62eff4703 100644 --- a/x-pack/plugins/dataset_quality/public/plugin.tsx +++ b/x-pack/plugins/dataset_quality/public/plugin.tsx @@ -8,6 +8,7 @@ import { CoreSetup, CoreStart, Plugin, PluginInitializerContext } from '@kbn/core/public'; import { createDatasetQuality } from './components/dataset_quality'; import { createDatasetQualityControllerLazyFactory } from './controller/lazy_create_controller'; +import { DataStreamsStatsService } from './services/data_streams_stats'; import { DatasetQualityPluginSetup, DatasetQualityPluginStart, @@ -25,14 +26,19 @@ export class DatasetQualityPlugin } public start(core: CoreStart, plugins: DatasetQualityStartDeps): DatasetQualityPluginStart { + const dataStreamStatsClient = new DataStreamsStatsService().start({ + http: core.http, + }).client; + const DatasetQuality = createDatasetQuality({ core, plugins, + dataStreamStatsClient, }); const createDatasetQualityController = createDatasetQualityControllerLazyFactory({ core, - plugins, + dataStreamStatsClient, }); return { DatasetQuality, createDatasetQualityController }; diff --git a/x-pack/plugins/dataset_quality/public/services/data_streams_stats/data_streams_stats_client.ts b/x-pack/plugins/dataset_quality/public/services/data_streams_stats/data_streams_stats_client.ts index 28fba012492f4..853e883aa5ee8 100644 --- a/x-pack/plugins/dataset_quality/public/services/data_streams_stats/data_streams_stats_client.ts +++ b/x-pack/plugins/dataset_quality/public/services/data_streams_stats/data_streams_stats_client.ts @@ -12,6 +12,7 @@ import { getDataStreamsDegradedDocsStatsResponseRt, getDataStreamsStatsResponseRt, getDataStreamsDetailsResponseRt, + getDataStreamsEstimatedDataInBytesResponseRt, } from '../../../common/api_types'; import { DEFAULT_DATASET_TYPE } from '../../../common/constants'; import { @@ -23,6 +24,8 @@ import { GetDataStreamsStatsResponse, GetDataStreamDetailsParams, GetDataStreamDetailsResponse, + GetDataStreamsEstimatedDataInBytesParams, + GetDataStreamsEstimatedDataInBytesResponse, } from '../../../common/data_streams_stats'; import { DataStreamDetails } from '../../../common/data_streams_stats'; import { DataStreamStat } from '../../../common/data_streams_stats/data_stream_stat'; @@ -100,4 +103,31 @@ export class DataStreamsStatsClient implements IDataStreamsStatsClient { return dataStreamDetails as DataStreamDetails; } + + public async getDataStreamsEstimatedDataInBytes( + params: GetDataStreamsEstimatedDataInBytesParams + ) { + const response = await this.http + .get( + `/internal/dataset_quality/data_streams/estimated_data`, + { + ...params, + } + ) + .catch((error) => { + throw new GetDataStreamsStatsError( + `Failed to fetch data streams estimated data in bytes": ${error}` + ); + }); + + const dataStreamsEstimatedDataInBytes = decodeOrThrow( + getDataStreamsEstimatedDataInBytesResponseRt, + (message: string) => + new GetDataStreamsStatsError( + `Failed to decode data streams estimated data in bytes response: ${message}"` + ) + )(response); + + return dataStreamsEstimatedDataInBytes; + } } diff --git a/x-pack/plugins/dataset_quality/public/services/data_streams_stats/types.ts b/x-pack/plugins/dataset_quality/public/services/data_streams_stats/types.ts index 0a70dc1d86489..a2627ec4bf222 100644 --- a/x-pack/plugins/dataset_quality/public/services/data_streams_stats/types.ts +++ b/x-pack/plugins/dataset_quality/public/services/data_streams_stats/types.ts @@ -13,6 +13,8 @@ import { GetDataStreamsStatsQuery, GetDataStreamDetailsParams, DataStreamDetails, + GetDataStreamsEstimatedDataInBytesParams, + GetDataStreamsEstimatedDataInBytesResponse, } from '../../../common/data_streams_stats'; export type DataStreamsStatsServiceSetup = void; @@ -31,4 +33,7 @@ export interface IDataStreamsStatsClient { params?: GetDataStreamsDegradedDocsStatsQuery ): Promise; getDataStreamDetails(params: GetDataStreamDetailsParams): Promise; + getDataStreamsEstimatedDataInBytes( + params: GetDataStreamsEstimatedDataInBytesParams + ): Promise; } diff --git a/x-pack/plugins/dataset_quality/public/state_machines/summary_panel/index.ts b/x-pack/plugins/dataset_quality/public/state_machines/summary_panel/index.ts new file mode 100644 index 0000000000000..3b2a320ae181f --- /dev/null +++ b/x-pack/plugins/dataset_quality/public/state_machines/summary_panel/index.ts @@ -0,0 +1,8 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export * from './src'; diff --git a/x-pack/plugins/dataset_quality/public/state_machines/summary_panel/src/defaults.ts b/x-pack/plugins/dataset_quality/public/state_machines/summary_panel/src/defaults.ts new file mode 100644 index 0000000000000..be7327c47a8cb --- /dev/null +++ b/x-pack/plugins/dataset_quality/public/state_machines/summary_panel/src/defaults.ts @@ -0,0 +1,29 @@ +/* + * 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 { DefaultDatasetsSummaryPanelContext } from './types'; + +export const MAX_RETRIES = 1; +export const RETRY_DELAY_IN_MS = 5000; + +export const defaultContext: DefaultDatasetsSummaryPanelContext = { + datasetsQuality: { + percentages: [], + }, + datasetsActivity: { + total: 0, + active: 0, + }, + estimatedData: { + estimatedDataInBytes: 0, + }, + retries: { + datasetsQualityRetries: 0, + datasetsActivityRetries: 0, + estimatedDataRetries: 0, + }, +}; diff --git a/x-pack/plugins/dataset_quality/public/state_machines/summary_panel/src/index.ts b/x-pack/plugins/dataset_quality/public/state_machines/summary_panel/src/index.ts new file mode 100644 index 0000000000000..a28f2651edff3 --- /dev/null +++ b/x-pack/plugins/dataset_quality/public/state_machines/summary_panel/src/index.ts @@ -0,0 +1,11 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export * from './state_machine'; +export * from './types'; +export * from './defaults'; +export * from './notifications'; diff --git a/x-pack/plugins/dataset_quality/public/state_machines/summary_panel/src/notifications.ts b/x-pack/plugins/dataset_quality/public/state_machines/summary_panel/src/notifications.ts new file mode 100644 index 0000000000000..dabd30f02af61 --- /dev/null +++ b/x-pack/plugins/dataset_quality/public/state_machines/summary_panel/src/notifications.ts @@ -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 { IToasts } from '@kbn/core/public'; +import { i18n } from '@kbn/i18n'; + +export const fetchDatasetsQualityFailedNotifier = (toasts: IToasts, error: Error) => { + toasts.addDanger({ + title: i18n.translate('xpack.datasetQuality.fetchDatasetsQualityDetailsFailed', { + defaultMessage: "We couldn't get your datasets quality details. Default values are shown.", + }), + text: error.message, + }); +}; + +export const fetchDatasetsActivityFailedNotifier = (toasts: IToasts, error: Error) => { + toasts.addDanger({ + title: i18n.translate('xpack.datasetQuality.fetchDatasetsActivityFailed', { + defaultMessage: + "We couldn't get your active/inactive datasets details. Default values are shown.", + }), + text: error.message, + }); +}; + +export const fetchDatasetsEstimatedDataFailedNotifier = (toasts: IToasts, error: Error) => { + toasts.addDanger({ + title: i18n.translate('xpack.datasetQuality.fetchDatasetsEstimatedDataFailed', { + defaultMessage: "We couldn't get your datasets estimated data. Default values are shown.", + }), + text: error.message, + }); +}; diff --git a/x-pack/plugins/dataset_quality/public/state_machines/summary_panel/src/state_machine.ts b/x-pack/plugins/dataset_quality/public/state_machines/summary_panel/src/state_machine.ts new file mode 100644 index 0000000000000..187a6155a7bae --- /dev/null +++ b/x-pack/plugins/dataset_quality/public/state_machines/summary_panel/src/state_machine.ts @@ -0,0 +1,241 @@ +/* + * 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 { IToasts } from '@kbn/core/public'; +import { assign, createMachine, DoneInvokeEvent, InterpreterFrom } from 'xstate'; +import { getDefaultTimeRange } from '../../../utils'; +import { filterInactiveDatasets } from '../../../utils/filter_inactive_datasets'; +import { IDataStreamsStatsClient } from '../../../services/data_streams_stats'; +import { defaultContext, MAX_RETRIES, RETRY_DELAY_IN_MS } from './defaults'; +import { + DatasetsActivityDetails, + DatasetsQuality, + DatasetsSummaryPanelContext, + DatasetsSummaryPanelState, + DatasetSummaryPanelEvent, + DefaultDatasetsSummaryPanelContext, + EstimatedDataDetails, + Retries, +} from './types'; +import { + fetchDatasetsEstimatedDataFailedNotifier, + fetchDatasetsActivityFailedNotifier, + fetchDatasetsQualityFailedNotifier, +} from './notifications'; + +export const createPureDatasetsSummaryPanelStateMachine = ( + initialContext: DatasetsSummaryPanelContext +) => + /** @xstate-layout N4IgpgJg5mDOIC5QGUCuBbdBDATgTwAUsA7MAGwDoIsAXLWMG2ARVSzIEsa8KAzRgMYALDsSgBiCAHtSFUQDcpAazAU0mXIRLkqteoxZtO3PoJFiECqQNocZAbQAMAXSfPEoAA5TYXO8Q8QAA9EABZQgCYKAHYAVgBmADZQ6IAOUNjQgE5YgEZYgBoQPEQAWlTcilyUx1q6lNSI0IBfZqL1bHwiUkpqOgYmVnYuHn4aYVEJMBwcKRwKTzJaXjn0NQxOrR7dfoMh41GzSctiRRsafzc3QO9fC5lAkIRI1IoK3IrE1LzonMSikoICJZChZL5NUKOTJfZJZFptEAdTTdHR9fSDIwjUzjcxTGZzBZLGgrHBrJFdbS9PQDQzDExjCYWKznS4uey5dxIEC3PwPLlPSKxCjAxJNRzxVKJLJw1IAxAfUIxdK5XLRaK5Rzg2KpVrtDbIyk7dGwACCAgu8ixDNxkhkqisKnWGgp2zRNLNFqtRyZp2stgcbJcNx8vIC-LCsSi2u+EVyEXiWUc0SlhWKiATlWqwNjwNSqSy8VisV1iP1LtR1IMHo4lvp3rxs3mi2WqydmxRVN2TGrtcOOOOzP9xCuQa5PPuYdAAviitCqWi4oSsUThdCcueWUqsca0ay0Xi+9CiRL5K2Fa7pvNNa9-bE4mmjcJLdJbYNrsr3avvexjKgJzOQ5XBywZ3P4jxhDOFBzguhaFiumTrokCRKtUjhgrk0qNNEJ5lmelBwBc2A0JAAAieg-ja0iyA6qinh2FAERwRGkeR1oDr6LIBq4o5eCGE7gc8hYULE0SRAejh5jkcLrpKiQUIk8QSbkimOLkSHxLkOHOnhDGwIRtAsXQFGTPe+JNkSJJkrh9GMcxEBkUZbE+gBE4jpyvGgXyU5hEm8kSvm8SKTBXzrlk+ZVKJEpgpKmTAlp7aGrZBn2ax9amY+zbEq2dGJXpTHJQ5WDGc5fquWywFjnxYHhs8jiKnG8QRKkdXpPGM7riqdWghh4SSrUcQZK0CLEFIEBwIEOU9CBoYCQuMQJI4EQRNEESxOKkTROupSrUKvxNcCjVQgWXzxW+57GvsIzTfxNVxlEFQ-JqMLigpW1qa8R7SotETfeqKSneWnYXZida3lA13Vd5CCqo4bz5DDiTPUk8TrnkwlFsuiYLgWinpADOlunsIM8DgjD4JMENecEiCI7DSFhdqSTLYkSYdQeFCBb8qpZE1PORvCerafRhMYnSPBkFIWBjRAlOTtTCBpJmy0ibG8MY4hsNQtkR5QvkB348LH6Xp63CywJKogtqLNLZkjSJpK66KfEcPShESGQk0arHgik3ne6X43r+Zu3Yk0TCX1NtzsCEn-GmG5yd8cbqhJc5wd7gsJe+F49lipM0OTYjB1DofO3kGluy8GShCjcfLnJyS2wu8aM1kBuGiLxvXiYEtS5ARfy2kiqFqHPM5NU+6x4Ca1CoWmqxizi0Hq3PvWbl+nESldD908KqvHEilLSta0zst65qiCQWJmFvxFqtbfbElG+FcV4OVZ5cs74j80H8tq3rafcc9xCjnIjJCzNGqBXvjoR+hkip5wLq-DyM0aorRBC9cIa1q7JmUh1I63V3r5Fkn9KB+E8p2Wfj3aW29EB2zhitZMjUmiBVlIA-cbxQhxm1GhcIqoIhDWaEAA */ + createMachine( + { + context: initialContext, + predictableActionArguments: true, + id: 'DatasetsQualitySummaryPanel', + type: 'parallel', + states: { + datasetsQuality: { + initial: 'fetching', + states: { + fetching: { + invoke: { + src: 'loadDatasetsQuality', + onDone: { + target: 'loaded', + actions: ['storeDatasetsQuality'], + }, + onError: [ + { + target: 'retrying', + cond: { + type: 'canRetry', + counter: 'datasetsQualityRetries', + }, + actions: ['incrementDatasetsQualityRetries'], + }, + { + target: 'loaded', + actions: ['notifyFetchDatasetsQualityFailed'], + }, + ], + }, + }, + retrying: { + after: { + [RETRY_DELAY_IN_MS]: 'fetching', + }, + }, + loaded: { + type: 'final', + }, + }, + }, + datasetsActivity: { + initial: 'fetching', + states: { + fetching: { + invoke: { + src: 'loadDatasetsActivity', + onDone: { + target: 'loaded', + actions: ['storeDatasetsActivity'], + }, + onError: [ + { + target: 'retrying', + cond: { + type: 'canRetry', + counter: 'datasetsActivityRetries', + }, + actions: ['incrementDatasetsActivityRetries'], + }, + { + target: 'loaded', + actions: ['notifyFetchDatasetsActivityFailed'], + }, + ], + }, + }, + retrying: { + after: { + [RETRY_DELAY_IN_MS]: 'fetching', + }, + }, + loaded: { + type: 'final', + }, + }, + }, + estimatedData: { + initial: 'fetching', + states: { + fetching: { + invoke: { + src: 'loadEstimatedData', + onDone: { + target: 'loaded', + actions: ['storeEstimatedData'], + }, + onError: [ + { + target: 'retrying', + cond: { + type: 'canRetry', + counter: 'estimatedDataRetries', + }, + actions: ['incrementEstimatedDataRetries'], + }, + { + target: 'loaded', + actions: ['notifyFetchEstimatedDataFailed'], + }, + ], + }, + }, + retrying: { + after: { + [RETRY_DELAY_IN_MS]: 'fetching', + }, + }, + loaded: { + type: 'final', + }, + }, + }, + }, + }, + { + actions: { + storeDatasetsQuality: assign((_context, event) => + 'data' in event ? { datasetsQuality: event.data as DatasetsQuality } : {} + ), + storeDatasetsActivity: assign((_context, event) => + 'data' in event ? { datasetsActivity: event.data as DatasetsActivityDetails } : {} + ), + storeEstimatedData: assign((_context, event) => + 'data' in event + ? { + estimatedData: event.data as EstimatedDataDetails, + } + : {} + ), + incrementDatasetsQualityRetries: assign(({ retries }, _event) => ({ + retries: { ...retries, datasetsQualityRetries: retries.datasetsQualityRetries + 1 }, + })), + incrementDatasetsActivityRetries: assign(({ retries }, _event) => ({ + retries: { ...retries, datasetsActivityRetries: retries.datasetsActivityRetries + 1 }, + })), + incrementEstimatedDataRetries: assign(({ retries }, _event) => ({ + retries: { ...retries, estimatedDataRetries: retries.estimatedDataRetries + 1 }, + })), + }, + guards: { + canRetry: (context, event, { cond }) => { + if ('counter' in cond && cond.counter in context.retries) { + const retriesKey = cond.counter as keyof Retries; + return context.retries[retriesKey] < MAX_RETRIES; + } + return false; + }, + }, + } + ); + +export interface DatasetsSummaryPanelStateMachineDependencies { + initialContext?: DefaultDatasetsSummaryPanelContext; + toasts: IToasts; + dataStreamStatsClient: IDataStreamsStatsClient; +} + +export const createDatasetsSummaryPanelStateMachine = ({ + initialContext = defaultContext, + toasts, + dataStreamStatsClient, +}: DatasetsSummaryPanelStateMachineDependencies) => + createPureDatasetsSummaryPanelStateMachine(initialContext).withConfig({ + actions: { + notifyFetchDatasetsQualityFailed: (_context, event: DoneInvokeEvent) => + fetchDatasetsQualityFailedNotifier(toasts, event.data), + notifyFetchDatasetsActivityFailed: (_context, event: DoneInvokeEvent) => + fetchDatasetsActivityFailedNotifier(toasts, event.data), + notifyFetchEstimatedDataFailed: (_context, event: DoneInvokeEvent) => + fetchDatasetsEstimatedDataFailedNotifier(toasts, event.data), + }, + services: { + loadDatasetsQuality: async (_context) => { + const dataStreamsStats = await dataStreamStatsClient.getDataStreamsDegradedStats(); + const percentages = dataStreamsStats.map((stat) => stat.percentage); + return { percentages }; + }, + loadDatasetsActivity: async (_context) => { + const dataStreamsStats = await dataStreamStatsClient.getDataStreamsStats(); + const activeDataStreams = filterInactiveDatasets({ datasets: dataStreamsStats }); + return { + total: dataStreamsStats.length, + active: activeDataStreams.length, + }; + }, + loadEstimatedData: async (_context) => { + const { from: start, to: end } = getDefaultTimeRange(); + return dataStreamStatsClient.getDataStreamsEstimatedDataInBytes({ + query: { + type: 'logs', + start, + end, + }, + }); + }, + }, + }); + +export type DatasetsSummaryPanelStateService = InterpreterFrom< + typeof createDatasetsSummaryPanelStateMachine +>; + +export type DatasetsSummaryPanelStateMachine = ReturnType< + typeof createDatasetsSummaryPanelStateMachine +>; diff --git a/x-pack/plugins/dataset_quality/public/state_machines/summary_panel/src/types.ts b/x-pack/plugins/dataset_quality/public/state_machines/summary_panel/src/types.ts new file mode 100644 index 0000000000000..7b3990b176079 --- /dev/null +++ b/x-pack/plugins/dataset_quality/public/state_machines/summary_panel/src/types.ts @@ -0,0 +1,96 @@ +/* + * 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 { DoneInvokeEvent } from 'xstate'; +import { GetDataStreamsEstimatedDataInBytesResponse } from '../../../../common/data_streams_stats'; + +export interface Retries { + datasetsQualityRetries: number; + datasetsActivityRetries: number; + estimatedDataRetries: number; +} + +export interface DatasetsQuality { + percentages: number[]; +} + +export interface DatasetsActivityDetails { + total: number; + active: number; +} + +export interface EstimatedDataDetails { + estimatedDataInBytes: number; +} + +export interface WithDatasetsQuality { + datasetsQuality: DatasetsQuality; +} + +export interface WithActiveDatasets { + datasetsActivity: DatasetsActivityDetails; +} + +export interface WithEstimatedData { + estimatedData: EstimatedDataDetails; +} + +export interface WithRetries { + retries: Retries; +} + +export type DefaultDatasetsSummaryPanelContext = WithDatasetsQuality & + WithActiveDatasets & + WithEstimatedData & + WithRetries; + +export type DatasetsSummaryPanelState = + | { + value: 'datasetsQuality.fetching'; + context: DefaultDatasetsSummaryPanelContext; + } + | { + value: 'datasetsQuality.loaded'; + context: DefaultDatasetsSummaryPanelContext; + } + | { + value: 'datasetsQuality.retrying'; + context: DefaultDatasetsSummaryPanelContext; + } + | { + value: 'datasetsActivity.fetching'; + context: DefaultDatasetsSummaryPanelContext; + } + | { + value: 'datasetsActivity.loaded'; + context: DefaultDatasetsSummaryPanelContext; + } + | { + value: 'datasetsActivity.retrying'; + context: DefaultDatasetsSummaryPanelContext; + } + | { + value: 'estimatedData.fetching'; + context: DefaultDatasetsSummaryPanelContext; + } + | { + value: 'estimatedData.loaded'; + context: DefaultDatasetsSummaryPanelContext; + } + | { + value: 'estimatedData.retrying'; + context: DefaultDatasetsSummaryPanelContext; + }; + +export type DatasetSummaryPanelEvent = + | DoneInvokeEvent + | DoneInvokeEvent + | DoneInvokeEvent + | DoneInvokeEvent + | DoneInvokeEvent; + +export type DatasetsSummaryPanelContext = DatasetsSummaryPanelState['context']; diff --git a/x-pack/plugins/dataset_quality/public/utils/filter_inactive_datasets.ts b/x-pack/plugins/dataset_quality/public/utils/filter_inactive_datasets.ts index c741e5a7b9f09..86aeb155fd4f8 100644 --- a/x-pack/plugins/dataset_quality/public/utils/filter_inactive_datasets.ts +++ b/x-pack/plugins/dataset_quality/public/utils/filter_inactive_datasets.ts @@ -6,20 +6,21 @@ */ import { DataStreamStat } from '../../common/data_streams_stats'; +import { getDefaultTimeRange } from './default_timerange'; interface FilterInactiveDatasetsOptions { datasets: DataStreamStat[]; - timeRange: { + timeRange?: { from: string; to: string; }; } -export const filterInactiveDatasets = (options: FilterInactiveDatasetsOptions) => { - const { - datasets, - timeRange: { from, to }, - } = options; +export const filterInactiveDatasets = ({ + datasets, + timeRange = getDefaultTimeRange(), +}: FilterInactiveDatasetsOptions) => { + const { from, to } = timeRange; const startDate = new Date(from).getTime(); const endDate = new Date(to).getTime(); diff --git a/x-pack/plugins/dataset_quality/server/routes/data_streams/get_degraded_docs.ts b/x-pack/plugins/dataset_quality/server/routes/data_streams/get_degraded_docs.ts index 558a4a4ab4f7c..863d4f23d2979 100644 --- a/x-pack/plugins/dataset_quality/server/routes/data_streams/get_degraded_docs.ts +++ b/x-pack/plugins/dataset_quality/server/routes/data_streams/get_degraded_docs.ts @@ -21,8 +21,8 @@ import { createDatasetQualityESClient, wildcardQuery } from '../../utils'; export async function getDegradedDocsPaginated(options: { esClient: ElasticsearchClient; type?: DataStreamType; - start: number; - end: number; + start?: number; + end?: number; datasetQuery?: string; after?: { dataset: string; diff --git a/x-pack/plugins/dataset_quality/server/routes/data_streams/get_estimated_data_in_bytes/index.ts b/x-pack/plugins/dataset_quality/server/routes/data_streams/get_estimated_data_in_bytes/index.ts new file mode 100644 index 0000000000000..faf10417c6850 --- /dev/null +++ b/x-pack/plugins/dataset_quality/server/routes/data_streams/get_estimated_data_in_bytes/index.ts @@ -0,0 +1,33 @@ +/* + * 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 } from '@kbn/core/server'; +import { DEFAULT_DATASET_TYPE } from '../../../../common/constants'; +import { DataStreamType } from '../../../../common/types'; +import { indexStatsService } from '../../../services'; + +export async function getEstimatedDataInBytes(args: { + esClient: ElasticsearchClient; + type?: DataStreamType; + start: number; + end: number; +}) { + const { esClient, type = DEFAULT_DATASET_TYPE, start, end } = args; + + const [{ doc_count: docCount, size_in_bytes: docSize }, indexDocCountInTimeRange] = + await Promise.all([ + indexStatsService.getIndexStats(esClient, type), + indexStatsService.getIndexDocCount(esClient, type, start, end), + ]); + + if (!docCount) return 0; + + const avgDocSize = docSize / docCount; + const estimatedDataInBytes = Math.round(indexDocCountInTimeRange * avgDocSize); + + return estimatedDataInBytes; +} diff --git a/x-pack/plugins/dataset_quality/server/routes/data_streams/routes.ts b/x-pack/plugins/dataset_quality/server/routes/data_streams/routes.ts index 02f1508433e79..dbca7e1d0b4bd 100644 --- a/x-pack/plugins/dataset_quality/server/routes/data_streams/routes.ts +++ b/x-pack/plugins/dataset_quality/server/routes/data_streams/routes.ts @@ -21,6 +21,7 @@ import { getDataStreams } from './get_data_streams'; import { getDataStreamsStats } from './get_data_streams_stats'; import { getDegradedDocsPaginated } from './get_degraded_docs'; import { getIntegrations } from './get_integrations'; +import { getEstimatedDataInBytes } from './get_estimated_data_in_bytes'; const statsRoute = createDatasetQualityServerRoute({ endpoint: 'GET /internal/dataset_quality/data_streams/stats', @@ -70,7 +71,7 @@ const degradedDocsRoute = createDatasetQualityServerRoute({ endpoint: 'GET /internal/dataset_quality/data_streams/degraded_docs', params: t.type({ query: t.intersection([ - rangeRt, + t.partial(rangeRt.props), typeRt, t.partial({ datasetQuery: t.string, @@ -135,8 +136,36 @@ const dataStreamDetailsRoute = createDatasetQualityServerRoute({ }, }); +const estimatedDataInBytesRoute = createDatasetQualityServerRoute({ + endpoint: 'GET /internal/dataset_quality/data_streams/estimated_data', + params: t.type({ + query: t.intersection([typeRt, rangeRt]), + }), + options: { + tags: [], + }, + async handler(resources): Promise<{ + estimatedDataInBytes: number; + }> { + const { context, params } = resources; + const coreContext = await context.core; + + const esClient = coreContext.elasticsearch.client.asCurrentUser; + + const estimatedDataInBytes = await getEstimatedDataInBytes({ + esClient, + ...params.query, + }); + + return { + estimatedDataInBytes, + }; + }, +}); + export const dataStreamsRouteRepository = { ...statsRoute, ...degradedDocsRoute, ...dataStreamDetailsRoute, + ...estimatedDataInBytesRoute, }; diff --git a/x-pack/plugins/dataset_quality/server/services/index.ts b/x-pack/plugins/dataset_quality/server/services/index.ts index 86173c476dd0e..702d1d4fcc76b 100644 --- a/x-pack/plugins/dataset_quality/server/services/index.ts +++ b/x-pack/plugins/dataset_quality/server/services/index.ts @@ -6,3 +6,4 @@ */ export { dataStreamService } from './data_stream'; +export { indexStatsService } from './index_stats'; diff --git a/x-pack/plugins/dataset_quality/server/services/index_stats.ts b/x-pack/plugins/dataset_quality/server/services/index_stats.ts new file mode 100644 index 0000000000000..c3288a893ef0e --- /dev/null +++ b/x-pack/plugins/dataset_quality/server/services/index_stats.ts @@ -0,0 +1,63 @@ +/* + * 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 } from '@kbn/core/server'; +import { rangeQuery } from '@kbn/observability-plugin/server'; +import { DataStreamType } from '../../common/types'; + +class IndexStatsService { + public async getIndexStats( + esClient: ElasticsearchClient, + type: DataStreamType + ): Promise<{ + doc_count: number; + size_in_bytes: number; + }> { + try { + const index = `${type}-*-*`; + + const indexStats = await esClient.indices.stats({ index }); + return { + doc_count: indexStats._all.total?.docs ? indexStats._all.total?.docs?.count : 0, + size_in_bytes: indexStats._all.total?.store + ? indexStats._all.total?.store.size_in_bytes + : 0, + }; + } catch (e) { + if (e.statusCode === 404) { + return { doc_count: 0, size_in_bytes: 0 }; + } + throw e; + } + } + + public async getIndexDocCount( + esClient: ElasticsearchClient, + type: DataStreamType, + start: number, + end: number + ): Promise { + try { + const index = `${type}-*-*`; + + const query = rangeQuery(start, end)[0]; + const docCount = await esClient.count({ + index, + query, + }); + + return docCount.count; + } catch (e) { + if (e.statusCode === 404) { + return 0; + } + throw e; + } + } +} + +export const indexStatsService = new IndexStatsService(); diff --git a/x-pack/plugins/dataset_quality/tsconfig.json b/x-pack/plugins/dataset_quality/tsconfig.json index be630d1b664ab..6191811e35779 100644 --- a/x-pack/plugins/dataset_quality/tsconfig.json +++ b/x-pack/plugins/dataset_quality/tsconfig.json @@ -29,6 +29,9 @@ "@kbn/router-utils", "@kbn/xstate-utils", "@kbn/shared-ux-utility", + "@kbn/ui-theme", + "@kbn/core-notifications-browser", + "@kbn/formatters" ], "exclude": [ "target/**/*", diff --git a/x-pack/test/dataset_quality_api_integration/tests/data_streams/estimated_data.spec.ts b/x-pack/test/dataset_quality_api_integration/tests/data_streams/estimated_data.spec.ts new file mode 100644 index 0000000000000..46a9822aa05ac --- /dev/null +++ b/x-pack/test/dataset_quality_api_integration/tests/data_streams/estimated_data.spec.ts @@ -0,0 +1,83 @@ +/* + * 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 { log, timerange } from '@kbn/apm-synthtrace-client'; +import expect from '@kbn/expect'; +import { DatasetQualityApiClientKey } from '../../common/config'; +import { FtrProviderContext } from '../../common/ftr_provider_context'; + +export default function ApiTest({ getService }: FtrProviderContext) { + const registry = getService('registry'); + const synthtrace = getService('logSynthtraceEsClient'); + const datasetQualityApiClient = getService('datasetQualityApiClient'); + const start = '2023-12-11T18:00:00.000Z'; + const oneDayEnd = '2023-12-12T18:00:00.000Z'; + const oneWeekEnd = '2023-12-18T18:00:00.000Z'; + const dataset = 'nginx.access'; + const namespace = 'default'; + + async function callApiAs(type: 'logs' | 'metrics', end: string) { + const user = 'datasetQualityLogsUser' as DatasetQualityApiClientKey; + return await datasetQualityApiClient[user]({ + endpoint: 'GET /internal/dataset_quality/data_streams/estimated_data', + params: { + query: { + type, + start, + end, + }, + }, + }); + } + + registry.when('Estimated Data Details', { config: 'basic' }, () => { + describe('gets the data streams estimated data', () => { + before(async () => { + await synthtrace.index([ + timerange(start, oneWeekEnd) + .interval('1h') + .rate(1) + .generator((timestamp) => + log + .create() + .message('This is a log message') + .timestamp(timestamp) + .dataset(dataset) + .namespace(namespace) + .defaults({ + 'log.file.path': '/my-service.log', + }) + ), + ]); + }); + + it('returns a non-empty body', async () => { + const resp = await callApiAs('logs', oneDayEnd); + expect(resp.body).not.empty(); + }); + + it('returns correct estimated data for 1 day of logs', async () => { + const resp = await callApiAs('logs', oneDayEnd); + expect(resp.body.estimatedDataInBytes).to.be.lessThan(2500).greaterThan(1000); + }); + + it('returns correct estimated data for 1 week of logs', async () => { + const resp = await callApiAs('logs', oneWeekEnd); + expect(resp.body.estimatedDataInBytes).to.be.lessThan(20000).greaterThan(10000); + }); + + it('returns correct estimated data for no data index', async () => { + const resp = await callApiAs('metrics', oneWeekEnd); + expect(resp.body.estimatedDataInBytes).to.equal(0); + }); + + after(async () => { + await synthtrace.clean(); + }); + }); + }); +} diff --git a/yarn.lock b/yarn.lock index 973c748f3b781..fc817a3e90445 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4760,6 +4760,10 @@ version "0.0.0" uid "" +"@kbn/formatters@link:packages/kbn-formatters": + version "0.0.0" + uid "" + "@kbn/ftr-apis-plugin@link:src/plugins/ftr_apis": version "0.0.0" uid "" From 1b51e72e14b819dee9be367a74d4e98e475071da Mon Sep 17 00:00:00 2001 From: Marta Bondyra <4283304+mbondyra@users.noreply.github.com> Date: Tue, 13 Feb 2024 13:54:26 +0100 Subject: [PATCH 60/83] [Lens] close dimension editor when editing color palette and clicking outside (#175215) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit https://github.com/elastic/kibana/assets/4283304/e85b7f97-114a-4b16-8b72-02eee5a121b9 Fixes https://github.com/elastic/kibana/issues/176247 (I removed the flaky tests because they were bad. Replaced them with functional ones, flaky test runner here: https://buildkite.com/elastic/kibana-flaky-test-suite-runner/builds/5071) Fixes an issue with the dimension editor visible on the video. When the second flyout (palette) is open and we change the chart (for example from Bar Vertical Stacked to Bar Vertical when only having a breakdown dimension defined), the breakdown dimension moves to horizontal axis, but it’s not reflected on the flyout (we still get the settings for breakdown, palette etc). I initially fixed it by closing two flyouts and not fixing updating the state of the flyout, but then I found a solution to also update the state instead. I did a refactor here for the palette flyout and generalized it to setting with sibling flyout component. I also moved some repeating logic inside of it. Styles are moved, but nothing changes from the user perspective. The only visual change is to unify palette settings by removing `Edit` copy from the button (some palette settings had it, other didn't). We can also do it the other way around if it makes more sense: before: Screenshot 2024-01-29 at 11 17 47 after: Screenshot 2024-01-29 at 11 18 32 --------- Co-authored-by: Michael Marcialis --- .../buttons/fake_dimension_button.tsx | 30 +++ .../config_panel/dimension_container.tsx | 4 +- .../config_panel/layer_panel.test.tsx | 80 +------ .../editor_frame/config_panel/layer_panel.tsx | 210 +++++++++--------- .../editor_frame/config_panel/types.ts | 8 +- .../coloring/palette_panel_container.tsx | 134 ++++------- .../shared_components/flyout_container.tsx | 20 +- ....scss => setting_with_sibling_flyout.scss} | 10 +- .../setting_with_sibling_flyout.tsx | 120 ++++++++++ .../components/dimension_editor.scss | 4 - .../components/dimension_editor.test.tsx | 21 +- .../datatable/components/dimension_editor.tsx | 84 ++----- .../visualizations/gauge/dimension_editor.tsx | 102 +++------ .../heatmap/dimension_editor.tsx | 94 ++------ .../legacy_metric/dimension_editor.test.tsx | 27 +-- .../legacy_metric/dimension_editor.tsx | 106 +++------ .../metric/dimension_editor.tsx | 77 ++----- .../partition/dimension_editor.tsx | 183 ++++++--------- .../tagcloud/tags_dimension_editor.tsx | 179 ++++++--------- .../xy/load_annotation_library_flyout.tsx | 3 +- .../xy/xy_config_panel/dimension_editor.tsx | 174 ++++++--------- .../translations/translations/fr-FR.json | 9 +- .../translations/translations/ja-JP.json | 9 +- .../translations/translations/zh-CN.json | 9 +- x-pack/test/accessibility/apps/group2/lens.ts | 2 +- .../apps/lens/group1/smokescreen.ts | 41 ++++ .../test/functional/apps/lens/group2/table.ts | 2 +- .../apps/lens/group3/add_to_dashboard.ts | 2 +- .../functional/apps/lens/group5/heatmap.ts | 2 +- .../apps/lens/group6/legacy_metric.ts | 2 +- .../functional/apps/lens/group6/metric.ts | 2 +- .../apps/lens/open_in_lens/agg_based/gauge.ts | 2 +- .../apps/lens/open_in_lens/agg_based/goal.ts | 2 +- .../lens/open_in_lens/agg_based/metric.ts | 2 +- .../apps/lens/open_in_lens/tsvb/gauge.ts | 4 +- .../apps/lens/open_in_lens/tsvb/metric.ts | 4 +- .../apps/lens/open_in_lens/tsvb/table.ts | 4 +- .../test/functional/page_objects/lens_page.ts | 14 +- .../group2/open_in_lens/agg_based/gauge.ts | 2 +- .../group2/open_in_lens/agg_based/goal.ts | 2 +- .../group2/open_in_lens/agg_based/metric.ts | 2 +- .../group3/open_in_lens/tsvb/gauge.ts | 4 +- .../group3/open_in_lens/tsvb/metric.ts | 4 +- .../group3/open_in_lens/tsvb/table.ts | 4 +- 44 files changed, 726 insertions(+), 1074 deletions(-) create mode 100644 x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/buttons/fake_dimension_button.tsx rename x-pack/plugins/lens/public/shared_components/{coloring/palette_panel_container.scss => setting_with_sibling_flyout.scss} (73%) create mode 100644 x-pack/plugins/lens/public/shared_components/setting_with_sibling_flyout.tsx diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/buttons/fake_dimension_button.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/buttons/fake_dimension_button.tsx new file mode 100644 index 0000000000000..c27339be9b0b9 --- /dev/null +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/buttons/fake_dimension_button.tsx @@ -0,0 +1,30 @@ +/* + * 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 { euiThemeVars } from '@kbn/ui-theme'; +import { css } from '@emotion/react'; +import { DimensionTrigger } from '@kbn/visualization-ui-components'; + +export const FakeDimensionButton = ({ label }: { label: string }) => ( +
+ +
+); diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/dimension_container.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/dimension_container.tsx index 6afc4069ec7ee..13694613d40fe 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/dimension_container.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/dimension_container.tsx @@ -15,9 +15,9 @@ export function DimensionContainer({ ...props }: { isOpen: boolean; - handleClose: () => boolean; + handleClose: () => void; panel: React.ReactElement | null; - groupLabel: string; + label: string; isFullscreen: boolean; panelRef: (el: HTMLDivElement) => void; isInlineEditing?: boolean; diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_panel.test.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_panel.test.tsx index d9c0438d6d298..71d29df9911e5 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_panel.test.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_panel.test.tsx @@ -74,7 +74,6 @@ const onDropToDimension = jest.fn(); describe('LayerPanel', () => { let mockVisualization: jest.Mocked; - let mockVisualization2: jest.Mocked; let mockDatasource = createMockDatasource('testDatasource'); @@ -144,7 +143,6 @@ describe('LayerPanel', () => { beforeEach(() => { mockVisualization = createMockVisualization(faker.random.alphaNumeric()); mockVisualization.getLayerIds.mockReturnValue(['first']); - mockVisualization2 = createMockVisualization(faker.random.alphaNumeric()); mockDatasource = createMockDatasource(); }); @@ -170,8 +168,7 @@ describe('LayerPanel', () => { }); }); - // FLAKY: https://github.com/elastic/kibana/issues/176247 - describe.skip('single group', () => { + describe('single group', () => { it('should render the group with a way to add a new column', async () => { mockVisualization.getConfiguration.mockReturnValue({ groups: [defaultGroup], @@ -435,6 +432,7 @@ describe('LayerPanel', () => { act(() => { stateFn('newDatasourceState'); }); + expect(updateAll).toHaveBeenCalled(); }); @@ -513,79 +511,6 @@ describe('LayerPanel', () => { expect(screen.getByRole('heading', { name: defaultGroup.groupLabel })).toBeInTheDocument(); }); - it('should close the DimensionContainer when the activeVisualization has changed', async () => { - /** - * The ID generation system for new dimensions has been messy before, so - * this tests that the ID used in the first render is used to keep the container - * open in future renders - */ - - (generateId as jest.Mock).mockReturnValue(`columnId`); - mockVisualization.getConfiguration.mockReturnValue({ - groups: [ - { - ...defaultGroup, - accessors: [{ columnId: 'columnId' }], - }, - ], - }); - // Normally the configuration would change in response to a state update, - // but this test is updating it directly - mockVisualization2.getConfiguration.mockReturnValue({ - groups: [ - { - ...defaultGroup, - accessors: [{ columnId: 'columnId' }, { columnId: 'secondColumnId' }], - }, - ], - }); - - const { rerender } = renderLayerPanel(); - userEvent.click(screen.getAllByTestId('lns-empty-dimension')[0]); - expect(screen.getByRole('heading', { name: defaultGroup.groupLabel })).toBeInTheDocument(); - rerender(); - expect( - screen.queryByRole('heading', { name: defaultGroup.groupLabel }) - ).not.toBeInTheDocument(); - }); - - it('should close the DimensionContainer when the column cannot be found in the config', async () => { - /** - * The ID generation system for new dimensions has been messy before, so - * this tests that the ID used in the first render is used to keep the container - * open in future renders - */ - - (generateId as jest.Mock).mockReturnValue(`columnId`); - mockVisualization.getConfiguration.mockReturnValue({ - groups: [ - { - ...defaultGroup, - accessors: [{ columnId: 'columnId' }], - }, - ], - }); - // Normally the configuration would change in response to a state update, - // but this test is updating it directly - mockVisualization2.getConfiguration.mockReturnValue({ - groups: [ - { - ...defaultGroup, - accessors: [{ columnId: 'secondColumnId' }], - }, - ], - }); - - const { rerender } = renderLayerPanel(); - // opens dimension editor and creates another column with secondColumnId - userEvent.click(screen.getAllByTestId('lns-empty-dimension')[0]); - expect(screen.getByRole('heading', { name: defaultGroup.groupLabel })).toBeInTheDocument(); - rerender(); - expect( - screen.queryByRole('heading', { name: defaultGroup.groupLabel }) - ).not.toBeInTheDocument(); - }); - it('should only update the state on close when needed', async () => { const updateDatasource = jest.fn(); mockVisualization.getConfiguration.mockReturnValue({ @@ -1077,6 +1002,5 @@ describe('LayerPanel', () => { }); }); }); - // TODO - test user message display }); diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_panel.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_panel.tsx index 57b40ac2d02af..0ed5945704a38 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_panel.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_panel.tsx @@ -21,11 +21,11 @@ import { i18n } from '@kbn/i18n'; import { css } from '@emotion/react'; import { euiThemeVars } from '@kbn/ui-theme'; import { DragDropIdentifier, ReorderProvider, DropType } from '@kbn/dom-drag-drop'; -import { DimensionButton, DimensionTrigger } from '@kbn/visualization-ui-components'; +import { DimensionButton } from '@kbn/visualization-ui-components'; import { LayerActions } from './layer_actions'; -import { isOperation, LayerAction } from '../../../types'; +import { isOperation, LayerAction, VisualizationDimensionGroupConfig } from '../../../types'; import { LayerSettings } from './layer_settings'; -import { LayerPanelProps, ActiveDimensionState } from './types'; +import { LayerPanelProps } from './types'; import { DimensionContainer } from './dimension_container'; import { EmptyDimensionButton } from './buttons/empty_dimension_button'; import { DraggableDimensionButton } from './buttons/draggable_dimension_button'; @@ -38,15 +38,15 @@ import { } from '../../../state_management'; import { getSharedActions } from './layer_actions/layer_actions'; import { FlyoutContainer } from '../../../shared_components/flyout_container'; - -const initialActiveDimensionState = { - isNew: false, -}; +import { FakeDimensionButton } from './buttons/fake_dimension_button'; export function LayerPanel(props: LayerPanelProps) { - const [activeDimension, setActiveDimension] = useState( - initialActiveDimensionState - ); + const [openDimension, setOpenDimension] = useState<{ + isComplete?: boolean; + openColumnId?: string; + openColumnGroup?: VisualizationDimensionGroupConfig; + }>({}); + const [isPanelSettingsOpen, setPanelSettingsOpen] = useState(false); const [hideTooltip, setHideTooltip] = useState(false); @@ -70,6 +70,7 @@ export function LayerPanel(props: LayerPanelProps) { onChangeIndexPattern, core, onDropToDimension, + setIsInlineFlyoutVisible, } = props; const isSaveable = useLensSelector((state) => state.lens.isSaveable); @@ -78,15 +79,10 @@ export function LayerPanel(props: LayerPanelProps) { const isFullscreen = useLensSelector(selectIsFullscreenDatasource); const dateRange = useLensSelector(selectResolvedDateRange); - useEffect(() => { - setActiveDimension(initialActiveDimensionState); - }, [activeVisualization.id]); - useEffect(() => { // is undefined when the dimension panel is closed - const activeDimensionId = activeDimension.activeId; - props?.setIsInlineFlyoutVisible?.(!Boolean(activeDimensionId)); - }, [activeDimension.activeId, activeVisualization.id, props]); + setIsInlineFlyoutVisible?.(!openDimension.openColumnId); + }, [openDimension.openColumnId, setIsInlineFlyoutVisible]); const panelRef = useRef(null); const settingsPanelRef = useRef(null); @@ -96,6 +92,26 @@ export function LayerPanel(props: LayerPanelProps) { [layerId, registerNewLayerRef] ); + const closeDimensionEditor = () => { + if (layerDatasource) { + if (layerDatasource.updateStateOnCloseDimension) { + const newState = layerDatasource.updateStateOnCloseDimension({ + state: layerDatasourceState, + layerId, + columnId: openColumnId!, + }); + if (newState) { + props.updateDatasource(datasourceId, newState); + } + } + } + + setOpenDimension({}); + if (isFullscreen) { + toggleFullscreen(); + } + }; + const layerVisualizationConfigProps = { layerId, state: props.visualizationState, @@ -137,7 +153,36 @@ export function LayerPanel(props: LayerPanelProps) { ); const isEmptyLayer = !dimensionGroups.some((d) => d.accessors.length > 0); - const { activeId, activeGroup } = activeDimension; + const { openColumnId, openColumnGroup, isComplete } = openDimension; + + useEffect(() => { + if (!openColumnId) { + return; + } + + const derivedOpenColumnGroup = dimensionGroups.find((group) => + group.accessors.some((a) => a.columnId === openColumnId) + ); + // dont update if nothing has changed + if ( + isComplete === !!derivedOpenColumnGroup && + derivedOpenColumnGroup?.groupId === openColumnGroup?.groupId + ) { + return; + } + if (derivedOpenColumnGroup) { + // if column is found, mark it as complete. If it's moved to another group, update the group + setOpenDimension({ + openColumnId, + openColumnGroup: derivedOpenColumnGroup, + isComplete: !!derivedOpenColumnGroup, + }); + } + // if column is not found but is not new (is complete), close the dimension panel + if (isComplete && !derivedOpenColumnGroup) { + setOpenDimension({}); + } + }, [openColumnId, dimensionGroups, isComplete, openColumnGroup?.groupId]); const allAccessors = dimensionGroups.flatMap((group) => group.accessors.map((accessor) => accessor.columnId) @@ -169,7 +214,7 @@ export function LayerPanel(props: LayerPanelProps) { [setNextFocusedButtonId, onDropToDimension] ); - const isDimensionPanelOpen = Boolean(activeId); + const isDimensionPanelOpen = Boolean(openColumnId); const updateDataLayerState = useCallback( ( @@ -181,10 +226,10 @@ export function LayerPanel(props: LayerPanelProps) { forceRender = false, }: { isDimensionComplete?: boolean; forceRender?: boolean } = {} ) => { - if (!activeGroup || !activeId) { + if (!openColumnGroup || !openColumnId) { return; } - if (allAccessors.includes(activeId)) { + if (allAccessors.includes(openColumnId)) { if (isDimensionComplete) { if (forceRender) { updateDatasource(datasourceId, newState); @@ -196,7 +241,7 @@ export function LayerPanel(props: LayerPanelProps) { // complete, which clears the visualization. This keeps the flyout open and reuses // the previous columnId props.updateDatasource(datasourceId, newState); - props.onRemoveDimension({ layerId, columnId: activeId }); + props.onRemoveDimension({ layerId, columnId: openColumnId }); } } else if (isDimensionComplete) { updateAll( @@ -204,13 +249,12 @@ export function LayerPanel(props: LayerPanelProps) { newState, activeVisualization.setDimension({ layerId, - groupId: activeGroup.groupId, - columnId: activeId, + groupId: openColumnGroup.groupId, + columnId: openColumnId, prevState: visualizationState, frame: framePublicAPI, }) ); - setActiveDimension({ ...activeDimension, isNew: false }); } else { if (forceRender) { updateDatasource(datasourceId, newState); @@ -221,9 +265,9 @@ export function LayerPanel(props: LayerPanelProps) { }, // eslint-disable-next-line react-hooks/exhaustive-deps [ - activeDimension, - activeGroup, - activeId, + openDimension, + openColumnGroup, + openColumnId, activeVisualization, datasourceId, layerId, @@ -510,10 +554,9 @@ export function LayerPanel(props: LayerPanelProps) { label={columnLabelMap?.[accessorConfig.columnId] ?? ''} groupLabel={group.groupLabel} onClick={(id: string) => { - setActiveDimension({ - isNew: false, - activeGroup: group, - activeId: id, + setOpenDimension({ + openColumnGroup: group, + openColumnId: id, }); }} onRemoveClick={(id: string) => { @@ -552,26 +595,7 @@ export function LayerPanel(props: LayerPanelProps) { ) : null} {group.fakeFinalAccessor && ( -
- -
+ )} {group.supportsMoreColumns ? ( @@ -603,10 +627,9 @@ export function LayerPanel(props: LayerPanelProps) { datasourceLayers={framePublicAPI.datasourceLayers} onClick={(id) => { props.onEmptyDimensionAdd(id, group); - setActiveDimension({ - activeGroup: group, - activeId: id, - isNew: !group.supportStaticValue && Boolean(layerDatasource), + setOpenDimension({ + openColumnGroup: group, + openColumnId: id, }); }} onDrop={onDrop} @@ -622,15 +645,13 @@ export function LayerPanel(props: LayerPanelProps) { {(layerDatasource?.LayerSettingsComponent || activeVisualization?.LayerSettingsComponent) && ( (settingsPanelRef.current = el)} - isOpen={isPanelSettingsOpen} isFullscreen={false} - groupLabel={i18n.translate('xpack.lens.editorFrame.layerSettingsTitle', { + label={i18n.translate('xpack.lens.editorFrame.layerSettingsTitle', { defaultMessage: 'Layer settings', })} + isOpen={isPanelSettingsOpen} handleClose={() => { - // update the current layer settings setPanelSettingsOpen(false); - return true; }} isInlineEditing={Boolean(props?.setIsInlineFlyoutVisible)} > @@ -651,9 +672,7 @@ export function LayerPanel(props: LayerPanelProps) {
) : null} {layerDatasource?.LayerSettingsComponent && ( - <> - - + )} {layerDatasource?.LayerSettingsComponent && visualizationLayerSettings.data ? ( @@ -700,59 +719,40 @@ export function LayerPanel(props: LayerPanelProps) { panelRef={(el) => (panelRef.current = el)} isOpen={isDimensionPanelOpen} isFullscreen={isFullscreen} - groupLabel={activeGroup?.dimensionEditorGroupLabel ?? (activeGroup?.groupLabel || '')} + label={openColumnGroup?.dimensionEditorGroupLabel ?? (openColumnGroup?.groupLabel || '')} isInlineEditing={Boolean(props?.setIsInlineFlyoutVisible)} - handleClose={() => { - if (layerDatasource) { - if (layerDatasource.updateStateOnCloseDimension) { - const newState = layerDatasource.updateStateOnCloseDimension({ - state: layerDatasourceState, - layerId, - columnId: activeId!, - }); - if (newState) { - props.updateDatasource(datasourceId, newState); - } - } - } - - setActiveDimension(initialActiveDimensionState); - if (isFullscreen) { - toggleFullscreen(); - } - return true; - }} + handleClose={closeDimensionEditor} panel={ <> - {activeGroup && - activeId && + {openColumnGroup && + openColumnId && layerDatasource && layerDatasource.DimensionEditorComponent({ ...layerDatasourceConfigProps, core: props.core, - columnId: activeId, - groupId: activeGroup.groupId, - hideGrouping: activeGroup.hideGrouping, - filterOperations: activeGroup.filterOperations, - isMetricDimension: activeGroup?.isMetricDimension, + columnId: openColumnId, + groupId: openColumnGroup.groupId, + hideGrouping: openColumnGroup.hideGrouping, + filterOperations: openColumnGroup.filterOperations, + isMetricDimension: openColumnGroup?.isMetricDimension, dimensionGroups, toggleFullscreen, isFullscreen, setState: updateDataLayerState, - supportStaticValue: Boolean(activeGroup.supportStaticValue), - paramEditorCustomProps: activeGroup.paramEditorCustomProps, - enableFormatSelector: activeGroup.enableFormatSelector !== false, + supportStaticValue: Boolean(openColumnGroup.supportStaticValue), + paramEditorCustomProps: openColumnGroup.paramEditorCustomProps, + enableFormatSelector: openColumnGroup.enableFormatSelector !== false, layerType: activeVisualization.getLayerType(layerId, visualizationState), indexPatterns: dataViews.indexPatterns, activeData: layerVisualizationConfigProps.activeData, dataSectionExtra: !isFullscreen && - !activeDimension.isNew && + openDimension.isComplete && activeVisualization.DimensionEditorDataExtraComponent && ( ), })} - {activeGroup && - activeId && + {openColumnGroup && + openColumnId && !isFullscreen && - !activeDimension.isNew && + openDimension.isComplete && activeVisualization.DimensionEditorComponent && - activeGroup?.enableDimensionEditor && ( + openColumnGroup?.enableDimensionEditor && ( <>
void; } - -export interface ActiveDimensionState { - isNew: boolean; - activeId?: string; - activeGroup?: VisualizationDimensionGroupConfig; -} diff --git a/x-pack/plugins/lens/public/shared_components/coloring/palette_panel_container.tsx b/x-pack/plugins/lens/public/shared_components/coloring/palette_panel_container.tsx index e492ceaf0e174..e79d5e9c1624c 100644 --- a/x-pack/plugins/lens/public/shared_components/coloring/palette_panel_container.tsx +++ b/x-pack/plugins/lens/public/shared_components/coloring/palette_panel_container.tsx @@ -5,105 +5,51 @@ * 2.0. */ -import './palette_panel_container.scss'; - import { i18n } from '@kbn/i18n'; -import React, { useState, useEffect, MutableRefObject } from 'react'; -import { - EuiFlyoutHeader, - EuiFlyoutFooter, - EuiTitle, - EuiButtonIcon, - EuiButtonEmpty, - EuiFlexGroup, - EuiFlexItem, - EuiFocusTrap, - EuiOutsideClickDetector, - EuiPortal, -} from '@elastic/eui'; +import React, { MutableRefObject } from 'react'; +import { EuiButtonIcon, EuiFlexItem, EuiColorPaletteDisplay, EuiToolTip } from '@elastic/eui'; +import { FIXED_PROGRESSION } from '@kbn/coloring'; +import { SettingWithSiblingFlyout } from '../setting_with_sibling_flyout'; -export function PalettePanelContainer({ - isOpen, - handleClose, - siblingRef, - children, - title, - isInlineEditing, -}: { - isOpen: boolean; - title: string; - handleClose: () => void; +export function PalettePanelContainer(props: { + palette: string[]; siblingRef: MutableRefObject; children?: React.ReactElement | React.ReactElement[]; isInlineEditing?: boolean; + title?: string; }) { - const [focusTrapIsEnabled, setFocusTrapIsEnabled] = useState(false); - - const closeFlyout = () => { - handleClose(); - setFocusTrapIsEnabled(false); - }; - - useEffect(() => { - // The EuiFocusTrap is disabled when inline editing as it causes bugs with comboboxes - if (isOpen && !isInlineEditing) { - // without setTimeout here the flyout pushes content when animating - setTimeout(() => { - setFocusTrapIsEnabled(true); - }, 255); - } - }, [isInlineEditing, isOpen]); - - return isOpen && siblingRef.current ? ( - - - -
- - - - - - - - -

- {title} -

-
-
-
-
- - {children &&
{children}
} - - - - {i18n.translate('xpack.lens.table.palettePanelContainer.back', { - defaultMessage: 'Back', + return ( + void }) => ( + <> + + + + + + - -
-
-
-
- ) : null; + iconType="controlsHorizontal" + onClick={onClick} + size="xs" + /> + + + + )} + /> + ); } diff --git a/x-pack/plugins/lens/public/shared_components/flyout_container.tsx b/x-pack/plugins/lens/public/shared_components/flyout_container.tsx index d06c9babed5c5..2b865daa31c08 100644 --- a/x-pack/plugins/lens/public/shared_components/flyout_container.tsx +++ b/x-pack/plugins/lens/public/shared_components/flyout_container.tsx @@ -41,7 +41,7 @@ function fromExcludedClickTarget(event: Event) { export function FlyoutContainer({ isOpen, - groupLabel, + label, handleClose, isFullscreen, panelRef, @@ -51,9 +51,9 @@ export function FlyoutContainer({ isInlineEditing, }: { isOpen: boolean; - handleClose: () => boolean; + handleClose: () => void; children: React.ReactElement | null; - groupLabel: string; + label: string; isFullscreen?: boolean; panelRef?: (el: HTMLDivElement) => void; panelContainerRef?: (el: HTMLDivElement) => void; @@ -63,11 +63,8 @@ export function FlyoutContainer({ const [focusTrapIsEnabled, setFocusTrapIsEnabled] = useState(false); const closeFlyout = useCallback(() => { - const canClose = handleClose(); - if (canClose) { - setFocusTrapIsEnabled(false); - } - return canClose; + setFocusTrapIsEnabled(false); + handleClose(); }, [handleClose]); useEffect(() => { @@ -138,12 +135,7 @@ export function FlyoutContainer({ id="lnsDimensionContainerTitle" className="lnsDimensionContainer__headerTitle" > - {i18n.translate('xpack.lens.configure.configurePanelTitle', { - defaultMessage: '{groupLabel}', - values: { - groupLabel, - }, - })} + {label} diff --git a/x-pack/plugins/lens/public/shared_components/coloring/palette_panel_container.scss b/x-pack/plugins/lens/public/shared_components/setting_with_sibling_flyout.scss similarity index 73% rename from x-pack/plugins/lens/public/shared_components/coloring/palette_panel_container.scss rename to x-pack/plugins/lens/public/shared_components/setting_with_sibling_flyout.scss index cfc880138d3c7..b6975710753e0 100644 --- a/x-pack/plugins/lens/public/shared_components/coloring/palette_panel_container.scss +++ b/x-pack/plugins/lens/public/shared_components/setting_with_sibling_flyout.scss @@ -1,6 +1,6 @@ -@import '../../mixins'; +@import '../mixins'; -.lnsPalettePanelContainer { +.lnsSettingWithSiblingFlyout { // Use the EuiFlyout style @include euiFlyout; // But with custom positioning to keep it within the sidebar contents @@ -14,15 +14,15 @@ z-index: $euiZLevel3 + 1 } -.lnsPalettePanelContainer__header { +.lnsSettingWithSiblingFlyout__header { padding: $euiSize; } -.lnsPalettePanelContainer__content { +.lnsSettingWithSiblingFlyout__content { @include euiYScroll; flex: 1; } -.lnsPalettePanelContainer__footer { +.lnsSettingWithSiblingFlyout__footer { padding: $euiSize; } \ No newline at end of file diff --git a/x-pack/plugins/lens/public/shared_components/setting_with_sibling_flyout.tsx b/x-pack/plugins/lens/public/shared_components/setting_with_sibling_flyout.tsx new file mode 100644 index 0000000000000..b96b47cfb6088 --- /dev/null +++ b/x-pack/plugins/lens/public/shared_components/setting_with_sibling_flyout.tsx @@ -0,0 +1,120 @@ +/* + * 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 './setting_with_sibling_flyout.scss'; + +import { i18n } from '@kbn/i18n'; +import React, { useState, useEffect, MutableRefObject } from 'react'; +import { + EuiFlyoutHeader, + EuiFlyoutFooter, + EuiTitle, + EuiButtonIcon, + EuiButtonEmpty, + EuiFlexGroup, + EuiFlexItem, + EuiFocusTrap, + EuiOutsideClickDetector, + EuiPortal, +} from '@elastic/eui'; + +const DEFAULT_TITLE = i18n.translate('xpack.lens.colorSiblingFlyoutTitle', { + defaultMessage: 'Color', +}); + +export function SettingWithSiblingFlyout({ + siblingRef, + children, + title = DEFAULT_TITLE, + isInlineEditing, + SettingTrigger, +}: { + title?: string; + siblingRef: MutableRefObject; + SettingTrigger: ({ onClick }: { onClick: () => void }) => JSX.Element; + children?: React.ReactElement | React.ReactElement[]; + isInlineEditing?: boolean; +}) { + const [focusTrapIsEnabled, setFocusTrapIsEnabled] = useState(false); + const [isFlyoutOpen, setIsFlyoutOpen] = useState(false); + + const toggleFlyout = () => { + setIsFlyoutOpen(!isFlyoutOpen); + }; + + const closeFlyout = () => { + setIsFlyoutOpen(false); + setFocusTrapIsEnabled(false); + }; + + useEffect(() => { + // The EuiFocusTrap is disabled when inline editing as it causes bugs with comboboxes + if (isFlyoutOpen && !isInlineEditing) { + // without setTimeout here the flyout pushes content when animating + setTimeout(() => { + setFocusTrapIsEnabled(true); + }, 255); + } + }, [isInlineEditing, isFlyoutOpen]); + + return ( + + + {isFlyoutOpen && siblingRef.current && ( + + + +
+ + + + + + + +

+ {title} +

+
+
+
+
+ + {children &&
{children}
} + + + + {i18n.translate('xpack.lens.settingWithSiblingFlyout.back', { + defaultMessage: 'Back', + })} + + +
+
+
+
+ )} +
+ ); +} diff --git a/x-pack/plugins/lens/public/visualizations/datatable/components/dimension_editor.scss b/x-pack/plugins/lens/public/visualizations/datatable/components/dimension_editor.scss index 504adb05e57d7..ef998626f6127 100644 --- a/x-pack/plugins/lens/public/visualizations/datatable/components/dimension_editor.scss +++ b/x-pack/plugins/lens/public/visualizations/datatable/components/dimension_editor.scss @@ -1,7 +1,3 @@ .lnsDynamicColoringRow { align-items: center; } - -.lnsDynamicColoringClickable { - cursor: pointer; -} \ No newline at end of file diff --git a/x-pack/plugins/lens/public/visualizations/datatable/components/dimension_editor.test.tsx b/x-pack/plugins/lens/public/visualizations/datatable/components/dimension_editor.test.tsx index 5bad424f5d208..cec781a905b44 100644 --- a/x-pack/plugins/lens/public/visualizations/datatable/components/dimension_editor.test.tsx +++ b/x-pack/plugins/lens/public/visualizations/datatable/components/dimension_editor.test.tsx @@ -142,9 +142,7 @@ describe('data table dimension editor', () => { expect(instance.find('[data-test-subj="lnsDatatable_dynamicColoring_groups"]').exists()).toBe( false ); - expect(instance.find('[data-test-subj="lnsDatatable_dynamicColoring_palette"]').exists()).toBe( - false - ); + expect(instance.find('[data-test-subj="lns_dynamicColoring_edit"]').exists()).toBe(false); }); it('should set the dynamic coloring default to "none"', () => { @@ -157,9 +155,7 @@ describe('data table dimension editor', () => { .prop('idSelected') ).toEqual(expect.stringContaining('none')); - expect(instance.find('[data-test-subj="lnsDatatable_dynamicColoring_palette"]').exists()).toBe( - false - ); + expect(instance.find('[data-test-subj="lns_dynamicColoring_edit"]').exists()).toBe(false); }); it('should show the dynamic palette display ony when colorMode is different from "none"', () => { @@ -173,9 +169,7 @@ describe('data table dimension editor', () => { .prop('idSelected') ).toEqual(expect.stringContaining('text')); - expect(instance.find('[data-test-subj="lnsDatatable_dynamicColoring_palette"]').exists()).toBe( - true - ); + expect(instance.find('[data-test-subj="lns_dynamicColoring_edit"]').exists()).toBe(true); }); it('should set the coloring mode to the right column', () => { @@ -214,10 +208,7 @@ describe('data table dimension editor', () => { const instance = mountWithIntl(); act(() => { - instance - .find('[data-test-subj="lnsDatatable_dynamicColoring_trigger"]') - .first() - .simulate('click'); + instance.find('[data-test-subj="lns_colorEditing_trigger"]').first().simulate('click'); }); expect(instance.find(PalettePanelContainer).exists()).toBe(true); @@ -235,9 +226,7 @@ describe('data table dimension editor', () => { expect(instance.find('[data-test-subj="lnsDatatable_dynamicColoring_groups"]').exists()).toBe( false ); - expect(instance.find('[data-test-subj="lnsDatatable_dynamicColoring_palette"]').exists()).toBe( - false - ); + expect(instance.find('[data-test-subj="lns_dynamicColoring_edit"]').exists()).toBe(false); }); it('should show the summary field for non numeric columns', () => { diff --git a/x-pack/plugins/lens/public/visualizations/datatable/components/dimension_editor.tsx b/x-pack/plugins/lens/public/visualizations/datatable/components/dimension_editor.tsx index a26e4e64f06c2..f431c757c3c73 100644 --- a/x-pack/plugins/lens/public/visualizations/datatable/components/dimension_editor.tsx +++ b/x-pack/plugins/lens/public/visualizations/datatable/components/dimension_editor.tsx @@ -5,19 +5,10 @@ * 2.0. */ -import React, { useState } from 'react'; +import React from 'react'; import { i18n } from '@kbn/i18n'; -import { - EuiFormRow, - EuiSwitch, - EuiButtonGroup, - htmlIdGenerator, - EuiColorPaletteDisplay, - EuiFlexItem, - EuiFlexGroup, - EuiButtonEmpty, -} from '@elastic/eui'; -import { CustomizablePalette, PaletteRegistry, FIXED_PROGRESSION } from '@kbn/coloring'; +import { EuiFormRow, EuiSwitch, EuiButtonGroup, htmlIdGenerator } from '@elastic/eui'; +import { CustomizablePalette, PaletteRegistry } from '@kbn/coloring'; import type { VisualizationDimensionEditorProps } from '../../../types'; import type { DatatableVisualizationState } from '../visualization'; @@ -58,7 +49,6 @@ export function TableDimensionEditor( ) { const { state, setState, frame, accessor, isInlineEditing } = props; const column = state.columns.find(({ columnId }) => accessor === columnId); - const [isPaletteOpen, setIsPaletteOpen] = useState(false); if (!column) return null; if (column.isTransposed) return null; @@ -219,59 +209,23 @@ export function TableDimensionEditor( defaultMessage: 'Color', })} > - color)} + siblingRef={props.panelRef} + isInlineEditing={isInlineEditing} > - - color)} - type={FIXED_PROGRESSION} - onClick={() => { - setIsPaletteOpen(!isPaletteOpen); - }} - /> - - - { - setIsPaletteOpen(!isPaletteOpen); - }} - size="xs" - flush="both" - > - {i18n.translate('xpack.lens.paletteTableGradient.customize', { - defaultMessage: 'Edit', - })} - - setIsPaletteOpen(!isPaletteOpen)} - title={i18n.translate('xpack.lens.table.colorByRangePanelTitle', { - defaultMessage: 'Color', - })} - isInlineEditing={isInlineEditing} - > - { - setState({ - ...state, - columns: updateColumnWith(state, accessor, { palette: newPalette }), - }); - }} - /> - - - + { + setState({ + ...state, + columns: updateColumnWith(state, accessor, { palette: newPalette }), + }); + }} + /> + )} diff --git a/x-pack/plugins/lens/public/visualizations/gauge/dimension_editor.tsx b/x-pack/plugins/lens/public/visualizations/gauge/dimension_editor.tsx index 8bd02702c841a..86eaf39479ff5 100644 --- a/x-pack/plugins/lens/public/visualizations/gauge/dimension_editor.tsx +++ b/x-pack/plugins/lens/public/visualizations/gauge/dimension_editor.tsx @@ -5,24 +5,10 @@ * 2.0. */ -import { - EuiButtonEmpty, - EuiFlexGroup, - EuiColorPaletteDisplay, - EuiFormRow, - EuiFlexItem, - EuiSwitchEvent, - EuiSwitch, - EuiIcon, -} from '@elastic/eui'; -import React, { useState } from 'react'; +import { EuiFormRow, EuiSwitchEvent, EuiSwitch, EuiIcon } from '@elastic/eui'; +import React from 'react'; import { i18n } from '@kbn/i18n'; -import { - PaletteRegistry, - CustomizablePalette, - CUSTOM_PALETTE, - FIXED_PROGRESSION, -} from '@kbn/coloring'; +import { PaletteRegistry, CustomizablePalette, CUSTOM_PALETTE } from '@kbn/coloring'; import { GaugeTicksPositions, GaugeColorModes } from '@kbn/expression-gauge-plugin/common'; import { getMaxValue, getMinValue } from '@kbn/expression-gauge-plugin/public'; import { TooltipWrapper } from '@kbn/visualization-utils'; @@ -41,7 +27,6 @@ export function GaugeDimensionEditor( } ) { const { state, setState, frame, accessor, isInlineEditing } = props; - const [isPaletteOpen, setIsPaletteOpen] = useState(false); if (state?.metricAccessor !== accessor) return null; @@ -76,7 +61,6 @@ export function GaugeDimensionEditor( const displayStops = applyPaletteParams(props.paletteService, activePalette, currentMinMax); - const togglePalette = () => setIsPaletteOpen(!isPaletteOpen); return ( <> - color)} + siblingRef={props.panelRef} + isInlineEditing={isInlineEditing} > - - color)} - type={FIXED_PROGRESSION} - onClick={togglePalette} - /> - - - - {i18n.translate('xpack.lens.paletteTableGradient.customize', { - defaultMessage: 'Edit', - })} - - - { - // if the new palette is not custom, replace the rangeMin with the artificial one - if ( - newPalette.name !== CUSTOM_PALETTE && - newPalette.params && - newPalette.params.rangeMin !== currentMinMax.min - ) { - newPalette.params.rangeMin = currentMinMax.min; - } - setState({ - ...state, - palette: newPalette, - }); - }} - /> - - - + { + // if the new palette is not custom, replace the rangeMin with the artificial one + if ( + newPalette.name !== CUSTOM_PALETTE && + newPalette.params && + newPalette.params.rangeMin !== currentMinMax.min + ) { + newPalette.params.rangeMin = currentMinMax.min; + } + setState({ + ...state, + palette: newPalette, + }); + }} + /> + - color)} + siblingRef={props.panelRef} + isInlineEditing={isInlineEditing} > - - color)} - type={FIXED_PROGRESSION} - onClick={() => { - setIsPaletteOpen(!isPaletteOpen); + {activePalette && ( + { + // make sure to always have a list of stops + if (newPalette.params && !newPalette.params.stops) { + newPalette.params.stops = displayStops; + } + (newPalette as HeatmapVisualizationState['palette'])!.accessor = accessor; + setState({ + ...state, + palette: newPalette as HeatmapVisualizationState['palette'], + }); }} /> - - - { - setIsPaletteOpen(!isPaletteOpen); - }} - size="xs" - flush="both" - > - {i18n.translate('xpack.lens.paletteHeatmapGradient.customize', { - defaultMessage: 'Edit', - })} - - setIsPaletteOpen(!isPaletteOpen)} - title={i18n.translate('xpack.lens.table.colorByRangePanelTitle', { - defaultMessage: 'Color', - })} - isInlineEditing={isInlineEditing} - > - {activePalette && ( - { - // make sure to always have a list of stops - if (newPalette.params && !newPalette.params.stops) { - newPalette.params.stops = displayStops; - } - (newPalette as HeatmapVisualizationState['palette'])!.accessor = accessor; - setState({ - ...state, - palette: newPalette as HeatmapVisualizationState['palette'], - }); - }} - /> - )} - - - + )} + ); diff --git a/x-pack/plugins/lens/public/visualizations/legacy_metric/dimension_editor.test.tsx b/x-pack/plugins/lens/public/visualizations/legacy_metric/dimension_editor.test.tsx index 5ed76f0322194..b4ac97cd5edd7 100644 --- a/x-pack/plugins/lens/public/visualizations/legacy_metric/dimension_editor.test.tsx +++ b/x-pack/plugins/lens/public/visualizations/legacy_metric/dimension_editor.test.tsx @@ -94,9 +94,7 @@ describe('metric dimension editor', () => { expect( instance.find('[data-test-subj="lnsLegacyMetric_dynamicColoring_groups"]').exists() ).toBe(false); - expect( - instance.find('[data-test-subj="lnsLegacyMetric_dynamicColoring_palette"]').exists() - ).toBe(false); + expect(instance.find('[data-test-subj="lns_dynamicColoring_edit"]').exists()).toBe(false); }); it('should set the dynamic coloring default to "none"', () => { @@ -109,9 +107,7 @@ describe('metric dimension editor', () => { .prop('idSelected') ).toEqual(expect.stringContaining(ColorMode.None)); - expect( - instance.find('[data-test-subj="lnsLegacyMetric_dynamicColoring_palette"]').exists() - ).toBe(false); + expect(instance.find('[data-test-subj="lns_dynamicColoring_edit"]').exists()).toBe(false); }); it('should show the dynamic palette display ony when colorMode is different from "none"', () => { @@ -125,9 +121,7 @@ describe('metric dimension editor', () => { .prop('idSelected') ).toEqual(expect.stringContaining(ColorMode.Labels)); - expect( - instance.find('[data-test-subj="lnsLegacyMetric_dynamicColoring_palette"]').exists() - ).toBe(true); + expect(instance.find('[data-test-subj="lns_dynamicColoring_edit"]').exists()).toBe(true); }); it('should prefill the palette stops with some colors when enabling coloring', () => { @@ -155,10 +149,7 @@ describe('metric dimension editor', () => { const instance = mountWithIntl(); act(() => { - instance - .find('[data-test-subj="lnsLegacyMetric_dynamicColoring_trigger"]') - .first() - .simulate('click'); + instance.find('[data-test-subj="lns_colorEditing_trigger"]').first().simulate('click'); }); instance.update(); @@ -172,10 +163,7 @@ describe('metric dimension editor', () => { const instance = mountWithIntl(); act(() => { - instance - .find('[data-test-subj="lnsLegacyMetric_dynamicColoring_trigger"]') - .first() - .simulate('click'); + instance.find('[data-test-subj="lns_colorEditing_trigger"]').first().simulate('click'); }); instance.update(); @@ -189,10 +177,7 @@ describe('metric dimension editor', () => { const instance = mountWithIntl(); act(() => { - instance - .find('[data-test-subj="lnsLegacyMetric_dynamicColoring_trigger"]') - .first() - .simulate('click'); + instance.find('[data-test-subj="lns_colorEditing_trigger"]').first().simulate('click'); }); instance.update(); diff --git a/x-pack/plugins/lens/public/visualizations/legacy_metric/dimension_editor.tsx b/x-pack/plugins/lens/public/visualizations/legacy_metric/dimension_editor.tsx index 0846959548b53..832118a899ae7 100644 --- a/x-pack/plugins/lens/public/visualizations/legacy_metric/dimension_editor.tsx +++ b/x-pack/plugins/lens/public/visualizations/legacy_metric/dimension_editor.tsx @@ -4,23 +4,10 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import { - EuiButtonEmpty, - EuiButtonGroup, - EuiColorPaletteDisplay, - EuiFlexGroup, - EuiFlexItem, - EuiFormRow, - htmlIdGenerator, -} from '@elastic/eui'; -import { - PaletteRegistry, - CustomizablePalette, - CUSTOM_PALETTE, - FIXED_PROGRESSION, -} from '@kbn/coloring'; +import { EuiButtonGroup, EuiFormRow, htmlIdGenerator } from '@elastic/eui'; +import { PaletteRegistry, CustomizablePalette, CUSTOM_PALETTE } from '@kbn/coloring'; import { i18n } from '@kbn/i18n'; -import React, { useCallback, useState } from 'react'; +import React from 'react'; import { ColorMode } from '@kbn/charts-plugin/common'; import type { LegacyMetricState } from '../../../common/types'; import { isNumericFieldForDatatable } from '../../../common/expressions/datatable/utils'; @@ -38,11 +25,6 @@ export function MetricDimensionEditor( } ) { const { state, setState, frame, accessor, isInlineEditing } = props; - const [isPaletteOpen, setIsPaletteOpen] = useState(false); - - const togglePalette = useCallback(() => { - setIsPaletteOpen(!isPaletteOpen); - }, [isPaletteOpen]); const currentData = frame.activeData?.[state.layerId]; const [firstRow] = currentData?.rows || []; @@ -155,64 +137,32 @@ export function MetricDimensionEditor( defaultMessage: 'Color', })} > - color)} + siblingRef={props.panelRef} + isInlineEditing={isInlineEditing} > - - color)} - type={FIXED_PROGRESSION} - onClick={togglePalette} - /> - - - - {i18n.translate('xpack.lens.paletteTableGradient.customize', { - defaultMessage: 'Edit', - })} - - - { - // if the new palette is not custom, replace the rangeMin with the artificial one - if ( - newPalette.name !== CUSTOM_PALETTE && - newPalette.params && - newPalette.params.rangeMin !== currentMinMax.min - ) { - newPalette.params.rangeMin = currentMinMax.min; - } - setState({ - ...state, - palette: newPalette, - }); - }} - showRangeTypeSelector={false} - /> - - - + { + // if the new palette is not custom, replace the rangeMin with the artificial one + if ( + newPalette.name !== CUSTOM_PALETTE && + newPalette.params && + newPalette.params.rangeMin !== currentMinMax.min + ) { + newPalette.params.rangeMin = currentMinMax.min; + } + setState({ + ...state, + palette: newPalette, + }); + }} + showRangeTypeSelector={false} + /> + )} diff --git a/x-pack/plugins/lens/public/visualizations/metric/dimension_editor.tsx b/x-pack/plugins/lens/public/visualizations/metric/dimension_editor.tsx index 6e71d21519231..f9b9fd9e1b363 100644 --- a/x-pack/plugins/lens/public/visualizations/metric/dimension_editor.tsx +++ b/x-pack/plugins/lens/public/visualizations/metric/dimension_editor.tsx @@ -6,11 +6,7 @@ */ import { - EuiButtonEmpty, - EuiFlexGroup, - EuiColorPaletteDisplay, EuiFormRow, - EuiFlexItem, EuiButtonGroup, EuiFieldNumber, htmlIdGenerator, @@ -21,12 +17,11 @@ import { useEuiTheme, } from '@elastic/eui'; import { LayoutDirection } from '@elastic/charts'; -import React, { useCallback, useState } from 'react'; +import React, { useCallback } from 'react'; import { i18n } from '@kbn/i18n'; import { PaletteRegistry, CustomizablePalette, - FIXED_PROGRESSION, DEFAULT_MAX_STOP, DEFAULT_MIN_STOP, } from '@kbn/coloring'; @@ -220,8 +215,6 @@ function SecondaryMetricEditor({ accessor, idPrefix, frame, layerId, setState, s function PrimaryMetricEditor(props: SubProps) { const { state, setState, frame, accessor, idPrefix, isInlineEditing } = props; - const [isPaletteOpen, setIsPaletteOpen] = useState(false); - const currentData = frame.activeData?.[state.layerId]; const isMetricNumeric = isNumericFieldForDatatable(currentData, accessor); @@ -262,8 +255,6 @@ function PrimaryMetricEditor(props: SubProps) { max: currentMinMax.max ?? DEFAULT_MAX_STOP, }); - const togglePalette = () => setIsPaletteOpen(!isPaletteOpen); - return ( <> {isMetricNumeric && ( @@ -334,56 +325,24 @@ function PrimaryMetricEditor(props: SubProps) { defaultMessage: 'Color', })} > - color)} + siblingRef={props.panelRef} + isInlineEditing={isInlineEditing} > - - color)} - type={FIXED_PROGRESSION} - onClick={togglePalette} - /> - - - - {i18n.translate('xpack.lens.paletteTableGradient.customize', { - defaultMessage: 'Edit', - })} - - - { - setState({ - ...state, - palette: newPalette, - }); - }} - /> - - - + { + setState({ + ...state, + palette: newPalette, + }); + }} + /> + )} layer.layerId === props.layerId); @@ -142,109 +132,76 @@ export function DimensionEditor(props: DimensionEditorProps) { style={{ alignItems: 'center' }} fullWidth > - - - { - setIsPaletteOpen(!isPaletteOpen); - }} - /> - - - { - setIsPaletteOpen(!isPaletteOpen); - }} - size="xs" - /> - setIsPaletteOpen(!isPaletteOpen)} - title={ - useNewColorMapping - ? i18n.translate('xpack.lens.colorMapping.editColorMappingTitle', { - defaultMessage: 'Edit colors by term mapping', - }) - : i18n.translate('xpack.lens.colorMapping.editColorsTitle', { - defaultMessage: 'Edit colors', - }) - } - isInlineEditing={props.isInlineEditing} - > -
- - - - - {i18n.translate('xpack.lens.colorMapping.tryLabel', { - defaultMessage: 'Use the new Color Mapping feature', - })}{' '} - - {i18n.translate('xpack.lens.colorMapping.techPreviewLabel', { - defaultMessage: 'Tech preview', - })} - - - - } - data-test-subj="lns_colorMappingOrLegacyPalette_switch" - compressed - checked={useNewColorMapping} - onChange={({ target: { checked } }) => { - trackUiCounterEvents( - `color_mapping_switch_${checked ? 'enabled' : 'disabled'}` - ); - setColorMapping( - checked ? { ...DEFAULT_COLOR_MAPPING_CONFIG } : undefined - ); - setUseNewColorMapping(checked); - }} - /> - - - {canUseColorMapping || useNewColorMapping ? ( - setColorMapping(model)} - palettes={AVAILABLE_PALETTES} - data={{ - type: 'categories', - categories: splitCategories, - }} - specialTokens={SPECIAL_TOKENS_STRING_CONVERTION} - /> - ) : ( - { - setLocalState({ ...props.state, palette: newPalette }); - }} - /> - )} - - -
-
-
-
+
+ + + + + {i18n.translate('xpack.lens.colorMapping.tryLabel', { + defaultMessage: 'Use the new Color Mapping feature', + })}{' '} + + {i18n.translate('xpack.lens.colorMapping.techPreviewLabel', { + defaultMessage: 'Tech preview', + })} + + + + } + data-test-subj="lns_colorMappingOrLegacyPalette_switch" + compressed + checked={useNewColorMapping} + onChange={({ target: { checked } }) => { + trackUiCounterEvents( + `color_mapping_switch_${checked ? 'enabled' : 'disabled'}` + ); + setColorMapping(checked ? { ...DEFAULT_COLOR_MAPPING_CONFIG } : undefined); + setUseNewColorMapping(checked); + }} + /> + + + {canUseColorMapping || useNewColorMapping ? ( + setColorMapping(model)} + palettes={AVAILABLE_PALETTES} + data={{ + type: 'categories', + categories: splitCategories, + }} + specialTokens={SPECIAL_TOKENS_STRING_CONVERTION} + /> + ) : ( + { + setLocalState({ ...props.state, palette: newPalette }); + }} + /> + )} + + +
+
)} {/* TODO: understand how this works */} diff --git a/x-pack/plugins/lens/public/visualizations/tagcloud/tags_dimension_editor.tsx b/x-pack/plugins/lens/public/visualizations/tagcloud/tags_dimension_editor.tsx index 3495d86187ef8..77fab257eb03b 100644 --- a/x-pack/plugins/lens/public/visualizations/tagcloud/tags_dimension_editor.tsx +++ b/x-pack/plugins/lens/public/visualizations/tagcloud/tags_dimension_editor.tsx @@ -17,16 +17,7 @@ import { getColorsFromMapping, } from '@kbn/coloring'; import { i18n } from '@kbn/i18n'; -import { - EuiButtonIcon, - EuiColorPaletteDisplay, - EuiFlexGroup, - EuiFlexItem, - EuiSwitch, - EuiFormRow, - EuiText, - EuiBadge, -} from '@elastic/eui'; +import { EuiFlexGroup, EuiFlexItem, EuiSwitch, EuiFormRow, EuiText, EuiBadge } from '@elastic/eui'; import { useState, MutableRefObject, useCallback } from 'react'; import { useDebouncedValue } from '@kbn/visualization-ui-components'; import { getColorCategories } from '@kbn/chart-expressions-common'; @@ -59,7 +50,6 @@ export function TagsDimensionEditor({ value: state, onChange: setState, }); - const [isPaletteOpen, setIsPaletteOpen] = useState(false); const [useNewColorMapping, setUseNewColorMapping] = useState(state.colorMapping ? true : false); const colors = getColorsFromMapping(isDarkMode, state.colorMapping); @@ -98,107 +88,74 @@ export function TagsDimensionEditor({ style={{ alignItems: 'center' }} fullWidth > - - - { - setIsPaletteOpen(!isPaletteOpen); - }} - /> - - - { - setIsPaletteOpen(!isPaletteOpen); - }} - size="xs" - /> - setIsPaletteOpen(!isPaletteOpen)} - title={ - useNewColorMapping - ? i18n.translate('xpack.lens.colorMapping.editColorMappingTitle', { - defaultMessage: 'Edit colors by term mapping', - }) - : i18n.translate('xpack.lens.colorMapping.editColorsTitle', { - defaultMessage: 'Edit colors', - }) - } - isInlineEditing={isInlineEditing} - > -
- - - - - {i18n.translate('xpack.lens.colorMapping.tryLabel', { - defaultMessage: 'Use the new Color Mapping feature', - })}{' '} - - {i18n.translate('xpack.lens.colorMapping.techPreviewLabel', { - defaultMessage: 'Tech preview', - })} - - - - } - data-test-subj="lns_colorMappingOrLegacyPalette_switch" - compressed - checked={useNewColorMapping} - onChange={({ target: { checked } }) => { - trackUiCounterEvents( - `color_mapping_switch_${checked ? 'enabled' : 'disabled'}` - ); - setColorMapping(checked ? { ...DEFAULT_COLOR_MAPPING_CONFIG } : undefined); - setUseNewColorMapping(checked); - }} - /> - - - {canUseColorMapping || useNewColorMapping ? ( - setColorMapping(model)} - palettes={AVAILABLE_PALETTES} - data={{ - type: 'categories', - categories: splitCategories, - }} - specialTokens={SPECIAL_TOKENS_STRING_CONVERTION} - /> - ) : ( - { - setPalette(newPalette); - }} - /> - )} - - -
-
-
-
+
+ + + + + {i18n.translate('xpack.lens.colorMapping.tryLabel', { + defaultMessage: 'Use the new Color Mapping feature', + })}{' '} + + {i18n.translate('xpack.lens.colorMapping.techPreviewLabel', { + defaultMessage: 'Tech preview', + })} + + + + } + data-test-subj="lns_colorMappingOrLegacyPalette_switch" + compressed + checked={useNewColorMapping} + onChange={({ target: { checked } }) => { + trackUiCounterEvents(`color_mapping_switch_${checked ? 'enabled' : 'disabled'}`); + setColorMapping(checked ? { ...DEFAULT_COLOR_MAPPING_CONFIG } : undefined); + setUseNewColorMapping(checked); + }} + /> + + + {canUseColorMapping || useNewColorMapping ? ( + setColorMapping(model)} + palettes={AVAILABLE_PALETTES} + data={{ + type: 'categories', + categories: splitCategories, + }} + specialTokens={SPECIAL_TOKENS_STRING_CONVERTION} + /> + ) : ( + { + setPalette(newPalette); + }} + /> + )} + + +
+ ); } diff --git a/x-pack/plugins/lens/public/visualizations/xy/load_annotation_library_flyout.tsx b/x-pack/plugins/lens/public/visualizations/xy/load_annotation_library_flyout.tsx index 098d76e7f9ad4..4dbbefdd243c8 100644 --- a/x-pack/plugins/lens/public/visualizations/xy/load_annotation_library_flyout.tsx +++ b/x-pack/plugins/lens/public/visualizations/xy/load_annotation_library_flyout.tsx @@ -61,12 +61,11 @@ export function LoadAnnotationLibraryFlyout({ } isOpen={Boolean(isLoadLibraryVisible)} - groupLabel={i18n.translate('xpack.lens.editorFrame.loadFromLibrary', { + label={i18n.translate('xpack.lens.editorFrame.loadFromLibrary', { defaultMessage: 'Select annotations from library', })} handleClose={() => { setLoadLibraryFlyoutVisible(false); - return true; }} isInlineEditing={isInlineEditing} > diff --git a/x-pack/plugins/lens/public/visualizations/xy/xy_config_panel/dimension_editor.tsx b/x-pack/plugins/lens/public/visualizations/xy/xy_config_panel/dimension_editor.tsx index 03dcc64d44297..e2ecac16daee1 100644 --- a/x-pack/plugins/lens/public/visualizations/xy/xy_config_panel/dimension_editor.tsx +++ b/x-pack/plugins/lens/public/visualizations/xy/xy_config_panel/dimension_editor.tsx @@ -13,8 +13,6 @@ import { ColorPicker } from '@kbn/visualization-ui-components'; import { EuiBadge, EuiButtonGroup, - EuiButtonIcon, - EuiColorPaletteDisplay, EuiFlexGroup, EuiFlexItem, EuiFormRow, @@ -75,7 +73,6 @@ export function DataDimensionEditor( const layer = state.layers[index] as XYDataLayerConfig; const canUseColorMapping = layer.colorMapping ? true : false; - const [isPaletteOpen, setIsPaletteOpen] = useState(false); const [useNewColorMapping, setUseNewColorMapping] = useState(canUseColorMapping); const { inputValue: localState, handleInputChange: setLocalState } = useDebouncedValue({ @@ -168,108 +165,77 @@ export function DataDimensionEditor( style={{ alignItems: 'center' }} fullWidth > - - - { - setIsPaletteOpen(!isPaletteOpen); - }} - /> - - - { - setIsPaletteOpen(!isPaletteOpen); - }} - size="xs" - /> - setIsPaletteOpen(!isPaletteOpen)} - title={ - useNewColorMapping - ? i18n.translate('xpack.lens.colorMapping.editColorMappingTitle', { - defaultMessage: 'Edit colors by term mapping', - }) - : i18n.translate('xpack.lens.colorMapping.editColorsTitle', { - defaultMessage: 'Edit colors', - }) - } - isInlineEditing={isInlineEditing} - > -
- - - - - {i18n.translate('xpack.lens.colorMapping.tryLabel', { - defaultMessage: 'Use the new Color Mapping feature', - })}{' '} - - {i18n.translate('xpack.lens.colorMapping.techPreviewLabel', { - defaultMessage: 'Tech preview', - })} - - - - } - data-test-subj="lns_colorMappingOrLegacyPalette_switch" - compressed - checked={useNewColorMapping} - onChange={({ target: { checked } }) => { - trackUiCounterEvents( - `color_mapping_switch_${checked ? 'enabled' : 'disabled'}` - ); - setColorMapping(checked ? { ...DEFAULT_COLOR_MAPPING_CONFIG } : undefined); - setUseNewColorMapping(checked); - }} - /> - - - - {canUseColorMapping || useNewColorMapping ? ( - setColorMapping(model)} - palettes={AVAILABLE_PALETTES} - data={{ - type: 'categories', - categories: splitCategories, - }} - specialTokens={SPECIAL_TOKENS_STRING_CONVERTION} - /> - ) : ( - { - setPalette(newPalette); - }} - /> - )} - - -
-
-
-
+
+ + + + + {i18n.translate('xpack.lens.colorMapping.tryLabel', { + defaultMessage: 'Use the new Color Mapping feature', + })}{' '} + + {i18n.translate('xpack.lens.colorMapping.techPreviewLabel', { + defaultMessage: 'Tech preview', + })} + + + + } + data-test-subj="lns_colorMappingOrLegacyPalette_switch" + compressed + checked={useNewColorMapping} + onChange={({ target: { checked } }) => { + trackUiCounterEvents( + `color_mapping_switch_${checked ? 'enabled' : 'disabled'}` + ); + setColorMapping(checked ? { ...DEFAULT_COLOR_MAPPING_CONFIG } : undefined); + setUseNewColorMapping(checked); + }} + /> + + + + {canUseColorMapping || useNewColorMapping ? ( + setColorMapping(model)} + palettes={AVAILABLE_PALETTES} + data={{ + type: 'categories', + categories: splitCategories, + }} + specialTokens={SPECIAL_TOKENS_STRING_CONVERTION} + /> + ) : ( + { + setPalette(newPalette); + }} + /> + )} + + +
+ ); } diff --git a/x-pack/plugins/translations/translations/fr-FR.json b/x-pack/plugins/translations/translations/fr-FR.json index ab9573719e9dc..2d22932853fa5 100644 --- a/x-pack/plugins/translations/translations/fr-FR.json +++ b/x-pack/plugins/translations/translations/fr-FR.json @@ -21473,7 +21473,6 @@ "xpack.lens.app.updatePanel": "Mettre à jour le panneau sur {originatingAppName}", "xpack.lens.breadcrumbsEditInLensFromDashboard": "Conversion de la visualisation {title}", "xpack.lens.chartSwitch.noResults": "Résultats introuvables pour {term}.", - "xpack.lens.configure.configurePanelTitle": "{groupLabel}", "xpack.lens.configure.suggestedValuee": "Valeur suggérée : {value}", "xpack.lens.confirmModal.saveDuplicateButtonLabel": "Enregistrer {name}", "xpack.lens.datatable.visualizationOf": "Tableau {operations}", @@ -21678,7 +21677,6 @@ "xpack.lens.collapse.min": "Min.", "xpack.lens.collapse.none": "Aucun", "xpack.lens.collapse.sum": "Somme", - "xpack.lens.colorMapping.editColorMappingButton": "Modifier la palette", "xpack.lens.colorMapping.editColorMappingSectionlabel": "Mapping des couleurs", "xpack.lens.colorMapping.editColorMappingTitle": "Modifier les couleurs par mapping de terme", "xpack.lens.colorMapping.editColorsTitle": "Modifier les couleurs", @@ -22233,12 +22231,9 @@ "xpack.lens.modalTitle.title.deleteReferenceLines": "Supprimer le calque de lignes de référence ?", "xpack.lens.modalTitle.title.deleteVis": "Supprimer le calque de visualisation ?", "xpack.lens.pageTitle": "Lens", - "xpack.lens.paletteHeatmapGradient.customize": "Modifier", - "xpack.lens.paletteHeatmapGradient.customizeLong": "Modifier la palette", "xpack.lens.paletteHeatmapGradient.label": "Couleur", "xpack.lens.paletteMetricGradient.label": "Couleur", "xpack.lens.palettePicker.label": "Palette", - "xpack.lens.paletteTableGradient.customize": "Modifier", "xpack.lens.paletteTableGradient.label": "Couleur", "xpack.lens.pie.addLayer": "Visualisation", "xpack.lens.pie.collapsedDimensionsDontCount": "(Les dimensions réduites ne sont pas concernées par cette limite.)", @@ -22348,7 +22343,7 @@ "xpack.lens.table.alignment.label": "Alignement du texte", "xpack.lens.table.alignment.left": "Gauche", "xpack.lens.table.alignment.right": "Droite", - "xpack.lens.table.colorByRangePanelTitle": "Couleur", + "xpack.lens.colorSiblingFlyoutTitle": "Couleur", "xpack.lens.table.columnFilter.filterForValueText": "Filtrer sur", "xpack.lens.table.columnFilter.filterOutValueText": "Exclure", "xpack.lens.table.columnFilterClickLabel": "Filtrer directement avec un clic", @@ -22359,7 +22354,7 @@ "xpack.lens.table.dynamicColoring.none": "Aucun", "xpack.lens.table.dynamicColoring.text": "Texte", "xpack.lens.table.hide.hideLabel": "Masquer", - "xpack.lens.table.palettePanelContainer.back": "Retour", + "xpack.lens.settingWithSiblingFlyout.back": "Retour", "xpack.lens.table.resize.reset": "Réinitialiser la largeur", "xpack.lens.table.sort.ascLabel": "Trier dans l'ordre croissant", "xpack.lens.table.sort.descLabel": "Trier dans l'ordre décroissant", diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index d590331781da8..cdcd915ba92ab 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -21487,7 +21487,6 @@ "xpack.lens.app.updatePanel": "{originatingAppName}でパネルを更新", "xpack.lens.breadcrumbsEditInLensFromDashboard": "{title}ビジュアライゼーションを変換中", "xpack.lens.chartSwitch.noResults": "{term}の結果が見つかりませんでした。", - "xpack.lens.configure.configurePanelTitle": "{groupLabel}", "xpack.lens.configure.suggestedValuee": "候補の値:{value}", "xpack.lens.confirmModal.saveDuplicateButtonLabel": "{name}を保存", "xpack.lens.datatable.visualizationOf": "表{operations}", @@ -21692,7 +21691,6 @@ "xpack.lens.collapse.min": "最低", "xpack.lens.collapse.none": "なし", "xpack.lens.collapse.sum": "合計", - "xpack.lens.colorMapping.editColorMappingButton": "パレットを編集", "xpack.lens.colorMapping.editColorMappingSectionlabel": "カラーマッピング", "xpack.lens.colorMapping.editColorMappingTitle": "用語マッピングで色を編集", "xpack.lens.colorMapping.editColorsTitle": "色を編集", @@ -22247,12 +22245,9 @@ "xpack.lens.modalTitle.title.deleteReferenceLines": "基準線レイヤーを削除しますか?", "xpack.lens.modalTitle.title.deleteVis": "ビジュアライゼーションレイヤーを削除しますか?", "xpack.lens.pageTitle": "レンズ", - "xpack.lens.paletteHeatmapGradient.customize": "編集", - "xpack.lens.paletteHeatmapGradient.customizeLong": "パレットを編集", "xpack.lens.paletteHeatmapGradient.label": "色", "xpack.lens.paletteMetricGradient.label": "色", "xpack.lens.palettePicker.label": "パレット", - "xpack.lens.paletteTableGradient.customize": "編集", "xpack.lens.paletteTableGradient.label": "色", "xpack.lens.pie.addLayer": "ビジュアライゼーション", "xpack.lens.pie.collapsedDimensionsDontCount": "(折りたたまれたディメンションはこの制限に対してカウントされません。)", @@ -22362,7 +22357,7 @@ "xpack.lens.table.alignment.label": "テキスト配置", "xpack.lens.table.alignment.left": "左", "xpack.lens.table.alignment.right": "右", - "xpack.lens.table.colorByRangePanelTitle": "色", + "xpack.lens.colorSiblingFlyoutTitle": "色", "xpack.lens.table.columnFilter.filterForValueText": "フィルター", "xpack.lens.table.columnFilter.filterOutValueText": "除外", "xpack.lens.table.columnFilterClickLabel": "クリック時に直接フィルター", @@ -22373,7 +22368,7 @@ "xpack.lens.table.dynamicColoring.none": "なし", "xpack.lens.table.dynamicColoring.text": "テキスト", "xpack.lens.table.hide.hideLabel": "非表示", - "xpack.lens.table.palettePanelContainer.back": "戻る", + "xpack.lens.settingWithSiblingFlyout.back": "戻る", "xpack.lens.table.resize.reset": "幅のリセット", "xpack.lens.table.sort.ascLabel": "昇順に並べ替える", "xpack.lens.table.sort.descLabel": "降順に並べ替える", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index c849a71a75e09..1ce0f06025276 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -21580,7 +21580,6 @@ "xpack.lens.app.updatePanel": "更新 {originatingAppName} 中的面板", "xpack.lens.breadcrumbsEditInLensFromDashboard": "正在转换 {title} 可视化", "xpack.lens.chartSwitch.noResults": "找不到 {term} 的结果。", - "xpack.lens.configure.configurePanelTitle": "{groupLabel}", "xpack.lens.configure.suggestedValuee": "建议值:{value}", "xpack.lens.confirmModal.saveDuplicateButtonLabel": "保存 {name}", "xpack.lens.datatable.visualizationOf": "表 {operations}", @@ -21785,7 +21784,6 @@ "xpack.lens.collapse.min": "最小值", "xpack.lens.collapse.none": "无", "xpack.lens.collapse.sum": "求和", - "xpack.lens.colorMapping.editColorMappingButton": "编辑调色板", "xpack.lens.colorMapping.editColorMappingSectionlabel": "颜色映射", "xpack.lens.colorMapping.editColorMappingTitle": "按词映射编辑颜色", "xpack.lens.colorMapping.editColorsTitle": "编辑颜色", @@ -22340,12 +22338,9 @@ "xpack.lens.modalTitle.title.deleteReferenceLines": "删除参考线图层?", "xpack.lens.modalTitle.title.deleteVis": "删除可视化图层?", "xpack.lens.pageTitle": "Lens", - "xpack.lens.paletteHeatmapGradient.customize": "编辑", - "xpack.lens.paletteHeatmapGradient.customizeLong": "编辑调色板", "xpack.lens.paletteHeatmapGradient.label": "颜色", "xpack.lens.paletteMetricGradient.label": "颜色", "xpack.lens.palettePicker.label": "调色板", - "xpack.lens.paletteTableGradient.customize": "编辑", "xpack.lens.paletteTableGradient.label": "颜色", "xpack.lens.pie.addLayer": "可视化", "xpack.lens.pie.collapsedDimensionsDontCount": "(折叠的维度不计入此限制。)", @@ -22455,7 +22450,7 @@ "xpack.lens.table.alignment.label": "文本对齐", "xpack.lens.table.alignment.left": "左", "xpack.lens.table.alignment.right": "右", - "xpack.lens.table.colorByRangePanelTitle": "颜色", + "xpack.lens.colorSiblingFlyoutTitle": "颜色", "xpack.lens.table.columnFilter.filterForValueText": "筛留", "xpack.lens.table.columnFilter.filterOutValueText": "筛除", "xpack.lens.table.columnFilterClickLabel": "单击时直接筛选", @@ -22466,7 +22461,7 @@ "xpack.lens.table.dynamicColoring.none": "无", "xpack.lens.table.dynamicColoring.text": "文本", "xpack.lens.table.hide.hideLabel": "隐藏", - "xpack.lens.table.palettePanelContainer.back": "返回", + "xpack.lens.settingWithSiblingFlyout.back": "返回", "xpack.lens.table.resize.reset": "重置宽度", "xpack.lens.table.sort.ascLabel": "升序", "xpack.lens.table.sort.descLabel": "降序", diff --git a/x-pack/test/accessibility/apps/group2/lens.ts b/x-pack/test/accessibility/apps/group2/lens.ts index e4ef53efc3548..a64ff207bc3f5 100644 --- a/x-pack/test/accessibility/apps/group2/lens.ts +++ b/x-pack/test/accessibility/apps/group2/lens.ts @@ -89,7 +89,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }); it('lens datatable with palette panel open', async () => { - await PageObjects.lens.openPalettePanel('lnsDatatable'); + await PageObjects.lens.openPalettePanel(); await a11y.testAppSnapshot(); }); diff --git a/x-pack/test/functional/apps/lens/group1/smokescreen.ts b/x-pack/test/functional/apps/lens/group1/smokescreen.ts index f482163930991..7a73428fdc6c9 100644 --- a/x-pack/test/functional/apps/lens/group1/smokescreen.ts +++ b/x-pack/test/functional/apps/lens/group1/smokescreen.ts @@ -71,6 +71,47 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { expect(await find.allByCssSelector('.echLegendItem')).to.have.length(4); }); + describe('dimension flyout keeping open/closing when palette is open ', () => { + it('should keep the dimension editor open when switching to a chart that moves the column to the new group', async () => { + await PageObjects.visualize.navigateToNewVisualization(); + await PageObjects.visualize.clickVisType('lens'); + await PageObjects.lens.goToTimeRange(); + + await PageObjects.lens.configureDimension({ + dimension: 'lnsXY_yDimensionPanel > lns-empty-dimension', + operation: 'average', + field: 'bytes', + }); + + await PageObjects.lens.configureDimension({ + dimension: 'lnsXY_splitDimensionPanel > lns-empty-dimension', + operation: 'terms', + field: '@message.raw', + keepOpen: true, + }); + + await PageObjects.lens.openPalettePanel(); + + await PageObjects.lens.switchToVisualization('bar'); + + expect(await PageObjects.lens.isDimensionEditorOpen()).to.eql(true); + }); + it('should close the dimension editor when switching to a chart that removes the column', async () => { + await PageObjects.lens.closeDimensionEditor(); + await PageObjects.lens.configureDimension({ + dimension: 'lnsXY_splitDimensionPanel > lns-empty-dimension', + operation: 'terms', + field: '@message.raw', + keepOpen: true, + }); + + await PageObjects.lens.openPalettePanel(); + await PageObjects.lens.switchToVisualization('lnsLegacyMetric'); + + expect(await PageObjects.lens.isDimensionEditorOpen()).to.eql(false); + }); + }); + it('should create an xy visualization with filters aggregation', async () => { await PageObjects.visualize.gotoVisualizationLandingPage(); await listingTable.searchForItemWithName('lnsXYvis'); diff --git a/x-pack/test/functional/apps/lens/group2/table.ts b/x-pack/test/functional/apps/lens/group2/table.ts index 4e58f3e6b9ef9..61e8b5f25554c 100644 --- a/x-pack/test/functional/apps/lens/group2/table.ts +++ b/x-pack/test/functional/apps/lens/group2/table.ts @@ -162,7 +162,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }); it('should open the palette panel to customize the palette look', async () => { - await PageObjects.lens.openPalettePanel('lnsDatatable'); + await PageObjects.lens.openPalettePanel(); await PageObjects.lens.waitForVisualization(); await PageObjects.lens.changePaletteTo('temperature'); await PageObjects.lens.waitForVisualization(); 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 91ec034295e2e..6723ebcb3c8e9 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 @@ -258,7 +258,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await PageObjects.lens.waitForVisualization('heatmapChart'); await PageObjects.lens.openDimensionEditor('lnsHeatmap_cellPanel > lns-dimensionTrigger'); - await PageObjects.lens.openPalettePanel('lnsHeatmap'); + await PageObjects.lens.openPalettePanel(); await testSubjects.click('lnsPalettePanel_dynamicColoring_rangeType_groups_number'); await PageObjects.header.waitUntilLoadingHasFinished(); diff --git a/x-pack/test/functional/apps/lens/group5/heatmap.ts b/x-pack/test/functional/apps/lens/group5/heatmap.ts index 26e77578f4545..746aa33e244f9 100644 --- a/x-pack/test/functional/apps/lens/group5/heatmap.ts +++ b/x-pack/test/functional/apps/lens/group5/heatmap.ts @@ -66,7 +66,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { it('should reflect stop colors change on the chart', async () => { await PageObjects.lens.openDimensionEditor('lnsHeatmap_cellPanel > lns-dimensionTrigger'); - await PageObjects.lens.openPalettePanel('lnsHeatmap'); + await PageObjects.lens.openPalettePanel(); await PageObjects.common.sleep(1000); await retry.try(async () => { await testSubjects.setValue('lnsPalettePanel_dynamicColoring_range_value_0', '10', { diff --git a/x-pack/test/functional/apps/lens/group6/legacy_metric.ts b/x-pack/test/functional/apps/lens/group6/legacy_metric.ts index c7595e2cba0a6..379ab15fecb29 100644 --- a/x-pack/test/functional/apps/lens/group6/legacy_metric.ts +++ b/x-pack/test/functional/apps/lens/group6/legacy_metric.ts @@ -45,7 +45,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }); it('should change the color of the metric when tweaking the values in the panel', async () => { - await PageObjects.lens.openPalettePanel('lnsLegacyMetric'); + await PageObjects.lens.openPalettePanel(); await PageObjects.header.waitUntilLoadingHasFinished(); await testSubjects.setValue('lnsPalettePanel_dynamicColoring_range_value_1', '21000', { clearWithKeyboard: true, diff --git a/x-pack/test/functional/apps/lens/group6/metric.ts b/x-pack/test/functional/apps/lens/group6/metric.ts index 14e46705d6d6b..108f9edcba044 100644 --- a/x-pack/test/functional/apps/lens/group6/metric.ts +++ b/x-pack/test/functional/apps/lens/group6/metric.ts @@ -298,7 +298,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }); it('converts color stops to number', async () => { - await PageObjects.lens.openPalettePanel('lnsMetric'); + await PageObjects.lens.openPalettePanel(); await PageObjects.common.sleep(1000); await testSubjects.click('lnsPalettePanel_dynamicColoring_rangeType_groups_number'); expect([ diff --git a/x-pack/test/functional/apps/lens/open_in_lens/agg_based/gauge.ts b/x-pack/test/functional/apps/lens/open_in_lens/agg_based/gauge.ts index 048f7584a86ab..622f5d0039929 100644 --- a/x-pack/test/functional/apps/lens/open_in_lens/agg_based/gauge.ts +++ b/x-pack/test/functional/apps/lens/open_in_lens/agg_based/gauge.ts @@ -122,7 +122,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { await dimensions[0].click(); - await lens.openPalettePanel('lnsGauge'); + await lens.openPalettePanel(); const colorStops = await lens.getPaletteColorStops(); expect(colorStops).to.eql([ diff --git a/x-pack/test/functional/apps/lens/open_in_lens/agg_based/goal.ts b/x-pack/test/functional/apps/lens/open_in_lens/agg_based/goal.ts index d9152af7411ba..dc2d16472bc31 100644 --- a/x-pack/test/functional/apps/lens/open_in_lens/agg_based/goal.ts +++ b/x-pack/test/functional/apps/lens/open_in_lens/agg_based/goal.ts @@ -215,7 +215,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { await dimensions[0].click(); - await lens.openPalettePanel('lnsMetric'); + await lens.openPalettePanel(); const colorStops = await lens.getPaletteColorStops(); expect(colorStops).to.eql([ diff --git a/x-pack/test/functional/apps/lens/open_in_lens/agg_based/metric.ts b/x-pack/test/functional/apps/lens/open_in_lens/agg_based/metric.ts index 632af7eed9f98..8d966bf24e7a2 100644 --- a/x-pack/test/functional/apps/lens/open_in_lens/agg_based/metric.ts +++ b/x-pack/test/functional/apps/lens/open_in_lens/agg_based/metric.ts @@ -225,7 +225,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { await dimensions[0].click(); - await lens.openPalettePanel('lnsMetric'); + await lens.openPalettePanel(); const colorStops = await lens.getPaletteColorStops(); expect(colorStops).to.eql([ diff --git a/x-pack/test/functional/apps/lens/open_in_lens/tsvb/gauge.ts b/x-pack/test/functional/apps/lens/open_in_lens/tsvb/gauge.ts index 6caaf00cc4b62..927f0621d2a09 100644 --- a/x-pack/test/functional/apps/lens/open_in_lens/tsvb/gauge.ts +++ b/x-pack/test/functional/apps/lens/open_in_lens/tsvb/gauge.ts @@ -103,7 +103,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { await lens.waitForVisualization('mtrVis'); await retry.try(async () => { const closePalettePanels = await testSubjects.findAll( - 'lns-indexPattern-PalettePanelContainerBack' + 'lns-indexPattern-SettingWithSiblingFlyoutBack' ); if (closePalettePanels.length) { await lens.closePalettePanel(); @@ -115,7 +115,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { await dimensions[0].click(); - await lens.openPalettePanel('lnsMetric'); + await lens.openPalettePanel(); const colorStops = await lens.getPaletteColorStops(); expect(colorStops).to.eql([ diff --git a/x-pack/test/functional/apps/lens/open_in_lens/tsvb/metric.ts b/x-pack/test/functional/apps/lens/open_in_lens/tsvb/metric.ts index 99cd075c0ce70..ed43fc3f449de 100644 --- a/x-pack/test/functional/apps/lens/open_in_lens/tsvb/metric.ts +++ b/x-pack/test/functional/apps/lens/open_in_lens/tsvb/metric.ts @@ -106,7 +106,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { await lens.waitForVisualization('mtrVis'); await retry.try(async () => { const closePalettePanels = await testSubjects.findAll( - 'lns-indexPattern-PalettePanelContainerBack' + 'lns-indexPattern-SettingWithSiblingFlyoutBack' ); if (closePalettePanels.length) { await lens.closePalettePanel(); @@ -118,7 +118,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { await dimensions[0].click(); - await lens.openPalettePanel('lnsMetric'); + await lens.openPalettePanel(); const colorStops = await lens.getPaletteColorStops(); expect(colorStops).to.eql([ diff --git a/x-pack/test/functional/apps/lens/open_in_lens/tsvb/table.ts b/x-pack/test/functional/apps/lens/open_in_lens/tsvb/table.ts index 9e9f301859db5..d402bd0c9d5af 100644 --- a/x-pack/test/functional/apps/lens/open_in_lens/tsvb/table.ts +++ b/x-pack/test/functional/apps/lens/open_in_lens/tsvb/table.ts @@ -200,7 +200,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { await lens.waitForVisualization('lnsDataTable'); await retry.try(async () => { const closePalettePanels = await testSubjects.findAll( - 'lns-indexPattern-PalettePanelContainerBack' + 'lns-indexPattern-SettingWithSiblingFlyoutBack' ); if (closePalettePanels.length) { await lens.closePalettePanel(); @@ -209,7 +209,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { await lens.openDimensionEditor('lnsDatatable_metrics > lns-dimensionTrigger'); - await lens.openPalettePanel('lnsDatatable'); + await lens.openPalettePanel(); const colorStops = await lens.getPaletteColorStops(); expect(colorStops).to.eql([ diff --git a/x-pack/test/functional/page_objects/lens_page.ts b/x-pack/test/functional/page_objects/lens_page.ts index c5018bda39ffa..734e7595a62bb 100644 --- a/x-pack/test/functional/page_objects/lens_page.ts +++ b/x-pack/test/functional/page_objects/lens_page.ts @@ -1220,17 +1220,19 @@ export function LensPageProvider({ getService, getPageObjects }: FtrProviderCont await testSubjects.click('lnsDatatable_dynamicColoring_groups_' + coloringType); }, - async openPalettePanel(chartType: string) { + async openPalettePanel() { await retry.try(async () => { - await testSubjects.click(`${chartType}_dynamicColoring_trigger`); + await testSubjects.click(`lns_colorEditing_trigger`); // wait for the UI to settle await PageObjects.common.sleep(100); - await testSubjects.existOrFail('lns-indexPattern-PalettePanelContainer', { timeout: 2500 }); + await testSubjects.existOrFail('lns-indexPattern-SettingWithSiblingFlyout', { + timeout: 2500, + }); }); }, async closePalettePanel() { - await testSubjects.click('lns-indexPattern-PalettePanelContainerBack'); + await testSubjects.click('lns-indexPattern-SettingWithSiblingFlyoutBack'); }, // different picker from the next one @@ -1257,8 +1259,8 @@ export function LensPageProvider({ getService, getPageObjects }: FtrProviderCont async closePaletteEditor() { await retry.try(async () => { - await testSubjects.click('lns-indexPattern-PalettePanelContainerBack'); - await testSubjects.missingOrFail('lns-indexPattern-PalettePanelContainerBack'); + await testSubjects.click('lns-indexPattern-SettingWithSiblingFlyoutBack'); + await testSubjects.missingOrFail('lns-indexPattern-SettingWithSiblingFlyoutBack'); }); }, diff --git a/x-pack/test_serverless/functional/test_suites/common/visualizations/group2/open_in_lens/agg_based/gauge.ts b/x-pack/test_serverless/functional/test_suites/common/visualizations/group2/open_in_lens/agg_based/gauge.ts index 8315a8e2a7eda..39a81069af327 100644 --- a/x-pack/test_serverless/functional/test_suites/common/visualizations/group2/open_in_lens/agg_based/gauge.ts +++ b/x-pack/test_serverless/functional/test_suites/common/visualizations/group2/open_in_lens/agg_based/gauge.ts @@ -95,7 +95,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { await dimensions[0].click(); - await lens.openPalettePanel('lnsGauge'); + await lens.openPalettePanel(); const colorStops = await lens.getPaletteColorStops(); expect(colorStops).to.eql([ diff --git a/x-pack/test_serverless/functional/test_suites/common/visualizations/group2/open_in_lens/agg_based/goal.ts b/x-pack/test_serverless/functional/test_suites/common/visualizations/group2/open_in_lens/agg_based/goal.ts index d1c73fc4dc45f..250241dbb43fe 100644 --- a/x-pack/test_serverless/functional/test_suites/common/visualizations/group2/open_in_lens/agg_based/goal.ts +++ b/x-pack/test_serverless/functional/test_suites/common/visualizations/group2/open_in_lens/agg_based/goal.ts @@ -201,7 +201,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { await dimensions[0].click(); - await lens.openPalettePanel('lnsMetric'); + await lens.openPalettePanel(); const colorStops = await lens.getPaletteColorStops(); expect(colorStops).to.eql([ diff --git a/x-pack/test_serverless/functional/test_suites/common/visualizations/group2/open_in_lens/agg_based/metric.ts b/x-pack/test_serverless/functional/test_suites/common/visualizations/group2/open_in_lens/agg_based/metric.ts index d767efbe11f10..31bbbca2e2718 100644 --- a/x-pack/test_serverless/functional/test_suites/common/visualizations/group2/open_in_lens/agg_based/metric.ts +++ b/x-pack/test_serverless/functional/test_suites/common/visualizations/group2/open_in_lens/agg_based/metric.ts @@ -198,7 +198,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { await dimensions[0].click(); - await lens.openPalettePanel('lnsMetric'); + await lens.openPalettePanel(); const colorStops = await lens.getPaletteColorStops(); expect(colorStops).to.eql([ diff --git a/x-pack/test_serverless/functional/test_suites/common/visualizations/group3/open_in_lens/tsvb/gauge.ts b/x-pack/test_serverless/functional/test_suites/common/visualizations/group3/open_in_lens/tsvb/gauge.ts index 63bff3578613a..7cfb968d8edc1 100644 --- a/x-pack/test_serverless/functional/test_suites/common/visualizations/group3/open_in_lens/tsvb/gauge.ts +++ b/x-pack/test_serverless/functional/test_suites/common/visualizations/group3/open_in_lens/tsvb/gauge.ts @@ -81,7 +81,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { await retry.try(async () => { const closePalettePanels = await testSubjects.findAll( - 'lns-indexPattern-PalettePanelContainerBack' + 'lns-indexPattern-SettingWithSiblingFlyoutBack' ); if (closePalettePanels.length) { await lens.closePalettePanel(); @@ -93,7 +93,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { await dimensions[0].click(); - await lens.openPalettePanel('lnsMetric'); + await lens.openPalettePanel(); const colorStops = await lens.getPaletteColorStops(); expect(colorStops).to.eql([ diff --git a/x-pack/test_serverless/functional/test_suites/common/visualizations/group3/open_in_lens/tsvb/metric.ts b/x-pack/test_serverless/functional/test_suites/common/visualizations/group3/open_in_lens/tsvb/metric.ts index e105dea10ea08..a9e668160eab5 100644 --- a/x-pack/test_serverless/functional/test_suites/common/visualizations/group3/open_in_lens/tsvb/metric.ts +++ b/x-pack/test_serverless/functional/test_suites/common/visualizations/group3/open_in_lens/tsvb/metric.ts @@ -93,7 +93,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { await retry.try(async () => { const closePalettePanels = await testSubjects.findAll( - 'lns-indexPattern-PalettePanelContainerBack' + 'lns-indexPattern-SettingWithSiblingFlyoutBack' ); if (closePalettePanels.length) { await lens.closePalettePanel(); @@ -105,7 +105,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { await dimensions[0].click(); - await lens.openPalettePanel('lnsMetric'); + await lens.openPalettePanel(); const colorStops = await lens.getPaletteColorStops(); expect(colorStops).to.eql([ diff --git a/x-pack/test_serverless/functional/test_suites/common/visualizations/group3/open_in_lens/tsvb/table.ts b/x-pack/test_serverless/functional/test_suites/common/visualizations/group3/open_in_lens/tsvb/table.ts index e5b4174435e05..707452f430d2f 100644 --- a/x-pack/test_serverless/functional/test_suites/common/visualizations/group3/open_in_lens/tsvb/table.ts +++ b/x-pack/test_serverless/functional/test_suites/common/visualizations/group3/open_in_lens/tsvb/table.ts @@ -150,7 +150,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { await retry.try(async () => { const closePalettePanels = await testSubjects.findAll( - 'lns-indexPattern-PalettePanelContainerBack' + 'lns-indexPattern-SettingWithSiblingFlyoutBack' ); if (closePalettePanels.length) { await lens.closePalettePanel(); @@ -159,7 +159,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { await lens.openDimensionEditor('lnsDatatable_metrics > lns-dimensionTrigger'); - await lens.openPalettePanel('lnsDatatable'); + await lens.openPalettePanel(); const colorStops = await lens.getPaletteColorStops(); expect(colorStops).to.eql([ From 44871bf76202f9a0561e2b77f5f5ea0197db8ba9 Mon Sep 17 00:00:00 2001 From: Antonio Date: Tue, 13 Feb 2024 15:03:22 +0100 Subject: [PATCH 61/83] [Cases] Fix flaky cases routes tests (#176591) Fixes #175229 Fixes #175230 Fixes #175231 Fixes #175232 Fixes #163263 ## Summary Again the same, the pipelines were failing at `getByText` so I changed that. --- .../public/components/app/routes.test.tsx | 62 ++++++++++--------- 1 file changed, 34 insertions(+), 28 deletions(-) diff --git a/x-pack/plugins/cases/public/components/app/routes.test.tsx b/x-pack/plugins/cases/public/components/app/routes.test.tsx index 91b4b1ef5227f..423b66c686cee 100644 --- a/x-pack/plugins/cases/public/components/app/routes.test.tsx +++ b/x-pack/plugins/cases/public/components/app/routes.test.tsx @@ -8,7 +8,7 @@ import React from 'react'; // eslint-disable-next-line @kbn/eslint/module_migration import type { MemoryRouterProps } from 'react-router'; -import { render, screen, waitFor } from '@testing-library/react'; +import { render, screen } from '@testing-library/react'; import { MemoryRouter } from 'react-router-dom'; import { noCasesSettingsPermission, @@ -18,6 +18,10 @@ import { } from '../../common/mock'; import { CasesRoutes } from './routes'; import type { CasesPermissions } from '../../../common'; +import { useGetCase } from '../../containers/use_get_case'; +import { defaultGetCase } from '../case_view/mocks'; + +jest.mock('../../containers/use_get_case'); jest.mock('../all_cases', () => ({ AllCases: () =>
{'All cases'}
, @@ -31,6 +35,12 @@ jest.mock('../configure_cases', () => ({ ConfigureCases: () =>
{'Settings'}
, })); +jest.mock('../case_view/case_view_page', () => ({ + CaseViewPage: () =>
{'Case View Page'}
, +})); + +const useGetCaseMock = useGetCase as jest.Mock; + const getCaseViewPaths = () => ['/cases/test-id', '/cases/test-id/comment-id']; const renderWithRouter = ( @@ -48,28 +58,30 @@ const renderWithRouter = ( describe('Cases routes', () => { describe('All cases', () => { - it('navigates to the all cases page', () => { + it('navigates to the all cases page', async () => { renderWithRouter(); - expect(screen.getByText('All cases')).toBeInTheDocument(); + expect(await screen.findByText('All cases')).toBeInTheDocument(); }); // User has read only privileges - it('user can navigate to the all cases page with only read permissions', () => { + it('user can navigate to the all cases page with only read permissions', async () => { renderWithRouter(['/cases'], readCasesPermissions()); - expect(screen.getByText('All cases')).toBeInTheDocument(); + expect(await screen.findByText('All cases')).toBeInTheDocument(); }); }); - // FLAKY: https://github.com/elastic/kibana/issues/163263 - describe.skip('Case view', () => { + describe('Case view', () => { + beforeEach(() => { + useGetCaseMock.mockReturnValue({ + ...defaultGetCase, + }); + }); + it.each(getCaseViewPaths())( 'navigates to the cases view page for path: %s', async (path: string) => { renderWithRouter([path]); - await waitFor(() => { - expect(screen.getByTestId('case-view-loading')).toBeInTheDocument(); - }); - + expect(await screen.findByText('Case View Page')).toBeInTheDocument(); // User has read only privileges } ); @@ -78,38 +90,32 @@ describe('Cases routes', () => { 'user can navigate to the cases view page with read permissions and path: %s', async (path: string) => { renderWithRouter([path], readCasesPermissions()); - await waitFor(() => { - expect(screen.getByTestId('case-view-loading')).toBeInTheDocument(); - }); + expect(await screen.findByText('Case View Page')).toBeInTheDocument(); } ); }); - // FLAKY: https://github.com/elastic/kibana/issues/175229 - // FLAKY: https://github.com/elastic/kibana/issues/175230 - describe.skip('Create case', () => { - it('navigates to the create case page', () => { + describe('Create case', () => { + it('navigates to the create case page', async () => { renderWithRouter(['/cases/create']); - expect(screen.getByText('Create case')).toBeInTheDocument(); + expect(await screen.findByText('Create case')).toBeInTheDocument(); }); - it('shows the no privileges page if the user does not have create privileges', () => { + it('shows the no privileges page if the user does not have create privileges', async () => { renderWithRouter(['/cases/create'], noCreateCasesPermissions()); - expect(screen.getByText('Privileges required')).toBeInTheDocument(); + expect(await screen.findByText('Privileges required')).toBeInTheDocument(); }); }); - // FLAKY: https://github.com/elastic/kibana/issues/175231 - // FLAKY: https://github.com/elastic/kibana/issues/175232 - describe.skip('Cases settings', () => { - it('navigates to the cases settings page', () => { + describe('Cases settings', () => { + it('navigates to the cases settings page', async () => { renderWithRouter(['/cases/configure']); - expect(screen.getByText('Settings')).toBeInTheDocument(); + expect(await screen.findByText('Settings')).toBeInTheDocument(); }); - it('shows the no privileges page if the user does not have settings privileges', () => { + it('shows the no privileges page if the user does not have settings privileges', async () => { renderWithRouter(['/cases/configure'], noCasesSettingsPermission()); - expect(screen.getByText('Privileges required')).toBeInTheDocument(); + expect(await screen.findByText('Privileges required')).toBeInTheDocument(); }); }); }); From 567e6de1c0f648e3b9f277eb82dd01b5a7b5adf9 Mon Sep 17 00:00:00 2001 From: Brad White Date: Tue, 13 Feb 2024 07:17:04 -0700 Subject: [PATCH 62/83] [ci] Fix purge projects undefined (#176774) ## Summary #176193 added an additional matching group which shifted the array elements of `match` resulting in Github API failures. This leads to serverless projects not being purged properly. [Failure log example](https://buildkite.com/elastic/kibana-purge-cloud-deployments/builds/17277#018d9fcc-f057-4347-bd57-08ccdae3efcc/152-157) [Post-fix log example](https://buildkite.com/elastic/kibana-purge-cloud-deployments/builds/17278#018d9fd0-79b6-4c6b-bd87-2bc57e2c7d99/153-158) --- .buildkite/scripts/steps/cloud/purge_projects.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.buildkite/scripts/steps/cloud/purge_projects.ts b/.buildkite/scripts/steps/cloud/purge_projects.ts index dbf0060fe8a45..a8a83266a826e 100644 --- a/.buildkite/scripts/steps/cloud/purge_projects.ts +++ b/.buildkite/scripts/steps/cloud/purge_projects.ts @@ -23,7 +23,7 @@ async function getPrProjects() { .flat() .filter((project) => project.name.match(match)) .map((project) => { - const [, prNumber, projectType] = project.name.match(match); + const [, , prNumber, projectType] = project.name.match(match); return { id: project.id, name: project.name, From 831cfd21ef6ca69a4ba0760b958dc9c248b64832 Mon Sep 17 00:00:00 2001 From: Jon Date: Tue, 13 Feb 2024 08:21:00 -0600 Subject: [PATCH 63/83] [ci/project deploy] Update _reset-credentials endpoint (#176604) --- .buildkite/scripts/steps/serverless/build_and_deploy.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.buildkite/scripts/steps/serverless/build_and_deploy.sh b/.buildkite/scripts/steps/serverless/build_and_deploy.sh index 3301959d71ef0..44529c6dba5f5 100644 --- a/.buildkite/scripts/steps/serverless/build_and_deploy.sh +++ b/.buildkite/scripts/steps/serverless/build_and_deploy.sh @@ -68,7 +68,7 @@ deploy() { echo "Get credentials..." curl -s -XPOST -H "Authorization: ApiKey $PROJECT_API_KEY" \ - "${PROJECT_API_DOMAIN}/api/v1/serverless/projects/${PROJECT_TYPE}/${PROJECT_ID}/_reset-credentials" &>> $DEPLOY_LOGS + "${PROJECT_API_DOMAIN}/api/v1/serverless/projects/${PROJECT_TYPE}/${PROJECT_ID}/_reset-internal-credentials" &>> $DEPLOY_LOGS PROJECT_USERNAME=$(jq -r --slurp '.[2].username' $DEPLOY_LOGS) PROJECT_PASSWORD=$(jq -r --slurp '.[2].password' $DEPLOY_LOGS) From d11b73d55a3fe4733e38a43a179f94dd883eda4b Mon Sep 17 00:00:00 2001 From: Nathan Reese Date: Tue, 13 Feb 2024 07:26:29 -0700 Subject: [PATCH 64/83] [lens] [ES|QL] - Remove drill-down capabilities on ES|QL charts / panels (#176741) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Closes https://github.com/elastic/kibana/issues/176245 ### test instructions 1. install web logs sample data 2. create new dashboard 3. click "Add panel" -> "ES|QL". 4. Set ESQL to `from kibana_sample_data_logs | stats count() by machine.os`. Click "Run". 5. Open context menu and verify "Create drilldown" action is not available. Screenshot 2024-02-12 at 11 46 29 AM 6. Create regular lens panel. Verify "Create drilldown" action is available in context menu. Screenshot 2024-02-12 at 11 46 42 AM --- x-pack/plugins/lens/public/embeddable/embeddable.tsx | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/x-pack/plugins/lens/public/embeddable/embeddable.tsx b/x-pack/plugins/lens/public/embeddable/embeddable.tsx index f2fa31b1aa474..2afe4a767b922 100644 --- a/x-pack/plugins/lens/public/embeddable/embeddable.tsx +++ b/x-pack/plugins/lens/public/embeddable/embeddable.tsx @@ -914,6 +914,12 @@ export class Embeddable savedObjectId: (input as LensByReferenceInput)?.savedObjectId, }; + if (this.isTextBasedLanguage()) { + this.updateInput({ + disabledActions: ['OPEN_FLYOUT_ADD_DRILLDOWN'], + }); + } + try { const { ast, indexPatterns, indexPatternRefs, activeVisualizationState } = await getExpressionFromDocument(this.savedVis, this.deps.documentToExpression); From 708db13e6e7dea5d8e99dff721d86dfa6ef25675 Mon Sep 17 00:00:00 2001 From: Pablo Machado Date: Tue, 13 Feb 2024 15:39:43 +0100 Subject: [PATCH 65/83] [Security Solution] Enable Entity Analytics feature flags by default (#175899) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary Enables the following feature flags by default: * newUserDetailsFlyout * newHostDetailsFlyout * ~entityAnalyticsAssetCriticalityEnabled~ * Cleanup any unnecessary feature flag enablements within Cypress/FTR tests - [ ] Wait for the green flag 🟢 --------- Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../common/experimental_features.ts | 4 +- ...ntity_flyout.cy.ts => entity_flyout.cy.ts} | 2 - .../e2e/explore/users/user_details.cy.ts | 40 ------------------- .../cypress/tasks/alerts.ts | 6 --- 4 files changed, 2 insertions(+), 50 deletions(-) rename x-pack/test/security_solution_cypress/cypress/e2e/entity_analytics/{new_entity_flyout.cy.ts => entity_flyout.cy.ts} (99%) delete mode 100644 x-pack/test/security_solution_cypress/cypress/e2e/explore/users/user_details.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 19af0a010342c..12e31e09f2176 100644 --- a/x-pack/plugins/security_solution/common/experimental_features.ts +++ b/x-pack/plugins/security_solution/common/experimental_features.ts @@ -107,7 +107,7 @@ export const allowedExperimentalValues = Object.freeze({ * Enables the new user details flyout displayed on the Alerts table. * **/ - newUserDetailsFlyout: false, + newUserDetailsFlyout: true, /* * Enables the Managed User section inside the new user details flyout. @@ -120,7 +120,7 @@ export const allowedExperimentalValues = Object.freeze({ * Enables the new host details flyout displayed on the Alerts table. * **/ - newHostDetailsFlyout: false, + newHostDetailsFlyout: true, /** * Enable risk engine client and initialisation of datastream, component templates and mappings diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/entity_analytics/new_entity_flyout.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/entity_analytics/entity_flyout.cy.ts similarity index 99% rename from x-pack/test/security_solution_cypress/cypress/e2e/entity_analytics/new_entity_flyout.cy.ts rename to x-pack/test/security_solution_cypress/cypress/e2e/entity_analytics/entity_flyout.cy.ts index 5817d338627ff..29a4ffc956587 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/entity_analytics/new_entity_flyout.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/entity_analytics/entity_flyout.cy.ts @@ -54,8 +54,6 @@ describe( ftrConfig: { kbnServerArgs: [ `--xpack.securitySolution.enableExperimental=${JSON.stringify([ - 'newUserDetailsFlyout', - 'newHostDetailsFlyout', 'entityAnalyticsAssetCriticalityEnabled', 'newUserDetailsFlyoutManagedUser', ])}`, diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/explore/users/user_details.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/explore/users/user_details.cy.ts deleted file mode 100644 index fd7aa715819a3..0000000000000 --- a/x-pack/test/security_solution_cypress/cypress/e2e/explore/users/user_details.cy.ts +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { ALERT_FLYOUT } from '../../../screens/alerts_details'; -import { createRule } from '../../../tasks/api_calls/rules'; -import { waitForAlertsToPopulate } from '../../../tasks/create_new_rule'; -import { login } from '../../../tasks/login'; -import { visit } from '../../../tasks/navigation'; -import { refreshPage } from '../../../tasks/security_header'; -import { getNewRule } from '../../../objects/rule'; -import { ALERTS_URL } from '../../../urls/navigation'; -import { - expandAlertTableCellValue, - openUserDetailsFlyout, - scrollAlertTableColumnIntoView, -} from '../../../tasks/alerts'; -import { USER_COLUMN } from '../../../screens/alerts'; - -describe('user details flyout', { tags: ['@ess', '@serverless'] }, () => { - beforeEach(() => { - login(); - }); - - it('shows user detail flyout from alert table', () => { - visit(ALERTS_URL); - createRule(getNewRule({ query: 'user.name:*' })); - refreshPage(); - waitForAlertsToPopulate(); - - scrollAlertTableColumnIntoView(USER_COLUMN); - expandAlertTableCellValue(USER_COLUMN); - openUserDetailsFlyout(); - - cy.get(ALERT_FLYOUT).should('be.visible'); - }); -}); diff --git a/x-pack/test/security_solution_cypress/cypress/tasks/alerts.ts b/x-pack/test/security_solution_cypress/cypress/tasks/alerts.ts index dfedc68af8985..a01edfd22c4e3 100644 --- a/x-pack/test/security_solution_cypress/cypress/tasks/alerts.ts +++ b/x-pack/test/security_solution_cypress/cypress/tasks/alerts.ts @@ -62,8 +62,6 @@ import { ENRICHMENT_QUERY_START_INPUT, THREAT_INTEL_TAB, CELL_EXPAND_VALUE, - CELL_EXPANSION_POPOVER, - USER_DETAILS_LINK, } from '../screens/alerts_details'; import { FIELD_INPUT } from '../screens/exceptions'; import { @@ -421,10 +419,6 @@ export const scrollAlertTableColumnIntoView = (columnSelector: string) => { }); }; -export const openUserDetailsFlyout = () => { - cy.get(CELL_EXPANSION_POPOVER).find(USER_DETAILS_LINK).click(); -}; - export const waitForPageFilters = () => { cy.log('Waiting for Page Filters'); cy.url().then((urlString) => { From 4010b318d915615a07ffc46e0abac91b8a67e8c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cau=C3=AA=20Marcondes?= <55978943+cauemarcondes@users.noreply.github.com> Date: Tue, 13 Feb 2024 14:43:21 +0000 Subject: [PATCH 66/83] [Profiling] Adding Azure settings (#176386) closes https://github.com/elastic/kibana/issues/176376 Screenshot 2024-02-07 at 10 52 49 **We have to wait until the ES [PR](https://github.com/elastic/elasticsearch/pull/105231) is merged to e2e test it.** --- docs/management/advanced-options.asciidoc | 21 ++++ .../server/collectors/management/schema.ts | 4 + .../server/collectors/management/types.ts | 1 + src/plugins/telemetry/schema/oss_plugins.json | 6 ++ x-pack/plugins/observability/common/index.ts | 1 + .../observability/common/ui_settings_keys.ts | 1 + .../observability/server/ui_settings.ts | 21 +++- .../e2e/profiling_views/settings.cy.ts | 1 + .../profiling/public/views/settings/index.tsx | 101 +++++++++++++----- .../common/profiling_es_client.ts | 6 +- .../server/services/fetch_flamechart/index.ts | 4 + .../server/services/functions/index.ts | 4 + .../services/search_stack_traces/index.ts | 39 ++++--- .../utils/create_profiling_es_client.ts | 4 + 14 files changed, 164 insertions(+), 50 deletions(-) diff --git a/docs/management/advanced-options.asciidoc b/docs/management/advanced-options.asciidoc index 4a77b7e7d0a49..f00523b5c1f5c 100644 --- a/docs/management/advanced-options.asciidoc +++ b/docs/management/advanced-options.asciidoc @@ -466,6 +466,27 @@ preview:[] Enables the Profiling view in Host details within Infrastructure. [[observability-infrastructure-hosts-custom-dashboard]]`observability:enableInfrastructureHostsCustomDashboards`:: preview:[] Enables option to link custom dashboards in the Host Details view. +[[observability-profiling-per-vcpu-watt-x86]]`observability:profilingPervCPUWattX86`:: +The average amortized per-core power consumption (based on 100% CPU utilization) for x86 architecture. + +[[observability-profiling-per-vcpu-watt-arm64]]`observability:profilingPervCPUWattArm64`:: +The average amortized per-core power consumption (based on 100% CPU utilization) for arm64 architecture. + +[[observability-profiling-datacenter-PUE]]`observability:profilingDatacenterPUE`:: +Data center power usage effectiveness (PUE) measures how efficiently a data center uses energy. Defaults to 1.7, the average on-premise data center PUE according to the https://ela.st/uptimeinstitute[Uptime Institute] survey. + +[[observability-profiling-per-co2-per-kwh]]`observability:profilingCo2PerKWH`:: +Carbon intensity measures how clean your data center electricity is. Specifically, it measures the average amount of CO2 emitted per kilowatt-hour (kWh) of electricity consumed in a particular region. + +[[observability-profiling-aws-cost-discount-rate]]`observability:profilingAWSCostDiscountRate`:: +If you're enrolled in the AWS Enterprise Discount Program (EDP), enter your discount rate to update the profiling cost calculation. + +[[observability-profiling-azure-cost-discount-rate]]`observability:profilingAzureCostDiscountRate`:: +If you have an Azure Enterprise Agreement with Microsoft, enter your discount rate to update the profiling cost calculation. + +[[observability-profiling-cost-per-vcpu-per-hour]]`observability:profilingCostPervCPUPerHour`:: +Default Hourly Cost per CPU Core for machines not on AWS or Azure. + [float] [[kibana-reporting-settings]] ==== Reporting diff --git a/src/plugins/kibana_usage_collection/server/collectors/management/schema.ts b/src/plugins/kibana_usage_collection/server/collectors/management/schema.ts index 4a98048f0d168..1f00fdc7c31b2 100644 --- a/src/plugins/kibana_usage_collection/server/collectors/management/schema.ts +++ b/src/plugins/kibana_usage_collection/server/collectors/management/schema.ts @@ -632,6 +632,10 @@ export const stackManagementSchema: MakeSchemaFrom = { type: 'integer', _meta: { description: 'Non-default value of setting.' }, }, + 'observability:profilingAzureCostDiscountRate': { + type: 'integer', + _meta: { description: 'Non-default value of setting.' }, + }, 'data_views:fields_excluded_data_tiers': { type: 'keyword', _meta: { description: 'Non-default value of setting.' }, diff --git a/src/plugins/kibana_usage_collection/server/collectors/management/types.ts b/src/plugins/kibana_usage_collection/server/collectors/management/types.ts index 3ef4623444aeb..171a54b5f01df 100644 --- a/src/plugins/kibana_usage_collection/server/collectors/management/types.ts +++ b/src/plugins/kibana_usage_collection/server/collectors/management/types.ts @@ -166,6 +166,7 @@ export interface UsageStats { 'observability:profilingDatacenterPUE': number; 'observability:profilingCostPervCPUPerHour': number; 'observability:profilingAWSCostDiscountRate': number; + 'observability:profilingAzureCostDiscountRate': number; 'data_views:fields_excluded_data_tiers': string; 'observability:apmEnableTransactionProfiling': boolean; 'devTools:enableDockedConsole': boolean; diff --git a/src/plugins/telemetry/schema/oss_plugins.json b/src/plugins/telemetry/schema/oss_plugins.json index 47736fc79c944..1f23517eb7a9f 100644 --- a/src/plugins/telemetry/schema/oss_plugins.json +++ b/src/plugins/telemetry/schema/oss_plugins.json @@ -10264,6 +10264,12 @@ "description": "Non-default value of setting." } }, + "observability:profilingAzureCostDiscountRate": { + "type": "integer", + "_meta": { + "description": "Non-default value of setting." + } + }, "data_views:fields_excluded_data_tiers": { "type": "keyword", "_meta": { diff --git a/x-pack/plugins/observability/common/index.ts b/x-pack/plugins/observability/common/index.ts index 850be12f6bb7f..1ccebf7b00aac 100644 --- a/x-pack/plugins/observability/common/index.ts +++ b/x-pack/plugins/observability/common/index.ts @@ -51,6 +51,7 @@ export { profilingPervCPUWattArm64, profilingAWSCostDiscountRate, profilingCostPervCPUPerHour, + profilingAzureCostDiscountRate, apmEnableTransactionProfiling, } from './ui_settings_keys'; diff --git a/x-pack/plugins/observability/common/ui_settings_keys.ts b/x-pack/plugins/observability/common/ui_settings_keys.ts index a5d251694cdd1..bd603b6c3bd3f 100644 --- a/x-pack/plugins/observability/common/ui_settings_keys.ts +++ b/x-pack/plugins/observability/common/ui_settings_keys.ts @@ -39,4 +39,5 @@ export const profilingCo2PerKWH = 'observability:profilingCo2PerKWH'; export const profilingDatacenterPUE = 'observability:profilingDatacenterPUE'; export const profilingAWSCostDiscountRate = 'observability:profilingAWSCostDiscountRate'; export const profilingCostPervCPUPerHour = 'observability:profilingCostPervCPUPerHour'; +export const profilingAzureCostDiscountRate = 'observability:profilingAzureCostDiscountRate'; export const apmEnableTransactionProfiling = 'observability:apmEnableTransactionProfiling'; diff --git a/x-pack/plugins/observability/server/ui_settings.ts b/x-pack/plugins/observability/server/ui_settings.ts index 789b0e2676e64..f908043d7a070 100644 --- a/x-pack/plugins/observability/server/ui_settings.ts +++ b/x-pack/plugins/observability/server/ui_settings.ts @@ -38,6 +38,7 @@ import { profilingPervCPUWattArm64, profilingAWSCostDiscountRate, profilingCostPervCPUPerHour, + profilingAzureCostDiscountRate, enableInfrastructureProfilingIntegration, apmEnableTransactionProfiling, enableInfrastructureHostsCustomDashboards, @@ -527,7 +528,7 @@ export const uiSettings: Record = { name: i18n.translate('xpack.observability.profilingAWSCostDiscountRateUiSettingName', { defaultMessage: 'AWS EDP discount rate (%)', }), - value: 6, + value: '0', schema: schema.number({ min: 0, max: 100 }), requiresPageReload: true, description: i18n.translate( @@ -538,6 +539,22 @@ export const uiSettings: Record = { } ), }, + [profilingAzureCostDiscountRate]: { + category: [observabilityFeatureId], + name: i18n.translate('xpack.observability.profilingAzureCostDiscountRateUiSettingName', { + defaultMessage: 'Azure discount rate (%)', + }), + value: '0', + schema: schema.number({ min: 0, max: 100 }), + requiresPageReload: true, + description: i18n.translate( + 'xpack.observability.profilingAzureCostDiscountRateUiSettingDescription', + { + defaultMessage: + 'If you have an Azure Enterprise Agreement with Microsoft, enter your discount rate to update the profiling cost calculation.', + } + ), + }, [profilingCostPervCPUPerHour]: { category: [observabilityFeatureId], name: i18n.translate('xpack.observability.profilingCostPervCPUPerHourUiSettingName', { @@ -547,7 +564,7 @@ export const uiSettings: Record = { description: i18n.translate( 'xpack.observability.profilingCostPervCPUPerHourUiSettingNameDescription', { - defaultMessage: `Default average cost per CPU core per hour (Non-AWS instances only)`, + defaultMessage: `Default Hourly Cost per CPU Core for machines not on AWS or Azure`, } ), schema: schema.number({ min: 0, max: 100 }), diff --git a/x-pack/plugins/profiling/e2e/cypress/e2e/profiling_views/settings.cy.ts b/x-pack/plugins/profiling/e2e/cypress/e2e/profiling_views/settings.cy.ts index 40927c5e38ae0..bc7fdc00c0ed9 100644 --- a/x-pack/plugins/profiling/e2e/cypress/e2e/profiling_views/settings.cy.ts +++ b/x-pack/plugins/profiling/e2e/cypress/e2e/profiling_views/settings.cy.ts @@ -39,6 +39,7 @@ describe('Settings page', () => { cy.contains('Per vCPU Watts - arm64'); cy.contains('AWS EDP discount rate (%)'); cy.contains('Cost per vCPU per hour ($)'); + cy.contains('Azure discount rate (%)'); cy.contains('Show error frames in the Universal Profiling views'); }); diff --git a/x-pack/plugins/profiling/public/views/settings/index.tsx b/x-pack/plugins/profiling/public/views/settings/index.tsx index 7b86ec607d141..56f6ca99e91c3 100644 --- a/x-pack/plugins/profiling/public/views/settings/index.tsx +++ b/x-pack/plugins/profiling/public/views/settings/index.tsx @@ -24,6 +24,7 @@ import { profilingPervCPUWattX86, profilingPervCPUWattArm64, profilingAWSCostDiscountRate, + profilingAzureCostDiscountRate, profilingCostPervCPUPerHour, profilingShowErrorFrames, } from '@kbn/observability-plugin/common'; @@ -47,7 +48,11 @@ const co2Settings = [ profilingPervCPUWattX86, profilingPervCPUWattArm64, ]; -const costSettings = [profilingAWSCostDiscountRate, profilingCostPervCPUPerHour]; +const costSettings = [ + profilingAWSCostDiscountRate, + profilingAzureCostDiscountRate, + profilingCostPervCPUPerHour, +]; const miscSettings = [profilingShowErrorFrames]; export function Settings() { @@ -106,30 +111,57 @@ export function Settings() { 'The Universal Profiling host agent can detect if your machine is running on AWS, Azure, or Google Cloud Platform.', }), subtitle: ( - - {i18n.translate('xpack.profiling.settings.co2.subtitle.link', { - defaultMessage: 'regional carbon intensity', - })} - - ), - pue: ( - - {i18n.translate('xpack.profiling.settings.co2.subtitle.pue', { - defaultMessage: 'PUE', - })} - - ), - }} - /> + <> + + {i18n.translate('xpack.profiling.settings.co2.subtitle.link', { + defaultMessage: 'regional carbon intensity', + })} + + ), + pue: ( + + {i18n.translate('xpack.profiling.settings.co2.subtitle.pue', { + defaultMessage: 'PUE', + })} + + ), + }} + /> + + + {i18n.translate('xpack.profiling.settings.co2.subtitle.link', { + defaultMessage: 'regional carbon intensity', + })} + + ), + pue: ( + + {i18n.translate('xpack.profiling.settings.co2.subtitle.pue', { + defaultMessage: 'PUE', + })} + + ), + }} + /> + ), text: i18n.translate('xpack.profiling.settings.co2.text', { defaultMessage: @@ -146,19 +178,30 @@ export function Settings() { title: ( - {i18n.translate('xpack.profiling.settings.cost.subtitle.link', { + {i18n.translate('xpack.profiling.settings.cost.subtitle.link.aws', { defaultMessage: 'AWS price list', })} ), + azurePriceList: ( + + {i18n.translate('xpack.profiling.settings.cost.subtitle.link.azure', { + defaultMessage: 'Azure price list', + })} + + ), }} /> ), diff --git a/x-pack/plugins/profiling_data_access/common/profiling_es_client.ts b/x-pack/plugins/profiling_data_access/common/profiling_es_client.ts index 4fdaef437c64f..e70a5ea4c1737 100644 --- a/x-pack/plugins/profiling_data_access/common/profiling_es_client.ts +++ b/x-pack/plugins/profiling_data_access/common/profiling_es_client.ts @@ -19,7 +19,7 @@ export interface ProfilingESClient { operationName: string, searchRequest: TSearchRequest ): Promise>; - profilingStacktraces({}: { + profilingStacktraces(params: { query: QueryDslQueryContainer; sampleSize: number; durationSeconds: number; @@ -29,12 +29,13 @@ export interface ProfilingESClient { pervCPUWattArm64?: number; awsCostDiscountRate?: number; costPervCPUPerHour?: number; + azureCostDiscountRate?: number; indices?: string[]; stacktraceIdsField?: string; }): Promise; profilingStatus(params?: { waitForResourcesCreated?: boolean }): Promise; getEsClient(): ElasticsearchClient; - profilingFlamegraph({}: { + profilingFlamegraph(params: { query: QueryDslQueryContainer; sampleSize: number; durationSeconds: number; @@ -43,6 +44,7 @@ export interface ProfilingESClient { pervCPUWattX86?: number; pervCPUWattArm64?: number; awsCostDiscountRate?: number; + azureCostDiscountRate?: number; costPervCPUPerHour?: number; indices?: string[]; stacktraceIdsField?: string; diff --git a/x-pack/plugins/profiling_data_access/server/services/fetch_flamechart/index.ts b/x-pack/plugins/profiling_data_access/server/services/fetch_flamechart/index.ts index 46b288c5ea361..5fe47a9e56079 100644 --- a/x-pack/plugins/profiling_data_access/server/services/fetch_flamechart/index.ts +++ b/x-pack/plugins/profiling_data_access/server/services/fetch_flamechart/index.ts @@ -13,6 +13,7 @@ import { profilingDatacenterPUE, profilingPervCPUWattArm64, profilingPervCPUWattX86, + profilingAzureCostDiscountRate, } from '@kbn/observability-plugin/common'; import { percentToFactor } from '../../utils/percent_to_factor'; import { RegisterServicesParams } from '../register_services'; @@ -44,6 +45,7 @@ export function createFetchFlamechart({ createProfilingEsClient }: RegisterServi pervCPUWattArm64, awsCostDiscountRate, costPervCPUPerHour, + azureCostDiscountRate, ] = await Promise.all([ core.uiSettings.client.get(profilingCo2PerKWH), core.uiSettings.client.get(profilingDatacenterPUE), @@ -51,6 +53,7 @@ export function createFetchFlamechart({ createProfilingEsClient }: RegisterServi core.uiSettings.client.get(profilingPervCPUWattArm64), core.uiSettings.client.get(profilingAWSCostDiscountRate), core.uiSettings.client.get(profilingCostPervCPUPerHour), + core.uiSettings.client.get(profilingAzureCostDiscountRate), ]); const profilingEsClient = createProfilingEsClient({ esClient }); @@ -65,6 +68,7 @@ export function createFetchFlamechart({ createProfilingEsClient }: RegisterServi pervCPUWattArm64, awsCostDiscountRate: percentToFactor(awsCostDiscountRate), costPervCPUPerHour, + azureCostDiscountRate: percentToFactor(azureCostDiscountRate), indices, stacktraceIdsField, }); diff --git a/x-pack/plugins/profiling_data_access/server/services/functions/index.ts b/x-pack/plugins/profiling_data_access/server/services/functions/index.ts index 5c07dd0ca9ed1..5f3e8fae8c89b 100644 --- a/x-pack/plugins/profiling_data_access/server/services/functions/index.ts +++ b/x-pack/plugins/profiling_data_access/server/services/functions/index.ts @@ -11,6 +11,7 @@ import { profilingDatacenterPUE, profilingPervCPUWattArm64, profilingPervCPUWattX86, + profilingAzureCostDiscountRate, profilingShowErrorFrames, } from '@kbn/observability-plugin/common'; import { CoreRequestHandlerContext, ElasticsearchClient } from '@kbn/core/server'; @@ -52,6 +53,7 @@ export function createFetchFunctions({ createProfilingEsClient }: RegisterServic pervCPUWattArm64, awsCostDiscountRate, costPervCPUPerHour, + azureCostDiscountRate, showErrorFrames, ] = await Promise.all([ core.uiSettings.client.get(profilingCo2PerKWH), @@ -60,6 +62,7 @@ export function createFetchFunctions({ createProfilingEsClient }: RegisterServic core.uiSettings.client.get(profilingPervCPUWattArm64), core.uiSettings.client.get(profilingAWSCostDiscountRate), core.uiSettings.client.get(profilingCostPervCPUPerHour), + core.uiSettings.client.get(profilingAzureCostDiscountRate), core.uiSettings.client.get(profilingShowErrorFrames), ]); @@ -76,6 +79,7 @@ export function createFetchFunctions({ createProfilingEsClient }: RegisterServic pervCPUWattArm64, awsCostDiscountRate: percentToFactor(awsCostDiscountRate), costPervCPUPerHour, + azureCostDiscountRate: percentToFactor(azureCostDiscountRate), indices, stacktraceIdsField, query, diff --git a/x-pack/plugins/profiling_data_access/server/services/search_stack_traces/index.ts b/x-pack/plugins/profiling_data_access/server/services/search_stack_traces/index.ts index cf96c1242b249..5ed44f9b0f6d0 100644 --- a/x-pack/plugins/profiling_data_access/server/services/search_stack_traces/index.ts +++ b/x-pack/plugins/profiling_data_access/server/services/search_stack_traces/index.ts @@ -9,21 +9,7 @@ import { decodeStackTraceResponse } from '@kbn/profiling-utils'; import { QueryDslQueryContainer } from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import { ProfilingESClient } from '../../../common/profiling_es_client'; -export async function searchStackTraces({ - client, - sampleSize, - durationSeconds, - co2PerKWH, - datacenterPUE, - pervCPUWattX86, - pervCPUWattArm64, - awsCostDiscountRate, - costPervCPUPerHour, - indices, - stacktraceIdsField, - query, - showErrorFrames, -}: { +interface Params { client: ProfilingESClient; sampleSize: number; durationSeconds: number; @@ -33,11 +19,29 @@ export async function searchStackTraces({ pervCPUWattArm64: number; awsCostDiscountRate: number; costPervCPUPerHour: number; + azureCostDiscountRate: number; + showErrorFrames: boolean; indices?: string[]; stacktraceIdsField?: string; query: QueryDslQueryContainer; - showErrorFrames: boolean; -}) { +} + +export async function searchStackTraces({ + client, + sampleSize, + durationSeconds, + co2PerKWH, + datacenterPUE, + pervCPUWattX86, + pervCPUWattArm64, + awsCostDiscountRate, + costPervCPUPerHour, + azureCostDiscountRate, + showErrorFrames, + indices, + query, + stacktraceIdsField, +}: Params) { const response = await client.profilingStacktraces({ query, sampleSize, @@ -48,6 +52,7 @@ export async function searchStackTraces({ pervCPUWattArm64, awsCostDiscountRate, costPervCPUPerHour, + azureCostDiscountRate, indices, stacktraceIdsField, }); diff --git a/x-pack/plugins/profiling_data_access/server/utils/create_profiling_es_client.ts b/x-pack/plugins/profiling_data_access/server/utils/create_profiling_es_client.ts index a06568418771a..4c209540f37a1 100644 --- a/x-pack/plugins/profiling_data_access/server/utils/create_profiling_es_client.ts +++ b/x-pack/plugins/profiling_data_access/server/utils/create_profiling_es_client.ts @@ -49,6 +49,7 @@ export function createProfilingEsClient({ costPervCPUPerHour, pervCPUWattArm64, pervCPUWattX86, + azureCostDiscountRate, indices, stacktraceIdsField, }) { @@ -68,6 +69,7 @@ export function createProfilingEsClient({ datacenter_pue: datacenterPUE, aws_cost_factor: awsCostDiscountRate, cost_per_core_hour: costPervCPUPerHour, + azure_cost_factor: azureCostDiscountRate, indices, stacktrace_ids_field: stacktraceIdsField, }, @@ -114,6 +116,7 @@ export function createProfilingEsClient({ costPervCPUPerHour, pervCPUWattArm64, pervCPUWattX86, + azureCostDiscountRate, indices, stacktraceIdsField, }) { @@ -134,6 +137,7 @@ export function createProfilingEsClient({ datacenter_pue: datacenterPUE, aws_cost_factor: awsCostDiscountRate, cost_per_core_hour: costPervCPUPerHour, + azure_cost_factor: azureCostDiscountRate, indices, stacktrace_ids_field: stacktraceIdsField, }, From 721f48cad363a6353e0d6ce3c6f455f2255f411b Mon Sep 17 00:00:00 2001 From: Peter Pisljar Date: Tue, 13 Feb 2024 15:54:51 +0100 Subject: [PATCH 67/83] discover query cancellation (#176202) --- .../layout/discover_histogram_layout.tsx | 1 + .../components/layout/discover_layout.tsx | 15 +++- .../components/top_nav/discover_topnav.tsx | 10 ++- .../main/hooks/use_saved_search_messages.ts | 2 +- .../services/discover_data_state_container.ts | 22 ++++++ .../discover/group4/_request_cancellation.ts | 72 +++++++++++++++++++ test/functional/apps/discover/group4/index.ts | 1 + test/functional/page_objects/discover_page.ts | 2 +- test/functional/services/filter_bar.ts | 6 +- .../services/visualizations/elastic_chart.ts | 7 +- 10 files changed, 128 insertions(+), 10 deletions(-) create mode 100644 test/functional/apps/discover/group4/_request_cancellation.ts diff --git a/src/plugins/discover/public/application/main/components/layout/discover_histogram_layout.tsx b/src/plugins/discover/public/application/main/components/layout/discover_histogram_layout.tsx index 18a27917868c7..79f4e9e74cc96 100644 --- a/src/plugins/discover/public/application/main/components/layout/discover_histogram_layout.tsx +++ b/src/plugins/discover/public/application/main/components/layout/discover_histogram_layout.tsx @@ -80,6 +80,7 @@ export const DiscoverHistogramLayout = ({ container={container} css={histogramLayoutCss} renderCustomChartToggleActions={renderCustomChartToggleActions} + abortController={stateContainer.dataState.getAbortController()} > { + stateContainer.dataState.cancel(); + sendErrorMsg(stateContainer.dataState.data$.documents$); + sendErrorMsg(stateContainer.dataState.data$.main$); + }, [stateContainer.dataState]); + return (
Promise; + isLoading?: boolean; + onCancelClick?: () => void; } export const DiscoverTopNav = ({ @@ -42,6 +44,8 @@ export const DiscoverTopNav = ({ textBasedLanguageModeErrors, textBasedLanguageModeWarning, onFieldEdited, + isLoading, + onCancelClick, }: DiscoverTopNavProps) => { const query = useAppStateSelector((state) => state.query); const adHocDataViews = useInternalStateSelector((state) => state.adHocDataViews); @@ -201,6 +205,8 @@ export const DiscoverTopNav = ({ appName="discover" indexPatterns={[dataView]} onQuerySubmit={updateQuery} + onCancel={onCancelClick} + isLoading={isLoading} onSavedQueryIdChange={updateSavedQueryId} query={query} savedQueryId={savedQuery} diff --git a/src/plugins/discover/public/application/main/hooks/use_saved_search_messages.ts b/src/plugins/discover/public/application/main/hooks/use_saved_search_messages.ts index c0f7dc3054144..b5f6e0f2176fa 100644 --- a/src/plugins/discover/public/application/main/hooks/use_saved_search_messages.ts +++ b/src/plugins/discover/public/application/main/hooks/use_saved_search_messages.ts @@ -115,7 +115,7 @@ export function sendLoadingMoreFinishedMsg( /** * Send ERROR message */ -export function sendErrorMsg(data$: DataMain$ | DataDocuments$ | DataTotalHits$, error: Error) { +export function sendErrorMsg(data$: DataMain$ | DataDocuments$ | DataTotalHits$, error?: Error) { const recordRawType = data$.getValue().recordRawType; data$.next({ fetchStatus: FetchStatus.ERROR, diff --git a/src/plugins/discover/public/application/main/services/discover_data_state_container.ts b/src/plugins/discover/public/application/main/services/discover_data_state_container.ts index ec718983a25a8..b2f8ae9f0a81d 100644 --- a/src/plugins/discover/public/application/main/services/discover_data_state_container.ts +++ b/src/plugins/discover/public/application/main/services/discover_data_state_container.ts @@ -123,6 +123,17 @@ export interface DiscoverDataStateContainer { * resetting all data observable to initial state */ reset: (savedSearch: SavedSearch) => void; + + /** + * cancels the running queries + */ + cancel: () => void; + + /** + * gets active AbortController for running queries + */ + getAbortController: () => AbortController; + /** * Available Inspector Adaptor allowing to get details about recent requests to ES */ @@ -314,6 +325,15 @@ export function getDataStateContainer({ sendResetMsg(dataSubjects, getInitialFetchStatus(), recordType); }; + const cancel = () => { + abortController?.abort(); + abortControllerFetchMore?.abort(); + }; + + const getAbortController = () => { + return abortController; + }; + return { fetch: fetchQuery, fetchMore, @@ -324,5 +344,7 @@ export function getDataStateContainer({ reset, inspectorAdapters, getInitialFetchStatus, + cancel, + getAbortController, }; } diff --git a/test/functional/apps/discover/group4/_request_cancellation.ts b/test/functional/apps/discover/group4/_request_cancellation.ts new file mode 100644 index 0000000000000..e76dc66fe1a83 --- /dev/null +++ b/test/functional/apps/discover/group4/_request_cancellation.ts @@ -0,0 +1,72 @@ +/* + * 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 expect from '@kbn/expect'; + +import { FtrProviderContext } from '../ftr_provider_context'; + +export default function ({ getService, getPageObjects }: FtrProviderContext) { + const retry = getService('retry'); + const esArchiver = getService('esArchiver'); + const kibanaServer = getService('kibanaServer'); + const filterBar = getService('filterBar'); + const testSubjects = getService('testSubjects'); + const PageObjects = getPageObjects(['common', 'discover', 'timePicker', 'header']); + + describe('Discover request cancellation', () => { + before(async () => { + await kibanaServer.importExport.load('test/functional/fixtures/kbn_archiver/discover'); + await esArchiver.loadIfNeeded('test/functional/fixtures/es_archiver/logstash_functional'); + await PageObjects.timePicker.setDefaultAbsoluteRangeViaUiSettings(); + }); + + after(async () => { + await kibanaServer.importExport.unload('test/functional/fixtures/kbn_archiver/discover'); + await esArchiver.unload('test/functional/fixtures/es_archiver/logstash_functional'); + await kibanaServer.savedObjects.cleanStandardList(); + await PageObjects.timePicker.resetDefaultAbsoluteRangeViaUiSettings(); + }); + + beforeEach(async () => { + await PageObjects.common.navigateToApp('discover'); + }); + + it('should allow cancelling active requests', async () => { + await PageObjects.discover.selectIndexPattern('logstash-*'); + await PageObjects.header.waitUntilLoadingHasFinished(); + expect(await PageObjects.discover.hasNoResults()).to.be(false); + await testSubjects.existOrFail('querySubmitButton'); + await testSubjects.missingOrFail('queryCancelButton'); + await filterBar.addDslFilter( + JSON.stringify({ + error_query: { + indices: [ + { + error_type: 'none', + name: 'logstash-*', + stall_time_seconds: 30, + }, + ], + }, + }), + false + ); + await retry.try(async () => { + await testSubjects.missingOrFail('querySubmitButton'); + await testSubjects.existOrFail('queryCancelButton'); + }); + await testSubjects.click('queryCancelButton'); + await retry.try(async () => { + expect(await PageObjects.discover.hasNoResults()).to.be(true); + await testSubjects.existOrFail('querySubmitButton'); + await testSubjects.missingOrFail('queryCancelButton'); + }); + await filterBar.removeAllFilters(); + }); + }); +} diff --git a/test/functional/apps/discover/group4/index.ts b/test/functional/apps/discover/group4/index.ts index 656a116551db8..b6692b8184cb1 100644 --- a/test/functional/apps/discover/group4/index.ts +++ b/test/functional/apps/discover/group4/index.ts @@ -34,5 +34,6 @@ export default function ({ getService, loadTestFile }: FtrProviderContext) { loadTestFile(require.resolve('./_hide_announcements')); loadTestFile(require.resolve('./_data_view_edit')); loadTestFile(require.resolve('./_field_list_new_fields')); + loadTestFile(require.resolve('./_request_cancellation')); }); } diff --git a/test/functional/page_objects/discover_page.ts b/test/functional/page_objects/discover_page.ts index 1d81faaf8a7fd..5791d12cb4866 100644 --- a/test/functional/page_objects/discover_page.ts +++ b/test/functional/page_objects/discover_page.ts @@ -199,7 +199,7 @@ export class DiscoverPageObject extends FtrService { } public async clickHistogramBar() { - await this.elasticChart.waitForRenderComplete(); + await this.elasticChart.waitForRenderComplete(undefined, 5000); const el = await this.elasticChart.getCanvas(); await this.browser.getActions().move({ x: 0, y: 0, origin: el._webElement }).click().perform(); diff --git a/test/functional/services/filter_bar.ts b/test/functional/services/filter_bar.ts index cd927ee18fb83..5866c0ee3f18a 100644 --- a/test/functional/services/filter_bar.ts +++ b/test/functional/services/filter_bar.ts @@ -321,7 +321,7 @@ export class FilterBarService extends FtrService { await this.addFilterAndSelectDataView(null, filter); } - public async addDslFilter(value: string) { + public async addDslFilter(value: string, waitUntilLoadingHasFinished = true) { await this.testSubjects.click('addFilter'); await this.testSubjects.click('editQueryDSL'); await this.monacoEditor.waitCodeEditorReady('addFilterPopover'); @@ -331,7 +331,9 @@ export class FilterBarService extends FtrService { await this.retry.try(async () => { await this.testSubjects.waitForDeleted('saveFilter'); }); - await this.header.waitUntilLoadingHasFinished(); + if (waitUntilLoadingHasFinished) { + await this.header.waitUntilLoadingHasFinished(); + } } /** diff --git a/test/functional/services/visualizations/elastic_chart.ts b/test/functional/services/visualizations/elastic_chart.ts index da872eb21670c..c3f2fdb20f388 100644 --- a/test/functional/services/visualizations/elastic_chart.ts +++ b/test/functional/services/visualizations/elastic_chart.ts @@ -40,10 +40,11 @@ export class ElasticChartService extends FtrService { return await this.find.existsByCssSelector('.echChart canvas:last-of-type'); } - public async waitForRenderComplete(dataTestSubj?: string) { - const chart = await this.getChart(dataTestSubj); + public async waitForRenderComplete(dataTestSubj?: string, timeout?: number) { + const chart = await this.getChart(dataTestSubj, timeout); const rendered = await chart.findAllByCssSelector( - '.echChartStatus[data-ech-render-complete=true]' + '.echChartStatus[data-ech-render-complete=true]', + timeout ); expect(rendered.length).to.equal(1); } From c984b0e05ff7647ba69b1ef0863e384cc36eb3ff Mon Sep 17 00:00:00 2001 From: Stratoula Kalafateli Date: Tue, 13 Feb 2024 17:14:58 +0200 Subject: [PATCH 68/83] [Functional tests] Stabilize the gauge filter on chart click test (#176787) ## Summary Closes https://github.com/elastic/kibana/issues/164358 Flaky test runner https://buildkite.com/elastic/kibana-flaky-test-suite-runner/builds/5148 --- test/functional/apps/visualize/group2/_gauge_chart.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/test/functional/apps/visualize/group2/_gauge_chart.ts b/test/functional/apps/visualize/group2/_gauge_chart.ts index 08425fcd78b5f..1232d84674475 100644 --- a/test/functional/apps/visualize/group2/_gauge_chart.ts +++ b/test/functional/apps/visualize/group2/_gauge_chart.ts @@ -96,6 +96,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { it('should add machine.os.raw:win 8 filter by click on the first Gauge', async () => { await PageObjects.visChart.clickOnGaugeByLabel('win 8'); + await PageObjects.visChart.waitForVisualizationRenderingStabilized(); const hasFilter = await filterBar.hasFilter('machine.os.raw', 'win 8'); expect(hasFilter).to.eql(true); From 853b5f63e5e498b868f5a505256c84b812313977 Mon Sep 17 00:00:00 2001 From: Coen Warmer Date: Tue, 13 Feb 2024 16:15:33 +0100 Subject: [PATCH 69/83] [Observability AI Assistant] Screen Context (#175885) ## Summary This adds functionality to allow consumers of the AI Assistant for Observability to add context to the LLM conversation, at the start of a conversation and contextual after every prompt. https://github.com/elastic/kibana/assets/535564/b4d62897-d701-4c23-b90b-464cad21e9d0 ![image](https://github.com/elastic/kibana/assets/352732/85a7e27a-e715-4273-a3a3-0dd68a7c9c5c) ## How to use The service now exposes a `setApplicationContext` function, that returns a hook to unregister the context. Here's an example: Consumers can use this to add context relevant to the route or settings of Kibana that might be relevant for the LLM within that conversation. Example: ```ts useEffect(() => { return setApplicationContext({ data: [ { name: 'top_transactions', description: 'The visible transaction groups', value: mainStatistics.transactionGroups.map((group) => { return { name: group.name, alertsCount: group.alertsCount, }; }), }, ], }); }, [setApplicationContext, mainStatistics]); ``` By default the URL that the user is currently on is always included in the context so the Assistant will always take that into account. ## Details for reviewers - `recall` function has been renamed to `context` - `context` function now returns both Knowledge base entries as well as chat context that is set by `setApplicationContext`.#176357 - part of the function logic was moved from the ObservabilityAIAssistantService into the ChatFunctionClient, for easier testing --------- Co-authored-by: Dario Gieselaar Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> --- .../app/service_inventory/index.tsx | 32 +++- .../app/transaction_overview/index.tsx | 12 +- .../routing/templates/apm_main_template.tsx | 27 ++- .../templates/settings_template.stories.tsx | 6 + .../shared/transactions_table/index.tsx | 23 ++- .../apm_plugin/mock_apm_plugin_context.tsx | 7 +- .../apm_plugin/mock_apm_plugin_storybook.tsx | 7 +- .../pages/alert_details/alert_details.tsx | 37 ++++ .../public/pages/slo_details/slo_details.tsx | 31 +++- .../public/pages/slos/components/slo_list.tsx | 47 ++++- x-pack/plugins/observability/public/plugin.ts | 2 - .../observability_ai_assistant/README.md | 2 +- .../common/functions/lens.ts | 2 +- .../common/types.ts | 9 + .../action_menu_item/action_menu_item.tsx | 10 ++ .../public/components/chat/chat_body.test.tsx | 8 +- .../public/components/insight/insight.tsx | 2 + ...ai_assistant_multipane_flyout_provider.tsx | 2 +- .../public/hooks/use_chat.test.ts | 3 + .../public/hooks/use_chat.ts | 17 +- .../public/hooks/use_conversation.test.tsx | 2 + .../public/hooks/use_conversation.ts | 1 + .../public/mock.tsx | 3 + .../public/service/create_chat_service.ts | 5 +- .../public/service/create_service.ts | 13 ++ .../public/types.ts | 15 +- ..._timeline_items_from_conversation.test.tsx | 16 +- .../evaluation/scenarios/kb/index.spec.ts | 2 +- .../functions/{recall.ts => context.ts} | 162 +++++++++++------- .../server/functions/index.ts | 26 ++- .../query/correct_common_esql_mistakes.ts | 2 +- .../server/functions/query/index.ts | 6 +- .../server/routes/chat/route.ts | 8 +- .../server/routes/functions/route.ts | 1 + .../server/routes/runtime_types.ts | 22 ++- .../chat_function_client/index.test.ts | 97 ++++++++++- .../service/chat_function_client/index.ts | 79 +++++++-- .../client/adapters/bedrock_claude_adapter.ts | 6 +- .../server/service/client/index.test.ts | 22 ++- .../server/service/client/index.ts | 92 +++++----- .../server/service/index.ts | 39 ++--- .../server/service/types.ts | 4 + .../util/create_function_request_message.ts | 37 ++++ .../util/create_function_response_message.ts | 37 ++++ .../tests/complete/complete.spec.ts | 56 +++++- .../tests/conversations/index.spec.ts | 60 ++++++- 46 files changed, 867 insertions(+), 232 deletions(-) rename x-pack/plugins/observability_ai_assistant/server/functions/{recall.ts => context.ts} (64%) create mode 100644 x-pack/plugins/observability_ai_assistant/server/service/util/create_function_request_message.ts create mode 100644 x-pack/plugins/observability_ai_assistant/server/service/util/create_function_response_message.ts diff --git a/x-pack/plugins/apm/public/components/app/service_inventory/index.tsx b/x-pack/plugins/apm/public/components/app/service_inventory/index.tsx index 55bde1c8fcf2b..1763dc3c878dd 100644 --- a/x-pack/plugins/apm/public/components/app/service_inventory/index.tsx +++ b/x-pack/plugins/apm/public/components/app/service_inventory/index.tsx @@ -7,7 +7,7 @@ import { EuiEmptyPrompt, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import React, { useCallback, useMemo, useState } from 'react'; +import React, { useCallback, useEffect, useMemo, useState } from 'react'; import { v4 as uuidv4 } from 'uuid'; import { APIReturnType } from '../../../services/rest/create_call_apm_api'; import { useStateDebounced } from '../../../hooks/use_debounce'; @@ -18,7 +18,7 @@ import { } from '../../../../common/service_inventory'; import { useAnomalyDetectionJobsContext } from '../../../context/anomaly_detection_jobs/use_anomaly_detection_jobs_context'; import { useApmParams } from '../../../hooks/use_apm_params'; -import { FETCH_STATUS } from '../../../hooks/use_fetcher'; +import { FETCH_STATUS, isFailure, isPending } from '../../../hooks/use_fetcher'; import { useLocalStorage } from '../../../hooks/use_local_storage'; import { usePreferredDataSourceAndBucketSize } from '../../../hooks/use_preferred_data_source_and_bucket_size'; import { useProgressiveFetcher } from '../../../hooks/use_progressive_fetcher'; @@ -29,6 +29,7 @@ import { isTimeComparison } from '../../shared/time_comparison/get_comparison_op import { ServiceList } from './service_list'; import { orderServiceItems } from './service_list/order_service_items'; import { SortFunction } from '../../shared/managed_table'; +import { useApmPluginContext } from '../../../context/apm_plugin/use_apm_plugin_context'; type MainStatisticsApiResponse = APIReturnType<'GET /internal/apm/services'>; @@ -265,6 +266,33 @@ export function ServiceInventory() { [tiebreakerField] ); + const { setScreenContext } = + useApmPluginContext().observabilityAIAssistant.service; + + useEffect(() => { + if (isFailure(mainStatisticsStatus)) { + return setScreenContext({ + screenDescription: 'The services have failed to load', + }); + } + + if (isPending(mainStatisticsStatus)) { + return setScreenContext({ + screenDescription: 'The services are still loading', + }); + } + + return setScreenContext({ + data: [ + { + name: 'services', + description: 'The list of services that the user is looking at', + value: mainStatisticsData.items, + }, + ], + }); + }, [mainStatisticsStatus, mainStatisticsData.items, setScreenContext]); + return ( <> diff --git a/x-pack/plugins/apm/public/components/app/transaction_overview/index.tsx b/x-pack/plugins/apm/public/components/app/transaction_overview/index.tsx index 73f5e40aa2e2a..0bc8c5e708f59 100644 --- a/x-pack/plugins/apm/public/components/app/transaction_overview/index.tsx +++ b/x-pack/plugins/apm/public/components/app/transaction_overview/index.tsx @@ -6,9 +6,10 @@ */ import { EuiFlexGroup, EuiFlexItem, EuiPanel, EuiSpacer } from '@elastic/eui'; -import React from 'react'; +import React, { useEffect } from 'react'; import { useHistory } from 'react-router-dom'; import { isServerlessAgentName } from '../../../../common/agent_name'; +import { useApmPluginContext } from '../../../context/apm_plugin/use_apm_plugin_context'; import { useApmServiceContext } from '../../../context/apm_service/use_apm_service_context'; import { useApmParams } from '../../../hooks/use_apm_params'; import { useLocalStorage } from '../../../hooks/use_local_storage'; @@ -55,6 +56,15 @@ export function TransactionOverview() { false ); + const { setScreenContext } = + useApmPluginContext().observabilityAIAssistant.service; + + useEffect(() => { + return setScreenContext({ + screenDescription: `The user is looking at the transactions overview for ${serviceName}, and the transaction type is ${transactionType}`, + }); + }, [setScreenContext, serviceName, transactionType]); + return ( <> {!sloCalloutDismissed && ( diff --git a/x-pack/plugins/apm/public/components/routing/templates/apm_main_template.tsx b/x-pack/plugins/apm/public/components/routing/templates/apm_main_template.tsx index 5e2e0964be791..09106b92e9618 100644 --- a/x-pack/plugins/apm/public/components/routing/templates/apm_main_template.tsx +++ b/x-pack/plugins/apm/public/components/routing/templates/apm_main_template.tsx @@ -9,7 +9,7 @@ import { EuiFlexGroup, EuiFlexItem, EuiPageHeaderProps } from '@elastic/eui'; import { useKibana } from '@kbn/kibana-react-plugin/public'; import { ObservabilityPageTemplateProps } from '@kbn/observability-shared-plugin/public'; import type { KibanaPageTemplateProps } from '@kbn/shared-ux-page-kibana-template'; -import React, { useContext } from 'react'; +import React, { useContext, useEffect } from 'react'; import { useLocation } from 'react-router-dom'; import { FeatureFeedbackButton } from '@kbn/observability-shared-plugin/public'; import { KibanaEnvironmentContext } from '../../../context/kibana_environment_context/kibana_environment_context'; @@ -66,6 +66,8 @@ export function ApmMainTemplate({ const basePath = http?.basePath.get(); const { config } = useApmPluginContext(); + const aiAssistant = services.observabilityAIAssistant.service; + const ObservabilityPageTemplate = observabilityShared.navigation.PageTemplate; const { data, status } = useFetcher((callApmApi) => { @@ -103,16 +105,35 @@ export function ApmMainTemplate({ status === FETCH_STATUS.LOADING || fleetApmPoliciesStatus === FETCH_STATUS.LOADING; + const hasApmData = !!data?.hasData; + const hasApmIntegrations = !!fleetApmPoliciesData?.hasApmPolicies; + const noDataConfig = getNoDataConfig({ basePath, docsLink: docLinks!.links.observability.guide, - hasApmData: data?.hasData, - hasApmIntegrations: fleetApmPoliciesData?.hasApmPolicies, + hasApmData, + hasApmIntegrations, shouldBypassNoDataScreen, loading: isLoading, isServerless: config?.serverlessOnboarding, }); + useEffect(() => { + return aiAssistant.setScreenContext({ + screenDescription: [ + hasApmData + ? 'The user has APM data.' + : 'The user does not have APM data.', + hasApmIntegrations + ? 'The user has the APM integration installed. ' + : 'The user does not have the APM integration installed', + noDataConfig !== undefined + ? 'The user is looking at a screen that tells them they do not have any data.' + : '', + ].join('\n'), + }); + }, [hasApmData, hasApmIntegrations, noDataConfig, aiAssistant]); + const rightSideItems = [ ...(showServiceGroupSaveButton ? [] : []), ]; diff --git a/x-pack/plugins/apm/public/components/routing/templates/settings_template.stories.tsx b/x-pack/plugins/apm/public/components/routing/templates/settings_template.stories.tsx index e3604e67ebf5b..c3c190bc8d27b 100644 --- a/x-pack/plugins/apm/public/components/routing/templates/settings_template.stories.tsx +++ b/x-pack/plugins/apm/public/components/routing/templates/settings_template.stories.tsx @@ -7,6 +7,7 @@ import type { CoreStart } from '@kbn/core/public'; import type { Meta, Story } from '@storybook/react'; +import { noop } from 'lodash'; import React, { ComponentProps } from 'react'; import type { ApmPluginContextValue } from '../../../context/apm_plugin/apm_plugin_context'; import { MockApmPluginStorybook } from '../../../context/apm_plugin/mock_apm_plugin_storybook'; @@ -23,6 +24,11 @@ const coreMock = { }, }, }, + observabilityAIAssistant: { + service: { + setScreenContext: () => noop, + }, + }, } as unknown as Partial; const configMock = { diff --git a/x-pack/plugins/apm/public/components/shared/transactions_table/index.tsx b/x-pack/plugins/apm/public/components/shared/transactions_table/index.tsx index 0d9621efd18c8..b2b3b64e2a0c5 100644 --- a/x-pack/plugins/apm/public/components/shared/transactions_table/index.tsx +++ b/x-pack/plugins/apm/public/components/shared/transactions_table/index.tsx @@ -10,7 +10,7 @@ import { i18n } from '@kbn/i18n'; import { v4 as uuidv4 } from 'uuid'; import { FormattedMessage } from '@kbn/i18n-react'; import { compact } from 'lodash'; -import React, { useMemo, useState } from 'react'; +import React, { useEffect, useMemo, useState } from 'react'; import { ApmDocumentType } from '../../../../common/document_type'; import { getLatencyAggregationType, @@ -33,6 +33,7 @@ import { ManagedTable, TableSearchBar } from '../managed_table'; import { OverviewTableContainer } from '../overview_table_container'; import { isTimeComparison } from '../time_comparison/get_comparison_options'; import { getColumns } from './get_columns'; +import { useApmPluginContext } from '../../../context/apm_plugin/use_apm_plugin_context'; type ApiResponse = APIReturnType<'GET /internal/apm/services/{serviceName}/transactions/groups/main_statistics'>; @@ -163,6 +164,26 @@ export function TransactionsTable({ }; }, [mainStatistics.maxCountExceeded, setSearchQueryDebounced]); + const { setScreenContext } = + useApmPluginContext().observabilityAIAssistant.service; + + useEffect(() => { + return setScreenContext({ + data: [ + { + name: 'top_transactions', + description: 'The visible transaction groups', + value: mainStatistics.transactionGroups.map((group) => { + return { + name: group.name, + alertsCount: group.alertsCount, + }; + }), + }, + ], + }); + }, [setScreenContext, mainStatistics]); + return ( Promise.resolve([]), }, + observabilityAIAssistant: { + service: { + setScreenContext: jest.fn().mockImplementation(() => noop), + }, + }, }; export function MockApmPluginContextWrapper({ diff --git a/x-pack/plugins/apm/public/context/apm_plugin/mock_apm_plugin_storybook.tsx b/x-pack/plugins/apm/public/context/apm_plugin/mock_apm_plugin_storybook.tsx index af57e9de63c31..a358565663aa8 100644 --- a/x-pack/plugins/apm/public/context/apm_plugin/mock_apm_plugin_storybook.tsx +++ b/x-pack/plugins/apm/public/context/apm_plugin/mock_apm_plugin_storybook.tsx @@ -13,7 +13,7 @@ import { UI_SETTINGS } from '@kbn/observability-shared-plugin/public/hooks/use_k import { UrlService } from '@kbn/share-plugin/common/url_service'; import { RouterProvider } from '@kbn/typed-react-router-config'; import { createMemoryHistory } from 'history'; -import { merge } from 'lodash'; +import { merge, noop } from 'lodash'; import React, { ReactNode } from 'react'; import { __IntlProvider as IntlProvider } from '@kbn/i18n-react'; import { Observable, of } from 'rxjs'; @@ -128,6 +128,11 @@ const mockCore = { const mockApmPluginContext = { core: mockCore, plugins: mockPlugin, + observabilityAIAssistant: { + service: { + setScreenContext: () => noop, + }, + }, } as unknown as ApmPluginContextValue; export function MockApmPluginStorybook({ diff --git a/x-pack/plugins/observability/public/pages/alert_details/alert_details.tsx b/x-pack/plugins/observability/public/pages/alert_details/alert_details.tsx index 6ce338358c646..6c15585dcf9f5 100644 --- a/x-pack/plugins/observability/public/pages/alert_details/alert_details.tsx +++ b/x-pack/plugins/observability/public/pages/alert_details/alert_details.tsx @@ -20,6 +20,7 @@ import { import { RuleTypeModel } from '@kbn/triggers-actions-ui-plugin/public'; import { useBreadcrumbs } from '@kbn/observability-shared-plugin/public'; +import dedent from 'dedent'; import { useKibana } from '../../utils/kibana_react'; import { useFetchRule } from '../../hooks/use_fetch_rule'; import { usePluginContext } from '../../hooks/use_plugin_context'; @@ -56,6 +57,9 @@ export function AlertDetails() { }, http, triggersActionsUi: { ruleTypeRegistry }, + observabilityAIAssistant: { + service: { setScreenContext }, + }, uiSettings, } = useKibana().services; @@ -71,6 +75,39 @@ export function AlertDetails() { const [summaryFields, setSummaryFields] = useState(); const [alertStatus, setAlertStatus] = useState(); + useEffect(() => { + if (!alertDetail) { + return; + } + + const screenDescription = dedent(`The user is looking at an ${ + alertDetail.formatted.active ? 'active' : 'recovered' + } alert. + It started at ${new Date( + alertDetail.formatted.start + ).toISOString()}, and was last updated at ${new Date( + alertDetail.formatted.lastUpdated + ).toISOString()}. + + ${ + alertDetail.formatted.reason + ? `The reason given for the alert is ${alertDetail.formatted.reason}.` + : '' + } + `); + + return setScreenContext({ + screenDescription, + data: [ + { + name: 'alert_fields', + description: 'The fields and values for the alert', + value: alertDetail.formatted.fields, + }, + ], + }); + }, [setScreenContext, alertDetail]); + useEffect(() => { if (alertDetail) { setRuleTypeModel(ruleTypeRegistry.get(alertDetail?.formatted.fields[ALERT_RULE_TYPE_ID]!)); diff --git a/x-pack/plugins/observability/public/pages/slo_details/slo_details.tsx b/x-pack/plugins/observability/public/pages/slo_details/slo_details.tsx index 868b90ea5549e..1e4f590a8bf7d 100644 --- a/x-pack/plugins/observability/public/pages/slo_details/slo_details.tsx +++ b/x-pack/plugins/observability/public/pages/slo_details/slo_details.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import React, { useState } from 'react'; +import React, { useEffect, useState } from 'react'; import { useParams } from 'react-router-dom'; import { useIsMutating } from '@tanstack/react-query'; import { EuiLoadingSpinner } from '@elastic/eui'; @@ -15,6 +15,7 @@ import type { ChromeBreadcrumb } from '@kbn/core-chrome-browser'; import type { SLOWithSummaryResponse } from '@kbn/slo-schema'; import { useBreadcrumbs } from '@kbn/observability-shared-plugin/public'; +import dedent from 'dedent'; import { useKibana } from '../../utils/kibana_react'; import { usePluginContext } from '../../hooks/use_plugin_context'; import { useFetchSloDetails } from '../../hooks/slo/use_fetch_slo_details'; @@ -34,6 +35,9 @@ export function SloDetailsPage() { const { application: { navigateToUrl }, http: { basePath }, + observabilityAIAssistant: { + service: { setScreenContext }, + }, } = useKibana().services; const { ObservabilityPageTemplate } = usePluginContext(); @@ -53,6 +57,31 @@ export function SloDetailsPage() { useBreadcrumbs(getBreadcrumbs(basePath, slo)); + useEffect(() => { + if (!slo) { + return; + } + + return setScreenContext({ + screenDescription: dedent(` + The user is looking at the detail page for the following SLO + + Name: ${slo.name}. + Id: ${slo.id} + Description: ${slo.description} + Observed value: ${slo.summary.sliValue} + Status: ${slo.summary.status} + `), + data: [ + { + name: 'slo', + description: 'The SLO and its metadata', + value: slo, + }, + ], + }); + }, [setScreenContext, slo]); + const isSloNotFound = !isLoading && slo === undefined; if (isSloNotFound) { return ; diff --git a/x-pack/plugins/observability/public/pages/slos/components/slo_list.tsx b/x-pack/plugins/observability/public/pages/slos/components/slo_list.tsx index 66a8ea84fcce8..b7b51f631860a 100644 --- a/x-pack/plugins/observability/public/pages/slos/components/slo_list.tsx +++ b/x-pack/plugins/observability/public/pages/slos/components/slo_list.tsx @@ -7,12 +7,15 @@ import { EuiFlexGroup, EuiFlexItem, EuiTablePagination } from '@elastic/eui'; import { useIsMutating } from '@tanstack/react-query'; -import React from 'react'; +import React, { useEffect } from 'react'; +import dedent from 'dedent'; +import { groupBy as _groupBy, mapValues } from 'lodash'; import { useFetchSloList } from '../../../hooks/slo/use_fetch_slo_list'; import { SearchState, useUrlSearchState } from '../hooks/use_url_search_state'; import { SlosView } from './slos_view'; import { ToggleSLOView } from './toggle_slo_view'; import { GroupView } from './grouped_slos/group_view'; +import { useKibana } from '../../../utils/kibana_react'; export function SloList() { const { state, onStateChange: storeState } = useUrlSearchState(); @@ -34,6 +37,12 @@ export function SloList() { sortDirection: state.sort.direction, lastRefresh: state.lastRefresh, }); + + const { + observabilityAIAssistant: { + service: { setScreenContext }, + }, + } = useKibana().services; const { results = [], total = 0 } = sloList ?? {}; const isCreatingSlo = Boolean(useIsMutating(['creatingSlo'])); @@ -45,6 +54,42 @@ export function SloList() { storeState({ page: 0, ...newState }); }; + useEffect(() => { + if (!sloList) { + return; + } + + const slosByStatus = mapValues( + _groupBy(sloList.results, (result) => result.summary.status), + (groupResults) => groupResults.map((result) => `- ${result.name}`).join('\n') + ) as Record; + + return setScreenContext({ + screenDescription: dedent(`The user is looking at a list of SLOs. + + ${ + sloList.total >= 1 + ? `There are ${sloList.total} SLOs. Out of those, ${sloList.results.length} are visible. + + Violating SLOs: + ${slosByStatus.VIOLATED} + + Degrading SLOs: + ${slosByStatus.DEGRADING} + + Healthy SLOs: + ${slosByStatus.HEALTHY} + + SLOs without data: + ${slosByStatus.NO_DATA} + + ` + : '' + } + `), + }); + }, [sloList, setScreenContext]); + return ( diff --git a/x-pack/plugins/observability/public/plugin.ts b/x-pack/plugins/observability/public/plugin.ts index 2eb60290dbb97..1f06179f20be7 100644 --- a/x-pack/plugins/observability/public/plugin.ts +++ b/x-pack/plugins/observability/public/plugin.ts @@ -410,8 +410,6 @@ export class Plugin const isAiAssistantEnabled = pluginsStart.observabilityAIAssistant.service.isEnabled(); - console.log({ isAiAssistantEnabled }); - const aiAssistantLink = isAiAssistantEnabled && !Boolean(pluginsSetup.serverless) && diff --git a/x-pack/plugins/observability_ai_assistant/README.md b/x-pack/plugins/observability_ai_assistant/README.md index 8d5e166883995..c1ff95bbaf35d 100644 --- a/x-pack/plugins/observability_ai_assistant/README.md +++ b/x-pack/plugins/observability_ai_assistant/README.md @@ -66,7 +66,7 @@ The knowledge base is an Elasticsearch index, with an inference processor powere Both the user and the LLM are able to suggest functions, that are executed on behalf (and with the privileges of) the user. Functions allow both the user and the LLM to include relevant context into the conversation. This context can be text, data, or a visual component, like a timeseries graph. Some of the functions that are available are: -- `recall` and `summarize`: these functions query (with a semantic search) or write to (with a summarisation) the knowledge database. This allows the LLM to create a (partly) user-specific working memory, and access predefined embeddings that help improve its understanding of the Elastic platform. +- `context` and `summarize`: these functions query (with a semantic search) or write to (with a summarisation) the knowledge database. This allows the LLM to create a (partly) user-specific working memory, and access predefined embeddings that help improve its understanding of the Elastic platform. - `lens`: a function that can be used to create Lens vizualisations using Formulas. - `get_apm_timeseries`, `get_apm_service_summary`, `get_apm_downstream_dependencies` and `get_apm_error_document`: a set of APM functions, some with visual components, that are helpful in performing root cause analysis. diff --git a/x-pack/plugins/observability_ai_assistant/common/functions/lens.ts b/x-pack/plugins/observability_ai_assistant/common/functions/lens.ts index c466b4e422f2f..5184eeacc0c76 100644 --- a/x-pack/plugins/observability_ai_assistant/common/functions/lens.ts +++ b/x-pack/plugins/observability_ai_assistant/common/functions/lens.ts @@ -27,7 +27,7 @@ export const lensFunctionDefinition = { // function is deprecated visibility: FunctionVisibility.Internal, description: - "Use this function to create custom visualizations, using Lens, that can be saved to dashboards. This function does not return data to the assistant, it only shows it to the user. When using this function, make sure to use the recall function to get more information about how to use it, with how you want to use it. Make sure the query also contains information about the user's request. The visualisation is displayed to the user above your reply, DO NOT try to generate or display an image yourself.", + "Use this function to create custom visualizations, using Lens, that can be saved to dashboards. This function does not return data to the assistant, it only shows it to the user. When using this function, make sure to use the context function to get more information about how to use it, with how you want to use it. Make sure the query also contains information about the user's request. The visualisation is displayed to the user above your reply, DO NOT try to generate or display an image yourself.", descriptionForUser: 'Use this function to create custom visualizations, using Lens, that can be saved to dashboards.', parameters: { diff --git a/x-pack/plugins/observability_ai_assistant/common/types.ts b/x-pack/plugins/observability_ai_assistant/common/types.ts index f04963a6c5a2c..563d5aa893df8 100644 --- a/x-pack/plugins/observability_ai_assistant/common/types.ts +++ b/x-pack/plugins/observability_ai_assistant/common/types.ts @@ -126,3 +126,12 @@ export type RegisterContextDefinition = (options: ContextDefinition) => void; export type ContextRegistry = Map; export type FunctionRegistry = Map; + +export interface ObservabilityAIAssistantScreenContext { + screenDescription?: string; + data?: Array<{ + name: string; + description: string; + value: any; + }>; +} diff --git a/x-pack/plugins/observability_ai_assistant/public/components/action_menu_item/action_menu_item.tsx b/x-pack/plugins/observability_ai_assistant/public/components/action_menu_item/action_menu_item.tsx index c357d9d22e8f6..c22d32e7a49d7 100644 --- a/x-pack/plugins/observability_ai_assistant/public/components/action_menu_item/action_menu_item.tsx +++ b/x-pack/plugins/observability_ai_assistant/public/components/action_menu_item/action_menu_item.tsx @@ -44,6 +44,16 @@ export function ObservabilityAIAssistantActionMenuItem() { }; }, []); + useEffect(() => { + const unregister = service.setScreenContext({ + screenDescription: 'The user is looking at ' + window.location.href, + }); + + return () => { + unregister(); + }; + }, [service]); + if (!service.isEnabled()) { return null; } diff --git a/x-pack/plugins/observability_ai_assistant/public/components/chat/chat_body.test.tsx b/x-pack/plugins/observability_ai_assistant/public/components/chat/chat_body.test.tsx index 025860ffd656f..6db880f536e8d 100644 --- a/x-pack/plugins/observability_ai_assistant/public/components/chat/chat_body.test.tsx +++ b/x-pack/plugins/observability_ai_assistant/public/components/chat/chat_body.test.tsx @@ -38,7 +38,7 @@ describe('', () => { message: { role: 'assistant', function_call: { - name: 'recall', + name: 'context', arguments: '{"queries":[],"categories":[]}', trigger: 'assistant', }, @@ -48,7 +48,7 @@ describe('', () => { { message: { role: 'user', - name: 'recall', + name: 'context', content: '[]', }, }, @@ -86,7 +86,7 @@ describe('', () => { message: { role: 'assistant', function_call: { - name: 'recall', + name: 'context', arguments: '{"queries":[],"categories":[]}', trigger: 'assistant', }, @@ -96,7 +96,7 @@ describe('', () => { { message: { role: 'user', - name: 'recall', + name: 'context', content: '[]', }, }, diff --git a/x-pack/plugins/observability_ai_assistant/public/components/insight/insight.tsx b/x-pack/plugins/observability_ai_assistant/public/components/insight/insight.tsx index 2d57ff92cd5bc..575cf1d5a444d 100644 --- a/x-pack/plugins/observability_ai_assistant/public/components/insight/insight.tsx +++ b/x-pack/plugins/observability_ai_assistant/public/components/insight/insight.tsx @@ -51,11 +51,13 @@ function ChatContent({ initialMessages: Message[]; connectorId: string; }) { + const service = useObservabilityAIAssistant(); const chatService = useObservabilityAIAssistantChatService(); const initialMessagesRef = useRef(initialMessages); const { messages, next, state, stop } = useChat({ + service, chatService, connectorId, initialMessages, diff --git a/x-pack/plugins/observability_ai_assistant/public/context/observability_ai_assistant_multipane_flyout_provider.tsx b/x-pack/plugins/observability_ai_assistant/public/context/observability_ai_assistant_multipane_flyout_provider.tsx index 93a091ff4a7d4..4ec1faa18b274 100644 --- a/x-pack/plugins/observability_ai_assistant/public/context/observability_ai_assistant_multipane_flyout_provider.tsx +++ b/x-pack/plugins/observability_ai_assistant/public/context/observability_ai_assistant_multipane_flyout_provider.tsx @@ -6,7 +6,7 @@ */ import { createContext } from 'react'; -import type { ChatFlyoutSecondSlotHandler } from '../types'; +import type { ChatFlyoutSecondSlotHandler } from '../components/chat/types'; export const ObservabilityAIAssistantMultipaneFlyoutContext = createContext< ChatFlyoutSecondSlotHandler | undefined diff --git a/x-pack/plugins/observability_ai_assistant/public/hooks/use_chat.test.ts b/x-pack/plugins/observability_ai_assistant/public/hooks/use_chat.test.ts index a5f06d3a27c98..2cc37ea2780f9 100644 --- a/x-pack/plugins/observability_ai_assistant/public/hooks/use_chat.test.ts +++ b/x-pack/plugins/observability_ai_assistant/public/hooks/use_chat.test.ts @@ -13,6 +13,7 @@ import { StreamingChatResponseEventType, StreamingChatResponseEventWithoutError, } from '../../common/conversation_complete'; +import { mockService } from '../mock'; import type { ObservabilityAIAssistantChatService } from '../types'; import { ChatState, useChat, type UseChatProps, type UseChatResult } from './use_chat'; import * as useKibanaModule from './use_kibana'; @@ -69,6 +70,7 @@ describe('useChat', () => { }, ], persist: false, + service: mockService, } as UseChatProps, }); }); @@ -95,6 +97,7 @@ describe('useChat', () => { chatService: mockChatService, initialMessages: [], persist: false, + service: mockService, } as UseChatProps, }); diff --git a/x-pack/plugins/observability_ai_assistant/public/hooks/use_chat.ts b/x-pack/plugins/observability_ai_assistant/public/hooks/use_chat.ts index 8eb5bf48963da..8d138a1150d20 100644 --- a/x-pack/plugins/observability_ai_assistant/public/hooks/use_chat.ts +++ b/x-pack/plugins/observability_ai_assistant/public/hooks/use_chat.ts @@ -17,7 +17,10 @@ import { StreamingChatResponseEventType, } from '../../common/conversation_complete'; import { getAssistantSetupMessage } from '../service/get_assistant_setup_message'; -import type { ObservabilityAIAssistantChatService } from '../types'; +import type { + ObservabilityAIAssistantChatService, + ObservabilityAIAssistantService, +} from '../types'; import { useKibana } from './use_kibana'; import { useOnce } from './use_once'; @@ -46,6 +49,7 @@ export interface UseChatResult { export interface UseChatProps { initialMessages: Message[]; initialConversationId?: string; + service: ObservabilityAIAssistantService; chatService: ObservabilityAIAssistantChatService; connectorId?: string; persist: boolean; @@ -56,6 +60,7 @@ export interface UseChatProps { export function useChat({ initialMessages, initialConversationId, + service, chatService, connectorId, onConversationUpdate, @@ -151,6 +156,7 @@ export function useChat({ setChatState(ChatState.Loading); const next$ = chatService.complete({ + screenContexts: service.getScreenContexts(), connectorId, messages: getWithSystemMessage(nextMessages, systemMessage), persist, @@ -245,13 +251,14 @@ export function useChat({ }); }, [ - connectorId, chatService, - handleSignalAbort, - systemMessage, + connectorId, + conversationId, handleError, + handleSignalAbort, persist, - conversationId, + service, + systemMessage, ] ); diff --git a/x-pack/plugins/observability_ai_assistant/public/hooks/use_conversation.test.tsx b/x-pack/plugins/observability_ai_assistant/public/hooks/use_conversation.test.tsx index e9e2de672f2f8..74bd34c3d5934 100644 --- a/x-pack/plugins/observability_ai_assistant/public/hooks/use_conversation.test.tsx +++ b/x-pack/plugins/observability_ai_assistant/public/hooks/use_conversation.test.tsx @@ -43,6 +43,8 @@ const mockService: MockedService = { isEnabled: jest.fn(), start: jest.fn(), register: jest.fn(), + setScreenContext: jest.fn(), + getScreenContexts: jest.fn(), }; const mockChatService = createMockChatService(); diff --git a/x-pack/plugins/observability_ai_assistant/public/hooks/use_conversation.ts b/x-pack/plugins/observability_ai_assistant/public/hooks/use_conversation.ts index e9d5b3f8073e4..df3c59a316ad7 100644 --- a/x-pack/plugins/observability_ai_assistant/public/hooks/use_conversation.ts +++ b/x-pack/plugins/observability_ai_assistant/public/hooks/use_conversation.ts @@ -105,6 +105,7 @@ export function useConversation({ initialMessages, initialConversationId, chatService, + service, connectorId, onConversationUpdate: (event) => { setDisplayedConversationId(event.conversation.id); diff --git a/x-pack/plugins/observability_ai_assistant/public/mock.tsx b/x-pack/plugins/observability_ai_assistant/public/mock.tsx index 7263fe6c40a3a..bda8beba8ba5e 100644 --- a/x-pack/plugins/observability_ai_assistant/public/mock.tsx +++ b/x-pack/plugins/observability_ai_assistant/public/mock.tsx @@ -7,6 +7,7 @@ import { i18n } from '@kbn/i18n'; import type { AuthenticatedUser } from '@kbn/security-plugin-types-common'; import type { SharePluginStart } from '@kbn/share-plugin/public'; +import { noop } from 'lodash'; import React from 'react'; import { Observable } from 'rxjs'; import type { StreamingChatResponseEventWithoutError } from '../common/conversation_complete'; @@ -64,6 +65,8 @@ export const mockService: ObservabilityAIAssistantService = { navigate: () => {}, } as unknown as SharePluginStart), register: () => {}, + setScreenContext: () => noop, + getScreenContexts: () => [], }; function createSetupContract(): ObservabilityAIAssistantPluginSetup { diff --git a/x-pack/plugins/observability_ai_assistant/public/service/create_chat_service.ts b/x-pack/plugins/observability_ai_assistant/public/service/create_chat_service.ts index 5fe933835eecd..a764fbcdbad60 100644 --- a/x-pack/plugins/observability_ai_assistant/public/service/create_chat_service.ts +++ b/x-pack/plugins/observability_ai_assistant/public/service/create_chat_service.ts @@ -146,14 +146,15 @@ export async function createChatService({ hasRenderFunction: (name: string) => { return renderFunctionRegistry.has(name); }, - complete({ connectorId, messages, conversationId, persist, signal }) { + complete({ screenContexts, connectorId, conversationId, messages, persist, signal }) { return new Observable((subscriber) => { client('POST /internal/observability_ai_assistant/chat/complete', { params: { body: { - messages, connectorId, conversationId, + screenContexts, + messages, persist, }, }, diff --git a/x-pack/plugins/observability_ai_assistant/public/service/create_service.ts b/x-pack/plugins/observability_ai_assistant/public/service/create_service.ts index 721f13234591b..1b6c68e6a1f61 100644 --- a/x-pack/plugins/observability_ai_assistant/public/service/create_service.ts +++ b/x-pack/plugins/observability_ai_assistant/public/service/create_service.ts @@ -9,6 +9,8 @@ import type { AnalyticsServiceStart, CoreStart } from '@kbn/core/public'; import type { LicensingPluginStart } from '@kbn/licensing-plugin/public'; import type { SecurityPluginStart } from '@kbn/security-plugin/public'; import type { SharePluginStart } from '@kbn/share-plugin/public'; +import { remove } from 'lodash'; +import { ObservabilityAIAssistantScreenContext } from '../../common/types'; import { createCallObservabilityAIAssistantAPI } from '../api'; import type { ChatRegistrationRenderFunction, ObservabilityAIAssistantService } from '../types'; @@ -31,6 +33,8 @@ export function createService({ const registrations: ChatRegistrationRenderFunction[] = []; + const screenContexts: ObservabilityAIAssistantScreenContext[] = []; + return { isEnabled: () => { return enabled; @@ -46,5 +50,14 @@ export function createService({ getCurrentUser: () => securityStart.authc.getCurrentUser(), getLicense: () => licenseStart.license$, getLicenseManagementLocator: () => shareStart, + setScreenContext: (context: ObservabilityAIAssistantScreenContext) => { + screenContexts.push(context); + return () => { + remove(screenContexts, context); + }; + }, + getScreenContexts: () => { + return screenContexts; + }, }; } diff --git a/x-pack/plugins/observability_ai_assistant/public/types.ts b/x-pack/plugins/observability_ai_assistant/public/types.ts index e303b01a5c9e9..3a4c4fbc59d7d 100644 --- a/x-pack/plugins/observability_ai_assistant/public/types.ts +++ b/x-pack/plugins/observability_ai_assistant/public/types.ts @@ -5,6 +5,8 @@ * 2.0. */ +import type { ForwardRefExoticComponent, RefAttributes } from 'react'; +import type { Observable } from 'rxjs'; import type { AnalyticsServiceStart } from '@kbn/core/public'; import type { FeaturesPluginStart, FeaturesPluginSetup } from '@kbn/features-plugin/public'; import type { UiActionsStart } from '@kbn/ui-actions-plugin/public'; @@ -30,17 +32,16 @@ import type { TriggersAndActionsUIPublicPluginSetup, TriggersAndActionsUIPublicPluginStart, } from '@kbn/triggers-actions-ui-plugin/public'; -import { ForwardRefExoticComponent, RefAttributes } from 'react'; -import type { Observable } from 'rxjs'; import type { StreamingChatResponseEventWithoutError } from '../common/conversation_complete'; import type { ContextDefinition, FunctionDefinition, FunctionResponse, Message, + ObservabilityAIAssistantScreenContext, PendingMessage, } from '../common/types'; -import type { ChatActionClickHandler, ChatFlyoutSecondSlotHandler } from './components/chat/types'; +import type { ChatActionClickHandler } from './components/chat/types'; import type { ObservabilityAIAssistantAPIClient } from './api'; import type { InsightProps } from './components/insight/insight'; import type { UseGenAIConnectorsResult } from './hooks/use_genai_connectors'; @@ -49,7 +50,6 @@ import type { UseGenAIConnectorsResult } from './hooks/use_genai_connectors'; export type { CreateChatCompletionResponseChunk } from '../common/types'; export type { PendingMessage }; -export type { ChatFlyoutSecondSlotHandler }; export interface ObservabilityAIAssistantChatService { analytics: AnalyticsServiceStart; @@ -63,10 +63,11 @@ export interface ObservabilityAIAssistantChatService { } ) => Observable; complete: (options: { - messages: Message[]; + screenContexts: ObservabilityAIAssistantScreenContext[]; + conversationId?: string; connectorId: string; + messages: Message[]; persist: boolean; - conversationId?: string; signal: AbortSignal; }) => Observable; getContexts: () => ContextDefinition[]; @@ -89,6 +90,8 @@ export interface ObservabilityAIAssistantService { getLicenseManagementLocator: () => SharePluginStart; start: ({}: { signal: AbortSignal }) => Promise; register: (fn: ChatRegistrationRenderFunction) => void; + setScreenContext: (screenContext: ObservabilityAIAssistantScreenContext) => () => void; + getScreenContexts: () => ObservabilityAIAssistantScreenContext[]; } export type RenderFunction = (options: { diff --git a/x-pack/plugins/observability_ai_assistant/public/utils/get_timeline_items_from_conversation.test.tsx b/x-pack/plugins/observability_ai_assistant/public/utils/get_timeline_items_from_conversation.test.tsx index 8135111a6f548..4bc2d28123336 100644 --- a/x-pack/plugins/observability_ai_assistant/public/utils/get_timeline_items_from_conversation.test.tsx +++ b/x-pack/plugins/observability_ai_assistant/public/utils/get_timeline_items_from_conversation.test.tsx @@ -116,8 +116,8 @@ describe('getTimelineItemsFromConversation', () => { message: { role: MessageRole.Assistant, function_call: { - name: 'recall', - arguments: JSON.stringify({ queries: [], categories: [] }), + name: 'context', + arguments: JSON.stringify({ queries: [], contexts: [] }), trigger: MessageRole.Assistant, }, }, @@ -126,7 +126,7 @@ describe('getTimelineItemsFromConversation', () => { '@timestamp': new Date().toISOString(), message: { role: MessageRole.User, - name: 'recall', + name: 'context', content: JSON.stringify([]), }, }, @@ -158,7 +158,7 @@ describe('getTimelineItemsFromConversation', () => { ), }); - expect(container.textContent).toBe('requested the function recall'); + expect(container.textContent).toBe('requested the function context'); }); it('formats the function response', () => { @@ -184,7 +184,7 @@ describe('getTimelineItemsFromConversation', () => { ), }); - expect(container.textContent).toBe('executed the function recall'); + expect(container.textContent).toBe('executed the function context'); }); }); describe('with a render function', () => { @@ -461,8 +461,8 @@ describe('getTimelineItemsFromConversation', () => { message: { role: MessageRole.Assistant, function_call: { - name: 'recall', - arguments: JSON.stringify({ queries: [], categories: [] }), + name: 'context', + arguments: JSON.stringify({ queries: [], contexts: [] }), trigger: MessageRole.User, }, }, @@ -471,7 +471,7 @@ describe('getTimelineItemsFromConversation', () => { '@timestamp': new Date().toISOString(), message: { role: MessageRole.User, - name: 'recall', + name: 'context', content: JSON.stringify([]), }, }, diff --git a/x-pack/plugins/observability_ai_assistant/scripts/evaluation/scenarios/kb/index.spec.ts b/x-pack/plugins/observability_ai_assistant/scripts/evaluation/scenarios/kb/index.spec.ts index f0fb64dc035bb..4460fffda8355 100644 --- a/x-pack/plugins/observability_ai_assistant/scripts/evaluation/scenarios/kb/index.spec.ts +++ b/x-pack/plugins/observability_ai_assistant/scripts/evaluation/scenarios/kb/index.spec.ts @@ -28,7 +28,7 @@ describe('kb functions', () => { const result = await chatClient.evaluate(conversation, [ 'Calls the summarize function', 'Effectively summarizes and remembers that this cluster is used to test the AI Assistant using the Observability AI Evaluation Framework', - 'Calls the recall function to respond to What is this cluster used for?', + 'Calls the "context" function to respond to What is this cluster used for?', 'Effectively recalls that this cluster is used to test the AI Assistant using the Observability AI Evaluation Framework', ]); diff --git a/x-pack/plugins/observability_ai_assistant/server/functions/recall.ts b/x-pack/plugins/observability_ai_assistant/server/functions/context.ts similarity index 64% rename from x-pack/plugins/observability_ai_assistant/server/functions/recall.ts rename to x-pack/plugins/observability_ai_assistant/server/functions/context.ts index 125c9a2f6eea0..febbc4156d66d 100644 --- a/x-pack/plugins/observability_ai_assistant/server/functions/recall.ts +++ b/x-pack/plugins/observability_ai_assistant/server/functions/context.ts @@ -6,49 +6,35 @@ */ import { decodeOrThrow, jsonRt } from '@kbn/io-ts-utils'; +import { Logger } from '@kbn/logging'; import type { Serializable } from '@kbn/utility-types'; import dedent from 'dedent'; +import { encode } from 'gpt-tokenizer'; import * as t from 'io-ts'; import { compact, last, omit } from 'lodash'; -import { lastValueFrom } from 'rxjs'; -import { Logger } from '@kbn/logging'; +import { lastValueFrom, Observable } from 'rxjs'; import { FunctionRegistrationParameters } from '.'; -import { MessageRole, type Message } from '../../common/types'; +import { MessageAddEvent } from '../../common/conversation_complete'; +import { FunctionVisibility, MessageRole, type Message } from '../../common/types'; import { concatenateChatCompletionChunks } from '../../common/utils/concatenate_chat_completion_chunks'; import type { ObservabilityAIAssistantClient } from '../service/client'; +import { createFunctionResponseMessage } from '../service/util/create_function_response_message'; -export function registerRecallFunction({ +const MAX_TOKEN_COUNT_FOR_DATA_ON_SCREEN = 1000; + +export function registerContextFunction({ client, registerFunction, resources, -}: FunctionRegistrationParameters) { + isKnowledgeBaseAvailable, +}: FunctionRegistrationParameters & { isKnowledgeBaseAvailable: boolean }) { registerFunction( { - name: 'recall', + name: 'context', contexts: ['core'], - description: `Use this function to recall earlier learnings. Anything you will summarize can be retrieved again later via this function. - - The learnings are sorted by score, descending. - - Make sure the query covers ONLY the following aspects: - - Anything you've inferred from the user's request, but is not mentioned in the user's request - - The functions you think might be suitable for answering the user's request. If there are multiple functions that seem suitable, create multiple queries. Use the function name in the query. - - DO NOT include the user's request. It will be added internally. - - The user asks: "can you visualise the average request duration for opbeans-go over the last 7 days?" - You recall: { - "queries": [ - "APM service, - "lens function usage", - "get_apm_timeseries function usage" - ], - "categories": [ - "lens", - "apm" - ] - }`, - descriptionForUser: 'This function allows the assistant to recall previous learnings.', + description: + 'This function provides context as to what the user is looking at on their screen, and recalled documents from the knowledge base that matches their query', + visibility: FunctionVisibility.AssistantOnly, parameters: { type: 'object', additionalProperties: false, @@ -77,49 +63,95 @@ export function registerRecallFunction({ required: ['queries', 'categories'], } as const, }, - async ({ arguments: { queries, categories }, messages, connectorId }, signal) => { - const systemMessage = messages.find((message) => message.message.role === MessageRole.System); - - if (!systemMessage) { - throw new Error('No system message found'); - } - - const userMessage = last( - messages.filter((message) => message.message.role === MessageRole.User) - ); - - const nonEmptyQueries = compact(queries); - - const queriesOrUserPrompt = nonEmptyQueries.length - ? nonEmptyQueries - : compact([userMessage?.message.content]); + async ({ arguments: args, messages, connectorId, screenContexts }, signal) => { + const { queries, categories } = args; + + async function getContext() { + const systemMessage = messages.find( + (message) => message.message.role === MessageRole.System + ); + + const screenDescription = compact( + screenContexts.map((context) => context.screenDescription) + ).join('\n\n'); + // any data that falls within the token limit, send it automatically + + const dataWithinTokenLimit = compact( + screenContexts.flatMap((context) => context.data) + ).filter( + (data) => encode(JSON.stringify(data.value)).length <= MAX_TOKEN_COUNT_FOR_DATA_ON_SCREEN + ); + + const content = { + screen_description: screenDescription, + learnings: [], + ...(dataWithinTokenLimit.length ? { data_on_screen: dataWithinTokenLimit } : {}), + }; - const suggestions = await retrieveSuggestions({ - userMessage, - client, - categories, - queries: queriesOrUserPrompt, - }); + if (!isKnowledgeBaseAvailable) { + return { content }; + } + + if (!systemMessage) { + throw new Error('No system message found'); + } + + const userMessage = last( + messages.filter((message) => message.message.role === MessageRole.User) + ); + + const nonEmptyQueries = compact(queries); + + const queriesOrUserPrompt = nonEmptyQueries.length + ? nonEmptyQueries + : compact([userMessage?.message.content]); + + queriesOrUserPrompt.push(screenDescription); + + const suggestions = await retrieveSuggestions({ + userMessage, + client, + categories, + queries: queriesOrUserPrompt, + }); + + if (suggestions.length === 0) { + return { + content, + }; + } + + const relevantDocuments = await scoreSuggestions({ + suggestions, + queries: queriesOrUserPrompt, + messages, + client, + connectorId, + signal, + logger: resources.logger, + }); - if (suggestions.length === 0) { return { - content: [] as unknown as Serializable, + content: { ...content, learnings: relevantDocuments as unknown as Serializable }, }; } - const relevantDocuments = await scoreSuggestions({ - suggestions, - queries: queriesOrUserPrompt, - messages, - client, - connectorId, - signal, - logger: resources.logger, + return new Observable((subscriber) => { + getContext() + .then(({ content }) => { + subscriber.next( + createFunctionResponseMessage({ + name: 'context', + content, + }) + ); + + subscriber.complete(); + }) + .catch((error) => { + subscriber.error(error); + }); }); - - return { - content: relevantDocuments as unknown as Serializable, - }; } ); } diff --git a/x-pack/plugins/observability_ai_assistant/server/functions/index.ts b/x-pack/plugins/observability_ai_assistant/server/functions/index.ts index 708f77da33321..9538351f4bf4e 100644 --- a/x-pack/plugins/observability_ai_assistant/server/functions/index.ts +++ b/x-pack/plugins/observability_ai_assistant/server/functions/index.ts @@ -6,7 +6,7 @@ */ import dedent from 'dedent'; -import { registerRecallFunction } from './recall'; +import { registerContextFunction } from './context'; import { registerSummarizationFunction } from './summarize'; import { ChatRegistrationFunction } from '../service/types'; import { registerAlertsFunction } from './alerts'; @@ -19,13 +19,14 @@ import { registerVisualizeESQLFunction } from './visualize_esql'; export type FunctionRegistrationParameters = Omit< Parameters[0], - 'registerContext' + 'registerContext' | 'hasFunction' >; export const registerFunctions: ChatRegistrationFunction = async ({ client, registerContext, registerFunction, + hasFunction, resources, signal, }) => { @@ -53,13 +54,12 @@ export const registerFunctions: ChatRegistrationFunction = async ({ If multiple functions are suitable, use the most specific and easy one. E.g., when the user asks to visualise APM data, use the APM functions (if available) rather than "query". - Note that ES|QL (the Elasticsearch query language, which is NOT Elasticsearch SQL, but a new piped language) is the preferred query language. If the user wants to visualize data, or run any arbitrary query, always use the "query" function. DO NOT UNDER ANY CIRCUMSTANCES generate ES|QL queries or explain anything about the ES|QL query language yourself. - Even if the "recall" function was used before that, follow it up with the "query" function. If a query fails, do not attempt to correct it yourself. Again you should call the "query" function, + Even if the "context" function was used before that, follow it up with the "query" function. If a query fails, do not attempt to correct it yourself. Again you should call the "query" function, even if it has been called before. When the "visualize_query" function has been called, a visualization has been displayed to the user. DO NOT UNDER ANY CIRCUMSTANCES follow up a "visualize_query" function call with your own visualization attempt. @@ -68,27 +68,37 @@ export const registerFunctions: ChatRegistrationFunction = async ({ Use the "get_dataset_info" function if it is not clear what fields or indices the user means, or if you want to get more information about the mappings. If the "get_dataset_info" function returns no data, and the user asks for a query, generate a query anyway with the "query" function, but be explicit about it potentially being incorrect. + + ${ + hasFunction('get_data_on_screen') + ? `You have access to data on the screen by calling the "get_data_on_screen" function. + Use it to help the user understand what they are looking at. A short summary of what they are looking at is available in the return of the "context" function. + Data that is compact enough automatically gets included in the response for the "context" function. + ` + : '' + } ` ); if (isReady) { description += `You can use the "summarize" functions to store new information you have learned in a knowledge database. Once you have established that you did not know the answer to a question, and the user gave you this information, it's important that you create a summarisation of what you have learned and store it in the knowledge database. Don't create a new summarization if you see a similar summarization in the conversation, instead, update the existing one by re-using its ID. - Additionally, you can use the "recall" function to retrieve relevant information from the knowledge database. + Additionally, you can use the "context" function to retrieve relevant information from the knowledge database. `; registerSummarizationFunction(registrationParameters); - registerRecallFunction(registrationParameters); registerLensFunction(registrationParameters); - registerVisualizeESQLFunction(registrationParameters); } else { - description += `You do not have a working memory. Don't try to recall information via the "recall" function. If the user expects you to remember the previous conversations, tell them they can set up the knowledge base. A banner is available at the top of the conversation to set this up.`; + description += `You do not have a working memory. If the user expects you to remember the previous conversations, tell them they can set up the knowledge base.`; } + registerContextFunction({ ...registrationParameters, isKnowledgeBaseAvailable: isReady }); + registerElasticsearchFunction(registrationParameters); registerKibanaFunction(registrationParameters); registerQueryFunction(registrationParameters); + registerVisualizeESQLFunction(registrationParameters); registerAlertsFunction(registrationParameters); registerGetDatasetInfoFunction(registrationParameters); diff --git a/x-pack/plugins/observability_ai_assistant/server/functions/query/correct_common_esql_mistakes.ts b/x-pack/plugins/observability_ai_assistant/server/functions/query/correct_common_esql_mistakes.ts index 11fcd3928695b..b0877dede3a84 100644 --- a/x-pack/plugins/observability_ai_assistant/server/functions/query/correct_common_esql_mistakes.ts +++ b/x-pack/plugins/observability_ai_assistant/server/functions/query/correct_common_esql_mistakes.ts @@ -178,7 +178,7 @@ export function correctCommonEsqlMistakes(content: string, log: Logger) { const correctedFormattedQuery = formattedCommands.join('\n| '); - const originalFormattedQuery = commands.join('\n| '); + const originalFormattedQuery = commands.map((cmd) => cmd.command).join('\n| '); if (originalFormattedQuery !== correctedFormattedQuery) { log.debug(`Modified query from: ${originalFormattedQuery}\nto:\n${correctedFormattedQuery}`); diff --git a/x-pack/plugins/observability_ai_assistant/server/functions/query/index.ts b/x-pack/plugins/observability_ai_assistant/server/functions/query/index.ts index 86da0c0395587..fb6f89db825e0 100644 --- a/x-pack/plugins/observability_ai_assistant/server/functions/query/index.ts +++ b/x-pack/plugins/observability_ai_assistant/server/functions/query/index.ts @@ -9,7 +9,7 @@ import Fs from 'fs'; import { keyBy, mapValues, once, pick } from 'lodash'; import pLimit from 'p-limit'; import Path from 'path'; -import { lastValueFrom, type Observable } from 'rxjs'; +import { lastValueFrom, startWith, type Observable } from 'rxjs'; import { promisify } from 'util'; import type { FunctionRegistrationParameters } from '..'; import type { ChatCompletionChunkEvent } from '../../../common/conversation_complete'; @@ -20,6 +20,7 @@ import { import { FunctionVisibility, MessageRole } from '../../../common/types'; import { concatenateChatCompletionChunks } from '../../../common/utils/concatenate_chat_completion_chunks'; import { emitWithConcatenatedMessage } from '../../../common/utils/emit_with_concatenated_message'; +import { createFunctionResponseMessage } from '../../service/util/create_function_response_message'; import { correctCommonEsqlMistakes } from './correct_common_esql_mistakes'; const readFile = promisify(Fs.readFile); @@ -339,7 +340,8 @@ export function registerQueryFunction({ : {}), }, }; - }) + }), + startWith(createFunctionResponseMessage({ name: 'query', content: { switch: true } })) ); } ); diff --git a/x-pack/plugins/observability_ai_assistant/server/routes/chat/route.ts b/x-pack/plugins/observability_ai_assistant/server/routes/chat/route.ts index a9c58a9a59e00..38edf29a81ee6 100644 --- a/x-pack/plugins/observability_ai_assistant/server/routes/chat/route.ts +++ b/x-pack/plugins/observability_ai_assistant/server/routes/chat/route.ts @@ -11,7 +11,7 @@ import { Readable } from 'stream'; import { flushBuffer } from '../../service/util/flush_buffer'; import { observableIntoStream } from '../../service/util/observable_into_stream'; import { createObservabilityAIAssistantServerRoute } from '../create_observability_ai_assistant_server_route'; -import { messageRt } from '../runtime_types'; +import { screenContextRt, messageRt } from '../runtime_types'; const chatRoute = createObservabilityAIAssistantServerRoute({ endpoint: 'POST /internal/observability_ai_assistant/chat', @@ -84,6 +84,7 @@ const chatCompleteRoute = createObservabilityAIAssistantServerRoute({ body: t.intersection([ t.type({ messages: t.array(messageRt), + screenContexts: t.array(screenContextRt), connectorId: t.string, persist: toBooleanRt, }), @@ -106,7 +107,7 @@ const chatCompleteRoute = createObservabilityAIAssistantServerRoute({ } const { - body: { messages, connectorId, conversationId, title, persist }, + body: { messages, connectorId, conversationId, title, persist, screenContexts }, } = params; const controller = new AbortController(); @@ -119,9 +120,10 @@ const chatCompleteRoute = createObservabilityAIAssistantServerRoute({ signal: controller.signal, resources, client, + screenContexts, }); - const response$ = await client.complete({ + const response$ = client.complete({ messages, connectorId, conversationId, diff --git a/x-pack/plugins/observability_ai_assistant/server/routes/functions/route.ts b/x-pack/plugins/observability_ai_assistant/server/routes/functions/route.ts index bf64f77d45ca3..b9c3e176cf22d 100644 --- a/x-pack/plugins/observability_ai_assistant/server/routes/functions/route.ts +++ b/x-pack/plugins/observability_ai_assistant/server/routes/functions/route.ts @@ -39,6 +39,7 @@ const getFunctionsRoute = createObservabilityAIAssistantServerRoute({ signal: controller.signal, resources, client, + screenContexts: [], }); return { diff --git a/x-pack/plugins/observability_ai_assistant/server/routes/runtime_types.ts b/x-pack/plugins/observability_ai_assistant/server/routes/runtime_types.ts index 41d0d9d19492a..cef56f673e235 100644 --- a/x-pack/plugins/observability_ai_assistant/server/routes/runtime_types.ts +++ b/x-pack/plugins/observability_ai_assistant/server/routes/runtime_types.ts @@ -7,12 +7,13 @@ import * as t from 'io-ts'; import { toBooleanRt } from '@kbn/io-ts-utils'; import { - Conversation, - ConversationCreateRequest, - ConversationRequestBase, - ConversationUpdateRequest, - Message, + type Conversation, + type ConversationCreateRequest, + type ConversationRequestBase, + type ConversationUpdateRequest, + type Message, MessageRole, + type ObservabilityAIAssistantScreenContext, } from '../../common/types'; const serializeableRt = t.any; @@ -92,3 +93,14 @@ export const conversationRt: t.Type = t.intersection([ }), }), ]); + +export const screenContextRt: t.Type = t.partial({ + description: t.string, + data: t.array( + t.type({ + name: t.string, + description: t.string, + value: t.any, + }) + ), +}); diff --git a/x-pack/plugins/observability_ai_assistant/server/service/chat_function_client/index.test.ts b/x-pack/plugins/observability_ai_assistant/server/service/chat_function_client/index.test.ts index 7d34404457d24..9ad808a134250 100644 --- a/x-pack/plugins/observability_ai_assistant/server/service/chat_function_client/index.test.ts +++ b/x-pack/plugins/observability_ai_assistant/server/service/chat_function_client/index.test.ts @@ -5,8 +5,9 @@ * 2.0. */ import Ajv, { type ValidateFunction } from 'ajv'; +import dedent from 'dedent'; import { ChatFunctionClient } from '.'; -import type { ContextRegistry } from '../../../common/types'; +import { ContextRegistry, FunctionVisibility } from '../../../common/types'; import type { FunctionHandlerRegistry } from '../types'; describe('chatFunctionClient', () => { @@ -53,7 +54,28 @@ describe('chatFunctionClient', () => { ) ); - client = new ChatFunctionClient(contextRegistry, functionRegistry, validators); + client = new ChatFunctionClient([]); + client.registerContext({ + description: '', + name: 'core', + }); + + client.registerFunction( + { + contexts: ['core'], + description: '', + name: 'myFunction', + parameters: { + properties: { + foo: { + type: 'string', + }, + }, + required: ['foo'], + }, + }, + respondFn + ); }); it('throws an error', async () => { @@ -72,4 +94,75 @@ describe('chatFunctionClient', () => { expect(respondFn).not.toHaveBeenCalled(); }); }); + + describe('when providing application context', () => { + it('exposes a function that returns the requested data', async () => { + const client = new ChatFunctionClient([ + { + screenDescription: 'My description', + data: [ + { + name: 'my_dummy_data', + description: 'My dummy data', + value: [ + { + foo: 'bar', + }, + ], + }, + { + name: 'my_other_dummy_data', + description: 'My other dummy data', + value: [ + { + foo: 'bar', + }, + ], + }, + ], + }, + ]); + + const functions = client.getFunctions(); + + expect(functions[0]).toEqual({ + definition: { + contexts: ['core'], + description: expect.any(String), + name: 'get_data_on_screen', + parameters: expect.any(Object), + visibility: FunctionVisibility.AssistantOnly, + }, + respond: expect.any(Function), + }); + + expect(functions[0].definition.description).toContain( + dedent(`my_dummy_data: My dummy data + my_other_dummy_data: My other dummy data + `) + ); + + const result = await client.executeFunction({ + name: 'get_data_on_screen', + args: JSON.stringify({ data: ['my_dummy_data'] }), + messages: [], + connectorId: '', + signal: new AbortController().signal, + }); + + expect(result).toEqual({ + content: [ + { + name: 'my_dummy_data', + description: 'My dummy data', + value: [ + { + foo: 'bar', + }, + ], + }, + ], + }); + }); + }); }); diff --git a/x-pack/plugins/observability_ai_assistant/server/service/chat_function_client/index.ts b/x-pack/plugins/observability_ai_assistant/server/service/chat_function_client/index.ts index 202df11f8faa4..bc74f2046c5d2 100644 --- a/x-pack/plugins/observability_ai_assistant/server/service/chat_function_client/index.ts +++ b/x-pack/plugins/observability_ai_assistant/server/service/chat_function_client/index.ts @@ -6,16 +6,20 @@ */ /* eslint-disable max-classes-per-file*/ -import type { ErrorObject, ValidateFunction } from 'ajv'; -import { keyBy } from 'lodash'; -import type { +import Ajv, { type ErrorObject, type ValidateFunction } from 'ajv'; +import dedent from 'dedent'; +import { compact, keyBy } from 'lodash'; +import { ContextDefinition, ContextRegistry, FunctionResponse, + FunctionVisibility, Message, + ObservabilityAIAssistantScreenContext, + RegisterContextDefinition, } from '../../../common/types'; import { filterFunctionDefinitions } from '../../../common/utils/filter_function_definitions'; -import type { FunctionHandler, FunctionHandlerRegistry } from '../types'; +import type { FunctionHandler, FunctionHandlerRegistry, RegisterFunction } from '../types'; export class FunctionArgsValidationError extends Error { constructor(public readonly errors: ErrorObject[]) { @@ -23,12 +27,64 @@ export class FunctionArgsValidationError extends Error { } } +const ajv = new Ajv({ + strict: false, +}); + export class ChatFunctionClient { - constructor( - private readonly contextRegistry: ContextRegistry, - private readonly functionRegistry: FunctionHandlerRegistry, - private readonly validators: Map - ) {} + private readonly contextRegistry: ContextRegistry = new Map(); + private readonly functionRegistry: FunctionHandlerRegistry = new Map(); + private readonly validators: Map = new Map(); + + constructor(private readonly screenContexts: ObservabilityAIAssistantScreenContext[]) { + const allData = compact(screenContexts.flatMap((context) => context.data)); + + if (allData.length) { + this.registerFunction( + { + name: 'get_data_on_screen', + contexts: ['core'], + description: dedent(`Get data that is on the screen: + ${allData.map((data) => `${data.name}: ${data.description}`).join('\n')} + `), + visibility: FunctionVisibility.AssistantOnly, + parameters: { + type: 'object', + additionalProperties: false, + additionalItems: false, + properties: { + data: { + type: 'array', + description: + 'The pieces of data you want to look at it. You can request one, or multiple', + items: { + type: 'string', + enum: allData.map((data) => data.name), + }, + additionalItems: false, + additionalProperties: false, + }, + }, + required: ['data' as const], + }, + }, + async ({ arguments: { data: dataNames } }) => { + return { + content: allData.filter((data) => dataNames.includes(data.name)), + }; + } + ); + } + } + + registerFunction: RegisterFunction = (definition, respond) => { + this.validators.set(definition.name, ajv.compile(definition.parameters)); + this.functionRegistry.set(definition.name, { definition, respond }); + }; + + registerContext: RegisterContextDefinition = (context) => { + this.contextRegistry.set(context.name, context); + }; private validate(name: string, parameters: unknown) { const validator = this.validators.get(name)!; @@ -89,6 +145,9 @@ export class ChatFunctionClient { this.validate(name, parsedArguments); - return await fn.respond({ arguments: parsedArguments, messages, connectorId }, signal); + return await fn.respond( + { arguments: parsedArguments, messages, connectorId, screenContexts: this.screenContexts }, + signal + ); } } diff --git a/x-pack/plugins/observability_ai_assistant/server/service/client/adapters/bedrock_claude_adapter.ts b/x-pack/plugins/observability_ai_assistant/server/service/client/adapters/bedrock_claude_adapter.ts index d5ba0d726ab12..da1ba9cb9fdc4 100644 --- a/x-pack/plugins/observability_ai_assistant/server/service/client/adapters/bedrock_claude_adapter.ts +++ b/x-pack/plugins/observability_ai_assistant/server/service/client/adapters/bedrock_claude_adapter.ts @@ -54,9 +54,9 @@ export const createBedrockClaudeAdapter: LlmApiAdapterFactory = ({ message, consider whether it still makes sense to follow it up with another function call. ${ - functions?.find((fn) => fn.name === 'recall') - ? `The "recall" function is ALWAYS used after a user question. Even if it was used before, your job is to answer the last user question, - even if the "recall" function was executed after that. Consider the tools you need to answer the user's question.` + functions?.find((fn) => fn.name === 'context') + ? `The "context" function is ALWAYS used after a user question. Even if it was used before, your job is to answer the last user question, + even if the "context" function was executed after that. Consider the tools you need to answer the user's question.` : '' } diff --git a/x-pack/plugins/observability_ai_assistant/server/service/client/index.test.ts b/x-pack/plugins/observability_ai_assistant/server/service/client/index.test.ts index cbcbf0ea3fa3a..1c7ecb99a9ebe 100644 --- a/x-pack/plugins/observability_ai_assistant/server/service/client/index.test.ts +++ b/x-pack/plugins/observability_ai_assistant/server/service/client/index.test.ts @@ -26,6 +26,7 @@ import { import type { CreateChatCompletionResponseChunk } from '../../../public/types'; import type { ChatFunctionClient } from '../chat_function_client'; import type { KnowledgeBaseService } from '../knowledge_base_service'; +import { createFunctionResponseMessage } from '../util/create_function_response_message'; import { observableIntoStream } from '../util/observable_into_stream'; type ChunkDelta = CreateChatCompletionResponseChunk['choices'][number]['delta']; @@ -124,7 +125,7 @@ describe('Observability AI Assistant client', () => { functionClientMock.getFunctions.mockReturnValue([]); functionClientMock.hasFunction.mockImplementation((name) => { - return name !== 'recall'; + return name !== 'context'; }); actionsClientMock.get.mockResolvedValue({ @@ -986,11 +987,14 @@ describe('Observability AI Assistant client', () => { beforeEach(async () => { response$ = new Subject(); fnResponseResolve(response$); - await waitForNextWrite(stream); + + await nextTick(); + + response$.next(createFunctionResponseMessage({ name: 'my-function', content: {} })); }); - it('appends the function response', () => { - expect(JSON.parse(dataHandler.mock.lastCall!)).toEqual({ + it('appends the function response', async () => { + expect(JSON.parse(dataHandler.mock.calls[2]!)).toEqual({ type: StreamingChatResponseEventType.MessageAdd, id: expect.any(String), message: { @@ -1083,7 +1087,7 @@ describe('Observability AI Assistant client', () => { }); }); - describe('when recall is available', () => { + describe('when context is available', () => { let stream: Readable; let dataHandler: jest.Mock; @@ -1136,7 +1140,7 @@ describe('Observability AI Assistant client', () => { await finished(stream); }); - it('appends the recall request message', () => { + it('appends the context request message', () => { expect(JSON.parse(dataHandler.mock.calls[0]!)).toEqual({ type: StreamingChatResponseEventType.MessageAdd, id: expect.any(String), @@ -1146,7 +1150,7 @@ describe('Observability AI Assistant client', () => { content: '', role: MessageRole.Assistant, function_call: { - name: 'recall', + name: 'context', arguments: JSON.stringify({ queries: [], categories: [] }), trigger: MessageRole.Assistant, }, @@ -1155,7 +1159,7 @@ describe('Observability AI Assistant client', () => { }); }); - it('appends the recall response', () => { + it('appends the context response', () => { expect(JSON.parse(dataHandler.mock.calls[1]!)).toEqual({ type: StreamingChatResponseEventType.MessageAdd, id: expect.any(String), @@ -1164,7 +1168,7 @@ describe('Observability AI Assistant client', () => { message: { content: JSON.stringify([{ id: 'my_document', text: 'My document' }]), role: MessageRole.User, - name: 'recall', + name: 'context', }, }, }); diff --git a/x-pack/plugins/observability_ai_assistant/server/service/client/index.ts b/x-pack/plugins/observability_ai_assistant/server/service/client/index.ts index 2fc6bb7be34cc..70f1f250859b5 100644 --- a/x-pack/plugins/observability_ai_assistant/server/service/client/index.ts +++ b/x-pack/plugins/observability_ai_assistant/server/service/client/index.ts @@ -169,21 +169,21 @@ export class ObservabilityAIAssistantClient { const isUserMessageWithoutFunctionResponse = isUserMessage && !lastMessage?.message.name; - const recallFirst = - isUserMessageWithoutFunctionResponse && functionClient.hasFunction('recall'); + const contextFirst = + isUserMessageWithoutFunctionResponse && functionClient.hasFunction('context'); const isAssistantMessageWithFunctionRequest = lastMessage?.message.role === MessageRole.Assistant && !!lastMessage?.message.function_call?.name; - if (recallFirst) { + if (contextFirst) { const addedMessage = { '@timestamp': new Date().toISOString(), message: { role: MessageRole.Assistant, content: '', function_call: { - name: 'recall', + name: 'context', arguments: JSON.stringify({ queries: [], categories: [], @@ -201,28 +201,30 @@ export class ObservabilityAIAssistantClient { return await next(nextMessages.concat(addedMessage)); } else if (isUserMessage) { + const functions = + numFunctionsCalled >= MAX_FUNCTION_CALLS + ? [] + : functionClient + .getFunctions() + .filter((fn) => { + const visibility = fn.definition.visibility ?? FunctionVisibility.All; + return ( + visibility === FunctionVisibility.All || + visibility === FunctionVisibility.AssistantOnly + ); + }) + .map((fn) => pick(fn.definition, 'name', 'description', 'parameters')); + const response$ = ( await this.chat( - lastMessage.message.name && lastMessage.message.name !== 'recall' + lastMessage.message.name && lastMessage.message.name !== 'context' ? 'function_response' : 'user_message', { messages: nextMessages, connectorId, signal, - functions: - numFunctionsCalled >= MAX_FUNCTION_CALLS - ? [] - : functionClient - .getFunctions() - .filter((fn) => { - const visibility = fn.definition.visibility ?? FunctionVisibility.All; - return ( - visibility === FunctionVisibility.All || - visibility === FunctionVisibility.AssistantOnly - ); - }) - .map((fn) => pick(fn.definition, 'name', 'description', 'parameters')), + functions, } ) ).pipe(emitWithConcatenatedMessage(), shareReplay()); @@ -311,37 +313,7 @@ export class ObservabilityAIAssistantClient { return; } - const functionResponseIsObservable = isObservable(functionResponse); - - const functionResponseMessage = { - '@timestamp': new Date().toISOString(), - message: { - name: lastMessage.message.function_call!.name, - ...(functionResponseIsObservable - ? { content: '{}' } - : { - content: JSON.stringify(functionResponse.content || {}), - data: functionResponse.data - ? JSON.stringify(functionResponse.data) - : undefined, - }), - role: MessageRole.User, - }, - }; - - this.dependencies.logger.debug( - `Function response: ${JSON.stringify(functionResponseMessage, null, 2)}` - ); - - nextMessages = nextMessages.concat(functionResponseMessage); - - subscriber.next({ - type: StreamingChatResponseEventType.MessageAdd, - message: functionResponseMessage, - id: v4(), - }); - - if (functionResponseIsObservable) { + if (isObservable(functionResponse)) { const shared = functionResponse.pipe(shareReplay()); shared.subscribe({ @@ -365,6 +337,28 @@ export class ObservabilityAIAssistantClient { return await next(nextMessages.concat(messageEvents.map((event) => event.message))); } + const functionResponseMessage = { + '@timestamp': new Date().toISOString(), + message: { + name: lastMessage.message.function_call!.name, + + content: JSON.stringify(functionResponse.content || {}), + data: functionResponse.data ? JSON.stringify(functionResponse.data) : undefined, + role: MessageRole.User, + }, + }; + + this.dependencies.logger.debug( + `Function response: ${JSON.stringify(functionResponseMessage, null, 2)}` + ); + nextMessages = nextMessages.concat(functionResponseMessage); + + subscriber.next({ + type: StreamingChatResponseEventType.MessageAdd, + message: functionResponseMessage, + id: v4(), + }); + span?.end(); return await next(nextMessages); diff --git a/x-pack/plugins/observability_ai_assistant/server/service/index.ts b/x-pack/plugins/observability_ai_assistant/server/service/index.ts index 324ee3ed26a00..dcd0cb95de7c4 100644 --- a/x-pack/plugins/observability_ai_assistant/server/service/index.ts +++ b/x-pack/plugins/observability_ai_assistant/server/service/index.ts @@ -12,13 +12,8 @@ import type { CoreSetup, CoreStart, KibanaRequest, Logger } from '@kbn/core/serv import type { SecurityPluginStart } from '@kbn/security-plugin/server'; import { getSpaceIdFromPath } from '@kbn/spaces-plugin/common'; import type { TaskManagerSetupContract } from '@kbn/task-manager-plugin/server'; -import Ajv, { type ValidateFunction } from 'ajv'; import { once } from 'lodash'; -import { - ContextRegistry, - KnowledgeBaseEntryRole, - RegisterContextDefinition, -} from '../../common/types'; +import { KnowledgeBaseEntryRole, ObservabilityAIAssistantScreenContext } from '../../common/types'; import type { ObservabilityAIAssistantPluginStartDependencies } from '../types'; import { ChatFunctionClient } from './chat_function_client'; import { ObservabilityAIAssistantClient } from './client'; @@ -27,17 +22,11 @@ import { kbComponentTemplate } from './kb_component_template'; import { KnowledgeBaseEntryOperationType, KnowledgeBaseService } from './knowledge_base_service'; import type { ChatRegistrationFunction, - FunctionHandlerRegistry, ObservabilityAIAssistantResourceNames, - RegisterFunction, RespondFunctionResources, } from './types'; import { splitKbText } from './util/split_kb_text'; -const ajv = new Ajv({ - strict: false, -}); - function getResourceName(resource: string) { return `.kibana-observability-ai-assistant-${resource}`; } @@ -297,37 +286,37 @@ export class ObservabilityAIAssistantService { } async getFunctionClient({ + screenContexts, signal, resources, client, }: { + screenContexts: ObservabilityAIAssistantScreenContext[]; signal: AbortSignal; resources: RespondFunctionResources; client: ObservabilityAIAssistantClient; }): Promise { - const contextRegistry: ContextRegistry = new Map(); - const functionHandlerRegistry: FunctionHandlerRegistry = new Map(); - - const validators = new Map(); - - const registerContext: RegisterContextDefinition = (context) => { - contextRegistry.set(context.name, context); + const fnClient = new ChatFunctionClient(screenContexts); + + const params = { + signal, + registerContext: fnClient.registerContext.bind(fnClient), + registerFunction: fnClient.registerFunction.bind(fnClient), + hasFunction: fnClient.hasFunction.bind(fnClient), + resources, + client, }; - const registerFunction: RegisterFunction = (definition, respond) => { - validators.set(definition.name, ajv.compile(definition.parameters)); - functionHandlerRegistry.set(definition.name, { definition, respond }); - }; await Promise.all( this.registrations.map((fn) => - fn({ signal, registerContext, registerFunction, resources, client }).catch((error) => { + fn(params).catch((error) => { this.logger.error(`Error registering functions`); this.logger.error(error); }) ) ); - return new ChatFunctionClient(contextRegistry, functionHandlerRegistry, validators); + return fnClient; } addToKnowledgeBase(entries: KnowledgeBaseEntryRequest[]): void { diff --git a/x-pack/plugins/observability_ai_assistant/server/service/types.ts b/x-pack/plugins/observability_ai_assistant/server/service/types.ts index d89bfd546c702..2215f565886bc 100644 --- a/x-pack/plugins/observability_ai_assistant/server/service/types.ts +++ b/x-pack/plugins/observability_ai_assistant/server/service/types.ts @@ -11,9 +11,11 @@ import type { FunctionDefinition, FunctionResponse, Message, + ObservabilityAIAssistantScreenContext, RegisterContextDefinition, } from '../../common/types'; import type { ObservabilityAIAssistantRouteHandlerResources } from '../routes/types'; +import { ChatFunctionClient } from './chat_function_client'; import type { ObservabilityAIAssistantClient } from './client'; export type RespondFunctionResources = Pick< @@ -26,6 +28,7 @@ type RespondFunction = ( arguments: TArguments; messages: Message[]; connectorId: string; + screenContexts: ObservabilityAIAssistantScreenContext[]; }, signal: AbortSignal ) => Promise; @@ -51,6 +54,7 @@ export type ChatRegistrationFunction = ({}: { client: ObservabilityAIAssistantClient; registerFunction: RegisterFunction; registerContext: RegisterContextDefinition; + hasFunction: ChatFunctionClient['hasFunction']; }) => Promise; export interface ObservabilityAIAssistantResourceNames { diff --git a/x-pack/plugins/observability_ai_assistant/server/service/util/create_function_request_message.ts b/x-pack/plugins/observability_ai_assistant/server/service/util/create_function_request_message.ts new file mode 100644 index 0000000000000..8c38b03040794 --- /dev/null +++ b/x-pack/plugins/observability_ai_assistant/server/service/util/create_function_request_message.ts @@ -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 { v4 } from 'uuid'; +import { MessageRole } from '../../../common'; +import { + MessageAddEvent, + StreamingChatResponseEventType, +} from '../../../common/conversation_complete'; + +export function createFunctionRequestMessage({ + name, + args, +}: { + name: string; + args: unknown; +}): MessageAddEvent { + return { + id: v4(), + type: StreamingChatResponseEventType.MessageAdd as const, + message: { + '@timestamp': new Date().toISOString(), + message: { + function_call: { + name, + arguments: JSON.stringify(args), + trigger: MessageRole.Assistant as const, + }, + role: MessageRole.Assistant, + }, + }, + }; +} diff --git a/x-pack/plugins/observability_ai_assistant/server/service/util/create_function_response_message.ts b/x-pack/plugins/observability_ai_assistant/server/service/util/create_function_response_message.ts new file mode 100644 index 0000000000000..186ff117734c3 --- /dev/null +++ b/x-pack/plugins/observability_ai_assistant/server/service/util/create_function_response_message.ts @@ -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 { v4 } from 'uuid'; +import { MessageRole } from '../../../common'; +import { + type MessageAddEvent, + StreamingChatResponseEventType, +} from '../../../common/conversation_complete'; + +export function createFunctionResponseMessage({ + name, + content, + data, +}: { + name: string; + content: unknown; + data?: unknown; +}): MessageAddEvent { + return { + id: v4(), + type: StreamingChatResponseEventType.MessageAdd as const, + message: { + '@timestamp': new Date().toISOString(), + message: { + content: JSON.stringify(content), + ...(data ? { data: JSON.stringify(data) } : {}), + name, + role: MessageRole.User, + }, + }, + }; +} diff --git a/x-pack/test/observability_ai_assistant_api_integration/tests/complete/complete.spec.ts b/x-pack/test/observability_ai_assistant_api_integration/tests/complete/complete.spec.ts index 82ad5b6dd1224..c4913ce3e41d2 100644 --- a/x-pack/test/observability_ai_assistant_api_integration/tests/complete/complete.spec.ts +++ b/x-pack/test/observability_ai_assistant_api_integration/tests/complete/complete.spec.ts @@ -10,7 +10,9 @@ import { omit } from 'lodash'; import { PassThrough } from 'stream'; import expect from '@kbn/expect'; import { + ChatCompletionChunkEvent, ConversationCreateEvent, + MessageAddEvent, StreamingChatResponseEvent, StreamingChatResponseEventType, } from '@kbn/observability-ai-assistant-plugin/common/conversation_complete'; @@ -91,6 +93,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { messages, connectorId, persist: false, + screenContexts: [], }) .pipe(passThrough); @@ -109,22 +112,63 @@ export default function ApiTest({ getService }: FtrProviderContext) { await new Promise((resolve) => passThrough.on('end', () => resolve())); - const parsedChunks = receivedChunks + const parsedEvents = receivedChunks .join('') .split('\n') .map((line) => line.trim()) .filter(Boolean) .map((line) => JSON.parse(line) as StreamingChatResponseEvent); - expect(parsedChunks.length).to.be(2); - expect(omit(parsedChunks[0], 'id')).to.eql({ + expect(parsedEvents.map((event) => event.type)).to.eql([ + StreamingChatResponseEventType.MessageAdd, + StreamingChatResponseEventType.MessageAdd, + StreamingChatResponseEventType.ChatCompletionChunk, + StreamingChatResponseEventType.MessageAdd, + ]); + + const messageEvents = parsedEvents.filter( + (msg): msg is MessageAddEvent => msg.type === StreamingChatResponseEventType.MessageAdd + ); + + const chunkEvents = parsedEvents.filter( + (msg): msg is ChatCompletionChunkEvent => + msg.type === StreamingChatResponseEventType.ChatCompletionChunk + ); + + expect(omit(messageEvents[0], 'id', 'message.@timestamp')).to.eql({ + type: StreamingChatResponseEventType.MessageAdd, + message: { + message: { + content: '', + role: MessageRole.Assistant, + function_call: { + name: 'context', + arguments: JSON.stringify({ queries: [], categories: [] }), + trigger: MessageRole.Assistant, + }, + }, + }, + }); + + expect(omit(messageEvents[1], 'id', 'message.@timestamp')).to.eql({ + type: StreamingChatResponseEventType.MessageAdd, + message: { + message: { + role: MessageRole.User, + name: 'context', + content: JSON.stringify({ screen_description: '', learnings: [] }), + }, + }, + }); + + expect(omit(chunkEvents[0], 'id')).to.eql({ type: StreamingChatResponseEventType.ChatCompletionChunk, message: { content: 'Hello', }, }); - expect(omit(parsedChunks[1], 'id', 'message.@timestamp')).to.eql({ + expect(omit(messageEvents[2], 'id', 'message.@timestamp')).to.eql({ type: StreamingChatResponseEventType.MessageAdd, message: { message: { @@ -167,6 +211,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { messages, connectorId, persist: true, + screenContexts: [], }) .end((err, response) => { if (err) { @@ -196,7 +241,8 @@ export default function ApiTest({ getService }: FtrProviderContext) { .split('\n') .map((line) => line.trim()) .filter(Boolean) - .map((line) => JSON.parse(line) as StreamingChatResponseEvent); + .map((line) => JSON.parse(line) as StreamingChatResponseEvent) + .slice(2); // ignore context request/response, we're testing this elsewhere }); it('creates a new conversation', async () => { diff --git a/x-pack/test/observability_ai_assistant_functional/tests/conversations/index.spec.ts b/x-pack/test/observability_ai_assistant_functional/tests/conversations/index.spec.ts index c0b2b36dfc029..66badaab026ab 100644 --- a/x-pack/test/observability_ai_assistant_functional/tests/conversations/index.spec.ts +++ b/x-pack/test/observability_ai_assistant_functional/tests/conversations/index.spec.ts @@ -6,6 +6,7 @@ */ import expect from '@kbn/expect'; +import { pick } from 'lodash'; import type OpenAI from 'openai'; import { createLlmProxy, @@ -188,10 +189,39 @@ export default function ApiTest({ getService, getPageObjects }: FtrProviderConte expect(response.body.conversations.length).to.eql(1); - expect(response.body.conversations[0].messages.length).to.eql(3); - expect(response.body.conversations[0].conversation.title).to.be('My title'); + const { messages } = response.body.conversations[0]; + + expect(messages.length).to.eql(5); + + const [ + systemMessage, + firstUserMessage, + contextRequest, + contextResponse, + assistantResponse, + ] = messages.map((msg) => msg.message); + + expect(systemMessage.role).to.eql('system'); + + expect(firstUserMessage.content).to.eql('hello'); + + expect(pick(contextRequest.function_call, 'name', 'arguments')).to.eql({ + name: 'context', + arguments: JSON.stringify({ queries: [], categories: [] }), + }); + + expect(pick(contextResponse, 'name', 'content')).to.eql({ + name: 'context', + content: JSON.stringify({ screen_description: '', learnings: [] }), + }); + + expect(pick(assistantResponse, 'role', 'content')).to.eql({ + role: 'assistant', + content: 'My response', + }); + await common.waitUntilUrlIncludes( `/conversations/${response.body.conversations[0].conversation.id}` ); @@ -227,7 +257,31 @@ export default function ApiTest({ getService, getPageObjects }: FtrProviderConte endpoint: 'POST /internal/observability_ai_assistant/conversations', }); - expect(response.body.conversations[0].messages.length).to.eql(5); + const messages = response.body.conversations[0].messages.slice(5); + + expect(messages.length).to.eql(4); + + const [userReply, contextRequest, contextResponse, assistantResponse] = + messages.map((msg) => msg.message); + + expect(userReply.content).to.eql('hello'); + + expect(pick(contextRequest.function_call, 'name', 'arguments')).to.eql({ + name: 'context', + arguments: JSON.stringify({ queries: [], categories: [] }), + }); + + expect(pick(contextResponse, 'name', 'content')).to.eql({ + name: 'context', + content: JSON.stringify({ screen_description: '', learnings: [] }), + }); + + expect(pick(assistantResponse, 'role', 'content')).to.eql({ + role: 'assistant', + content: 'My second response', + }); + + expect(response.body.conversations[0].messages.length).to.eql(9); }); }); }); From 15c615319ebc598f51df501e1eb0d561168aa600 Mon Sep 17 00:00:00 2001 From: Davis McPhee Date: Tue, 13 Feb 2024 11:26:22 -0400 Subject: [PATCH 70/83] [Unified Search] Disable "Load query" button when `onClearSavedQuery` is undefined (#176756) ## Summary This PR disables the "Load query" button in Unified Search when the `onClearSavedQuery` callback is undefined. Previously it was possible to click the "Load query" button, but an empty popover would be shown instead of the saved query list, such as on the SLOs page. SLOs page currently: https://github.com/elastic/kibana/assets/25592674/3df21ac5-b727-4aa1-95c4-f81d7bf4a21b "Load query" disabled: disabled This isn't really a fix to the underlying problem, it's just a quick workaround to ensure the empty popover isn't shown. A full solution would probably involve marking `onClearSavedQuery` as a required prop and updating existing usages where its undefined, or updating Unified Search to not display saved query actions if certain props aren't defined. ### Checklist - [ ] 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 - [ ] [Flaky Test Runner](https://ci-stats.kibana.dev/trigger_flaky_test_runner/1) was used on any tests changed - [ ] 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) ### 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) --- .../public/query_string_input/query_bar_menu_panels.tsx | 2 +- src/plugins/unified_search/public/search_bar/search_bar.tsx | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/plugins/unified_search/public/query_string_input/query_bar_menu_panels.tsx b/src/plugins/unified_search/public/query_string_input/query_bar_menu_panels.tsx index 6ca40656467e5..a7d772e312bab 100644 --- a/src/plugins/unified_search/public/query_string_input/query_bar_menu_panels.tsx +++ b/src/plugins/unified_search/public/query_string_input/query_bar_menu_panels.tsx @@ -373,7 +373,7 @@ export function useQueryBarMenuPanels({ panel: QueryBarMenuPanel.loadQuery, icon: 'filter', 'data-test-subj': 'saved-query-management-load-button', - disabled: !hasSavedQueries, + disabled: !hasSavedQueries || !Boolean(manageFilterSetComponent), }, { name: savedQuery ? strings.getSaveAsNewFilterSetLabel() : strings.getSaveFilterSetLabel(), diff --git a/src/plugins/unified_search/public/search_bar/search_bar.tsx b/src/plugins/unified_search/public/search_bar/search_bar.tsx index 77755ccd6a990..6ce80367cb323 100644 --- a/src/plugins/unified_search/public/search_bar/search_bar.tsx +++ b/src/plugins/unified_search/public/search_bar/search_bar.tsx @@ -543,7 +543,7 @@ class SearchBarUI extends C timeRangeForSuggestionsOverride={timeRangeForSuggestionsOverride} filtersForSuggestions={this.props.filtersForSuggestions} manageFilterSetComponent={ - this.props.showFilterBar && this.state.query + this.props.showFilterBar && this.state.query && this.props.onClearSavedQuery ? this.renderSavedQueryManagement( this.props.onClearSavedQuery, this.props.showSaveQuery, @@ -649,11 +649,11 @@ class SearchBarUI extends C private renderSavedQueryManagement = memoizeOne( ( - onClearSavedQuery: SearchBarOwnProps['onClearSavedQuery'], + onClearSavedQuery: NonNullable, showSaveQuery: SearchBarOwnProps['showSaveQuery'], savedQuery: SearchBarOwnProps['savedQuery'] ) => { - const savedQueryManagement = onClearSavedQuery && ( + const savedQueryManagement = ( Date: Tue, 13 Feb 2024 16:27:23 +0100 Subject: [PATCH 71/83] [UnifiedFieldList] Categorize fields as empty that never had a value in matching indices (#174063) With this commit the UnifiedFieldList used in Discover and Lens will categorize fields that never had any value in matching indices as "Empty fields". This was built by using the new `include_fields_with_no_value` parameter of Elasticsearch field_caps API. Co-authored-by: Matt Kime --- .../field_existing/field_existing_utils.ts | 1 + src/plugins/data_views/common/types.ts | 1 + .../data_views/data_views_api_client.ts | 2 + .../server/fetcher/index_patterns_fetcher.ts | 3 + .../data_views/server/fetcher/lib/es_api.ts | 4 ++ .../field_capabilities.test.js | 1 + .../field_capabilities/field_capabilities.ts | 3 + .../rest_api_routes/internal/fields_for.ts | 4 ++ .../apps/discover/group3/_drag_drop.ts | 4 +- .../apps/discover/group3/_sidebar.ts | 34 +++++----- .../discover/group4/_field_list_new_fields.ts | 62 +++++++++++++++++++ .../apps/lens/group2/fields_list.ts | 2 +- .../common/discover/group3/_sidebar.ts | 32 +++++----- 13 files changed, 115 insertions(+), 38 deletions(-) diff --git a/packages/kbn-unified-field-list/src/services/field_existing/field_existing_utils.ts b/packages/kbn-unified-field-list/src/services/field_existing/field_existing_utils.ts index 5cceb49cb5ecb..3105d94402086 100644 --- a/packages/kbn-unified-field-list/src/services/field_existing/field_existing_utils.ts +++ b/packages/kbn-unified-field-list/src/services/field_existing/field_existing_utils.ts @@ -53,6 +53,7 @@ export async function fetchFieldExistence({ // filled in by data views service pattern: '', indexFilter: toQuery(timeFieldName, fromDate, toDate, dslQuery), + includeEmptyFields: false, }); // take care of fields of existingFieldList, that are not yet available diff --git a/src/plugins/data_views/common/types.ts b/src/plugins/data_views/common/types.ts index caf1fc80dc5b6..b669ed1496133 100644 --- a/src/plugins/data_views/common/types.ts +++ b/src/plugins/data_views/common/types.ts @@ -315,6 +315,7 @@ export interface GetFieldsOptions { fields?: string[]; allowHidden?: boolean; forceRefresh?: boolean; + includeEmptyFields?: boolean; } /** diff --git a/src/plugins/data_views/public/data_views/data_views_api_client.ts b/src/plugins/data_views/public/data_views/data_views_api_client.ts index 48d5db17c0915..7fecbae92431f 100644 --- a/src/plugins/data_views/public/data_views/data_views_api_client.ts +++ b/src/plugins/data_views/public/data_views/data_views_api_client.ts @@ -95,6 +95,7 @@ export class DataViewsApiClient implements IDataViewsApiClient { fields, forceRefresh, allowHidden, + includeEmptyFields, } = options; const path = indexFilter ? FIELDS_FOR_WILDCARD_PATH : FIELDS_PATH; const versionQueryParam = indexFilter ? {} : { apiVersion: version }; @@ -111,6 +112,7 @@ export class DataViewsApiClient implements IDataViewsApiClient { fields, // default to undefined to keep value out of URL params and improve caching allow_hidden: allowHidden || undefined, + include_empty_fields: includeEmptyFields, ...versionQueryParam, }, indexFilter ? JSON.stringify({ index_filter: indexFilter }) : undefined, diff --git a/src/plugins/data_views/server/fetcher/index_patterns_fetcher.ts b/src/plugins/data_views/server/fetcher/index_patterns_fetcher.ts index 15ee00e5118cb..f444ddbc434f0 100644 --- a/src/plugins/data_views/server/fetcher/index_patterns_fetcher.ts +++ b/src/plugins/data_views/server/fetcher/index_patterns_fetcher.ts @@ -73,6 +73,7 @@ export class IndexPatternsFetcher { indexFilter?: QueryDslQueryContainer; fields?: string[]; allowHidden?: boolean; + includeEmptyFields?: boolean; }): Promise<{ fields: FieldDescriptor[]; indices: string[] }> { const { pattern, @@ -82,6 +83,7 @@ export class IndexPatternsFetcher { rollupIndex, indexFilter, allowHidden, + includeEmptyFields, } = options; const allowNoIndices = fieldCapsOptions ? fieldCapsOptions.allow_no_indices @@ -100,6 +102,7 @@ export class IndexPatternsFetcher { indexFilter, fields: options.fields || ['*'], expandWildcards, + includeEmptyFields, }); if (this.rollupsEnabled && type === 'rollup' && rollupIndex) { diff --git a/src/plugins/data_views/server/fetcher/lib/es_api.ts b/src/plugins/data_views/server/fetcher/lib/es_api.ts index 62ad89a87f50b..ca2d55da536f4 100644 --- a/src/plugins/data_views/server/fetcher/lib/es_api.ts +++ b/src/plugins/data_views/server/fetcher/lib/es_api.ts @@ -47,6 +47,7 @@ interface FieldCapsApiParams { indexFilter?: QueryDslQueryContainer; fields?: string[]; expandWildcards?: ExpandWildcard; + includeEmptyFields?: boolean; } /** @@ -72,6 +73,7 @@ export async function callFieldCapsApi(params: FieldCapsApiParams) { }, fields = ['*'], expandWildcards, + includeEmptyFields, } = params; try { return await callCluster.fieldCaps( @@ -81,6 +83,8 @@ export async function callFieldCapsApi(params: FieldCapsApiParams) { ignore_unavailable: true, index_filter: indexFilter, expand_wildcards: expandWildcards, + // @ts-expect-error + include_empty_fields: includeEmptyFields ?? true, ...fieldCapsOptions, }, { meta: true } diff --git a/src/plugins/data_views/server/fetcher/lib/field_capabilities/field_capabilities.test.js b/src/plugins/data_views/server/fetcher/lib/field_capabilities/field_capabilities.test.js index 8ea934e9f703e..8b27084100b5b 100644 --- a/src/plugins/data_views/server/fetcher/lib/field_capabilities/field_capabilities.test.js +++ b/src/plugins/data_views/server/fetcher/lib/field_capabilities/field_capabilities.test.js @@ -38,6 +38,7 @@ describe('index_patterns/field_capabilities/field_capabilities', () => { fieldCapsOptions: undefined, indexFilter: undefined, fields: undefined, + includeEmptyFields: undefined, ...args, }); diff --git a/src/plugins/data_views/server/fetcher/lib/field_capabilities/field_capabilities.ts b/src/plugins/data_views/server/fetcher/lib/field_capabilities/field_capabilities.ts index 1ca5a6a734470..c0091d987a359 100644 --- a/src/plugins/data_views/server/fetcher/lib/field_capabilities/field_capabilities.ts +++ b/src/plugins/data_views/server/fetcher/lib/field_capabilities/field_capabilities.ts @@ -24,6 +24,7 @@ interface FieldCapabilitiesParams { indexFilter?: QueryDslQueryContainer; fields?: string[]; expandWildcards?: ExpandWildcard; + includeEmptyFields?: boolean; } /** @@ -45,6 +46,7 @@ export async function getFieldCapabilities(params: FieldCapabilitiesParams) { metaFields = [], fields, expandWildcards, + includeEmptyFields, } = params; const esFieldCaps = await callFieldCapsApi({ callCluster, @@ -53,6 +55,7 @@ export async function getFieldCapabilities(params: FieldCapabilitiesParams) { indexFilter, fields, expandWildcards, + includeEmptyFields, }); const fieldCapsArr = readFieldCapsResponse(esFieldCaps.body); const fieldsFromFieldCapsByName = keyBy(fieldCapsArr, 'name'); diff --git a/src/plugins/data_views/server/rest_api_routes/internal/fields_for.ts b/src/plugins/data_views/server/rest_api_routes/internal/fields_for.ts index 1d5f8e636315a..e88a744ef450d 100644 --- a/src/plugins/data_views/server/rest_api_routes/internal/fields_for.ts +++ b/src/plugins/data_views/server/rest_api_routes/internal/fields_for.ts @@ -55,6 +55,7 @@ export interface IQuery { include_unmapped?: boolean; fields?: string[]; allow_hidden?: boolean; + include_empty_fields?: boolean; } export const querySchema = schema.object({ @@ -68,6 +69,7 @@ export const querySchema = schema.object({ include_unmapped: schema.maybe(schema.boolean()), fields: schema.maybe(schema.oneOf([schema.string(), schema.arrayOf(schema.string())])), allow_hidden: schema.maybe(schema.boolean()), + include_empty_fields: schema.maybe(schema.boolean()), }); const fieldSubTypeSchema = schema.object({ @@ -135,6 +137,7 @@ const handler: (isRollupsEnabled: () => boolean) => RequestHandler<{}, IQuery, I allow_no_index: allowNoIndex, include_unmapped: includeUnmapped, allow_hidden: allowHidden, + include_empty_fields: includeEmptyFields, } = request.query; // not available to get request @@ -161,6 +164,7 @@ const handler: (isRollupsEnabled: () => boolean) => RequestHandler<{}, IQuery, I }, indexFilter: getIndexFilterDsl({ indexFilter, excludedTiers }), allowHidden, + includeEmptyFields, ...(parsedFields.length > 0 ? { fields: parsedFields } : {}), }); diff --git a/test/functional/apps/discover/group3/_drag_drop.ts b/test/functional/apps/discover/group3/_drag_drop.ts index 3988daa287bd6..f545651db42fd 100644 --- a/test/functional/apps/discover/group3/_drag_drop.ts +++ b/test/functional/apps/discover/group3/_drag_drop.ts @@ -48,7 +48,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await PageObjects.unifiedFieldList.waitUntilSidebarHasLoaded(); expect(await PageObjects.unifiedFieldList.getSidebarAriaDescription()).to.be( - '53 available fields. 3 meta fields.' + '48 available fields. 5 empty fields. 3 meta fields.' ); expect((await PageObjects.discover.getColumnHeaders()).join(', ')).to.be( '@timestamp, Document' @@ -72,7 +72,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await PageObjects.unifiedFieldList.waitUntilSidebarHasLoaded(); expect(await PageObjects.unifiedFieldList.getSidebarAriaDescription()).to.be( - '53 available fields. 3 meta fields.' + '48 available fields. 5 empty fields. 3 meta fields.' ); expect((await PageObjects.discover.getColumnHeaders()).join(', ')).to.be( '@timestamp, Document' diff --git a/test/functional/apps/discover/group3/_sidebar.ts b/test/functional/apps/discover/group3/_sidebar.ts index ba903c446c77e..c81722ec78cbf 100644 --- a/test/functional/apps/discover/group3/_sidebar.ts +++ b/test/functional/apps/discover/group3/_sidebar.ts @@ -28,7 +28,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const fieldEditor = getService('fieldEditor'); const retry = getService('retry'); const dataGrid = getService('dataGrid'); - const INITIAL_FIELD_LIST_SUMMARY = '53 available fields. 3 meta fields.'; + const INITIAL_FIELD_LIST_SUMMARY = '48 available fields. 5 empty fields. 3 meta fields.'; describe('discover sidebar', function describeIndexTests() { before(async function () { @@ -72,7 +72,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await retry.waitFor('first updates', async () => { return ( (await PageObjects.unifiedFieldList.getSidebarAriaDescription()) === - '7 available fields. 2 meta fields.' + '6 available fields. 1 empty field. 2 meta fields.' ); }); @@ -81,7 +81,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await retry.waitFor('second updates', async () => { return ( (await PageObjects.unifiedFieldList.getSidebarAriaDescription()) === - '13 available fields. 3 meta fields.' + '10 available fields. 3 empty fields. 3 meta fields.' ); }); @@ -178,7 +178,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await retry.waitFor('first updates', async () => { return ( (await PageObjects.unifiedFieldList.getSidebarAriaDescription()) === - '30 available fields. 2 meta fields.' + '28 available fields. 2 empty fields. 2 meta fields.' ); }); @@ -315,11 +315,11 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { // Initial Available fields const expectedInitialAvailableFields = - '@message, @tags, @timestamp, agent, bytes, clientip, extension, geo.coordinates, geo.dest, geo.src, geo.srcdest, headings, host, id, index, ip, links, machine.os, machine.ram, machine.ram_range, memory, meta.char, meta.related, meta.user.firstname, meta.user.lastname, nestedField.child, phpmemory, referer, relatedContent.article:modified_time, relatedContent.article:published_time, relatedContent.article:section, relatedContent.article:tag, relatedContent.og:description, relatedContent.og:image, relatedContent.og:image:height, relatedContent.og:image:width, relatedContent.og:site_name, relatedContent.og:title, relatedContent.og:type, relatedContent.og:url, relatedContent.twitter:card, relatedContent.twitter:description, relatedContent.twitter:image, relatedContent.twitter:site, relatedContent.twitter:title, relatedContent.url, request, response, spaces, type'; + '@message, @tags, @timestamp, agent, bytes, clientip, extension, geo.coordinates, geo.dest, geo.src, geo.srcdest, headings, host, index, ip, links, machine.os, machine.ram, machine.ram_range, memory, nestedField.child, phpmemory, referer, relatedContent.article:modified_time, relatedContent.article:published_time, relatedContent.article:section, relatedContent.article:tag, relatedContent.og:description, relatedContent.og:image, relatedContent.og:image:height, relatedContent.og:image:width, relatedContent.og:site_name, relatedContent.og:title, relatedContent.og:type, relatedContent.og:url, relatedContent.twitter:card, relatedContent.twitter:description, relatedContent.twitter:image, relatedContent.twitter:site, relatedContent.twitter:title, relatedContent.url, request, response, spaces, type, url, utc_time, xss'; let availableFields = await PageObjects.unifiedFieldList.getSidebarSectionFieldNames( 'available' ); - expect(availableFields.length).to.be(50); + expect(availableFields.length).to.be(48); expect(availableFields.join(', ')).to.be(expectedInitialAvailableFields); // Available fields after scrolling down @@ -332,12 +332,10 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { availableFields = await PageObjects.unifiedFieldList.getSidebarSectionFieldNames( 'available' ); - return availableFields.length === 53; + return availableFields.length === 48; }); - expect(availableFields.join(', ')).to.be( - `${expectedInitialAvailableFields}, url, utc_time, xss` - ); + expect(availableFields.join(', ')).to.be(`${expectedInitialAvailableFields}`); // Expand Meta section await PageObjects.unifiedFieldList.toggleSidebarSection('meta'); @@ -361,7 +359,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const availableFields = await PageObjects.unifiedFieldList.getSidebarSectionFieldNames( 'available' ); - expect(availableFields.length).to.be(50); + expect(availableFields.length).to.be(48); expect( availableFields .join(', ') @@ -389,7 +387,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { ).to.be('relatedContent'); expect(await PageObjects.unifiedFieldList.getSidebarAriaDescription()).to.be( - '53 available fields. 1 unmapped field. 3 meta fields.' + '48 available fields. 1 unmapped field. 5 empty fields. 3 meta fields.' ); }); @@ -410,7 +408,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { expect(availableFields.includes('@message')).to.be(true); expect(await PageObjects.unifiedFieldList.getSidebarAriaDescription()).to.be( - '2 selected fields. 2 popular fields. 53 available fields. 3 meta fields.' + '2 selected fields. 2 popular fields. 48 available fields. 5 empty fields. 3 meta fields.' ); await PageObjects.unifiedFieldList.clickFieldListItemRemove('@message'); @@ -430,7 +428,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { ).to.be('@message, _id, extension'); expect(await PageObjects.unifiedFieldList.getSidebarAriaDescription()).to.be( - '3 selected fields. 3 popular fields. 53 available fields. 3 meta fields.' + '3 selected fields. 3 popular fields. 48 available fields. 5 empty fields. 3 meta fields.' ); }); @@ -485,7 +483,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await PageObjects.unifiedFieldList.waitUntilSidebarHasLoaded(); expect(await PageObjects.unifiedFieldList.getSidebarAriaDescription()).to.be( - '53 available fields. 3 meta fields.' + '48 available fields. 5 empty fields. 3 meta fields.' ); }); @@ -670,7 +668,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await PageObjects.unifiedFieldList.waitUntilSidebarHasLoaded(); expect(await PageObjects.unifiedFieldList.getSidebarAriaDescription()).to.be( - '54 available fields. 3 meta fields.' + '49 available fields. 5 empty fields. 3 meta fields.' ); let allFields = await PageObjects.unifiedFieldList.getAllFieldNames(); @@ -689,7 +687,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await PageObjects.unifiedFieldList.waitUntilSidebarHasLoaded(); expect(await PageObjects.unifiedFieldList.getSidebarAriaDescription()).to.be( - '54 available fields. 3 meta fields.' + '49 available fields. 5 empty fields. 3 meta fields.' ); allFields = await PageObjects.unifiedFieldList.getAllFieldNames(); @@ -726,7 +724,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { // check that the sidebar is rendered expect(await PageObjects.unifiedFieldList.getSidebarAriaDescription()).to.be( - '54 available fields. 3 meta fields.' + '49 available fields. 5 empty fields. 3 meta fields.' ); let allFields = await PageObjects.unifiedFieldList.getAllFieldNames(); expect(allFields.includes('_invalid-runtimefield')).to.be(true); diff --git a/test/functional/apps/discover/group4/_field_list_new_fields.ts b/test/functional/apps/discover/group4/_field_list_new_fields.ts index 3c24bcf613ae4..4646a57600b4a 100644 --- a/test/functional/apps/discover/group4/_field_list_new_fields.ts +++ b/test/functional/apps/discover/group4/_field_list_new_fields.ts @@ -35,6 +35,10 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { path: '/my-index-000001', method: 'DELETE', }); + await es.transport.request({ + path: '/my-index-000002', + method: 'DELETE', + }); }); it('Check that new ingested fields are added to the available fields section', async function () { @@ -82,5 +86,63 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { 'b', ]); }); + + it("Mapped fields without values aren't shown", async function () { + const initialPattern = 'my-index-000002'; + await es.transport.request({ + path: '/my-index-000002/_doc', + method: 'POST', + body: { + '@timestamp': new Date().toISOString(), + a: 'GET /search HTTP/1.1 200 1070000', + }, + }); + + await PageObjects.discover.createAdHocDataView(initialPattern, true); + + await retry.waitFor('current data view to get updated', async () => { + return (await PageObjects.discover.getCurrentlySelectedDataView()) === `${initialPattern}*`; + }); + await PageObjects.unifiedFieldList.waitUntilSidebarHasLoaded(); + + expect(await PageObjects.discover.getHitCountInt()).to.be(1); + expect(await PageObjects.unifiedFieldList.getSidebarSectionFieldNames('available')).to.eql([ + '@timestamp', + 'a', + ]); + + await es.transport.request({ + path: '/my-index-000002/_mapping', + method: 'PUT', + body: { + properties: { + b: { + type: 'keyword', + }, + }, + }, + }); + + // add new doc and check for it to make sure we're looking at fresh results + await es.transport.request({ + path: '/my-index-000002/_doc', + method: 'POST', + body: { + '@timestamp': new Date().toISOString(), + a: 'GET /search HTTP/1.1 200 1070000', + }, + }); + + await retry.waitFor('the new record was found', async () => { + await queryBar.submitQuery(); + await PageObjects.unifiedFieldList.waitUntilSidebarHasLoaded(); + return (await PageObjects.discover.getHitCountInt()) === 2; + }); + + expect(await PageObjects.unifiedFieldList.getSidebarSectionFieldNames('available')).to.eql([ + '@timestamp', + 'a', + ]); + }); }); } diff --git a/x-pack/test/functional/apps/lens/group2/fields_list.ts b/x-pack/test/functional/apps/lens/group2/fields_list.ts index 79baafe6100a6..93790aa97db39 100644 --- a/x-pack/test/functional/apps/lens/group2/fields_list.ts +++ b/x-pack/test/functional/apps/lens/group2/fields_list.ts @@ -53,7 +53,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { it('should show all fields as available', async () => { expect( await (await testSubjects.find('lnsIndexPatternAvailableFields-count')).getVisibleText() - ).to.eql(53); + ).to.eql(50); }); it('should show a histogram and top values popover for numeric field', async () => { diff --git a/x-pack/test_serverless/functional/test_suites/common/discover/group3/_sidebar.ts b/x-pack/test_serverless/functional/test_suites/common/discover/group3/_sidebar.ts index 270abef04517e..31262b8a24262 100644 --- a/x-pack/test_serverless/functional/test_suites/common/discover/group3/_sidebar.ts +++ b/x-pack/test_serverless/functional/test_suites/common/discover/group3/_sidebar.ts @@ -26,7 +26,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const fieldEditor = getService('fieldEditor'); const retry = getService('retry'); const dataGrid = getService('dataGrid'); - const INITIAL_FIELD_LIST_SUMMARY = '53 available fields. 3 meta fields.'; + const INITIAL_FIELD_LIST_SUMMARY = '48 available fields. 5 empty fields. 3 meta fields.'; describe('discover sidebar', function describeIndexTests() { before(async function () { @@ -71,7 +71,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await retry.waitFor('first updates', async () => { return ( (await PageObjects.unifiedFieldList.getSidebarAriaDescription()) === - '7 available fields. 2 meta fields.' + '6 available fields. 1 empty field. 2 meta fields.' ); }); @@ -80,7 +80,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await retry.waitFor('second updates', async () => { return ( (await PageObjects.unifiedFieldList.getSidebarAriaDescription()) === - '13 available fields. 3 meta fields.' + '10 available fields. 3 empty fields. 3 meta fields.' ); }); @@ -125,7 +125,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await retry.waitFor('first updates', async () => { return ( (await PageObjects.unifiedFieldList.getSidebarAriaDescription()) === - '30 available fields. 2 meta fields.' + '28 available fields. 2 empty fields. 2 meta fields.' ); }); @@ -262,11 +262,11 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { // Initial Available fields const expectedInitialAvailableFields = - '@message, @tags, @timestamp, agent, bytes, clientip, extension, geo.coordinates, geo.dest, geo.src, geo.srcdest, headings, host, id, index, ip, links, machine.os, machine.ram, machine.ram_range, memory, meta.char, meta.related, meta.user.firstname, meta.user.lastname, nestedField.child, phpmemory, referer, relatedContent.article:modified_time, relatedContent.article:published_time, relatedContent.article:section, relatedContent.article:tag, relatedContent.og:description, relatedContent.og:image, relatedContent.og:image:height, relatedContent.og:image:width, relatedContent.og:site_name, relatedContent.og:title, relatedContent.og:type, relatedContent.og:url, relatedContent.twitter:card, relatedContent.twitter:description, relatedContent.twitter:image, relatedContent.twitter:site, relatedContent.twitter:title, relatedContent.url, request, response, spaces, type'; + '@message, @tags, @timestamp, agent, bytes, clientip, extension, geo.coordinates, geo.dest, geo.src, geo.srcdest, headings, host, index, ip, links, machine.os, machine.ram, machine.ram_range, memory, nestedField.child, phpmemory, referer, relatedContent.article:modified_time, relatedContent.article:published_time, relatedContent.article:section, relatedContent.article:tag, relatedContent.og:description, relatedContent.og:image, relatedContent.og:image:height, relatedContent.og:image:width, relatedContent.og:site_name, relatedContent.og:title, relatedContent.og:type, relatedContent.og:url, relatedContent.twitter:card, relatedContent.twitter:description, relatedContent.twitter:image, relatedContent.twitter:site, relatedContent.twitter:title, relatedContent.url, request, response, spaces, type, url, utc_time, xss'; let availableFields = await PageObjects.unifiedFieldList.getSidebarSectionFieldNames( 'available' ); - expect(availableFields.length).to.be(50); + expect(availableFields.length).to.be(48); expect(availableFields.join(', ')).to.be(expectedInitialAvailableFields); // Available fields after scrolling down @@ -279,12 +279,10 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { availableFields = await PageObjects.unifiedFieldList.getSidebarSectionFieldNames( 'available' ); - return availableFields.length === 53; + return availableFields.length === 48; }); - expect(availableFields.join(', ')).to.be( - `${expectedInitialAvailableFields}, url, utc_time, xss` - ); + expect(availableFields.join(', ')).to.be(`${expectedInitialAvailableFields}`); // Expand Meta section await PageObjects.unifiedFieldList.toggleSidebarSection('meta'); @@ -308,7 +306,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const availableFields = await PageObjects.unifiedFieldList.getSidebarSectionFieldNames( 'available' ); - expect(availableFields.length).to.be(50); + expect(availableFields.length).to.be(48); expect( availableFields .join(', ') @@ -336,7 +334,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { ).to.be('relatedContent'); expect(await PageObjects.unifiedFieldList.getSidebarAriaDescription()).to.be( - '53 available fields. 1 unmapped field. 3 meta fields.' + '48 available fields. 1 unmapped field. 5 empty fields. 3 meta fields.' ); }); @@ -357,7 +355,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { expect(availableFields.includes('@message')).to.be(true); expect(await PageObjects.unifiedFieldList.getSidebarAriaDescription()).to.be( - '2 selected fields. 2 popular fields. 53 available fields. 3 meta fields.' + '2 selected fields. 2 popular fields. 48 available fields. 5 empty fields. 3 meta fields.' ); await PageObjects.unifiedFieldList.clickFieldListItemRemove('@message'); @@ -377,7 +375,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { ).to.be('@message, _id, extension'); expect(await PageObjects.unifiedFieldList.getSidebarAriaDescription()).to.be( - '3 selected fields. 3 popular fields. 53 available fields. 3 meta fields.' + '3 selected fields. 3 popular fields. 48 available fields. 5 empty fields. 3 meta fields.' ); }); @@ -564,7 +562,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await PageObjects.unifiedFieldList.waitUntilSidebarHasLoaded(); expect(await PageObjects.unifiedFieldList.getSidebarAriaDescription()).to.be( - '54 available fields. 3 meta fields.' + '49 available fields. 5 empty fields. 3 meta fields.' ); let allFields = await PageObjects.unifiedFieldList.getAllFieldNames(); @@ -583,7 +581,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await PageObjects.unifiedFieldList.waitUntilSidebarHasLoaded(); expect(await PageObjects.unifiedFieldList.getSidebarAriaDescription()).to.be( - '54 available fields. 3 meta fields.' + '49 available fields. 5 empty fields. 3 meta fields.' ); allFields = await PageObjects.unifiedFieldList.getAllFieldNames(); @@ -620,7 +618,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { // check that the sidebar is rendered expect(await PageObjects.unifiedFieldList.getSidebarAriaDescription()).to.be( - '54 available fields. 3 meta fields.' + '49 available fields. 5 empty fields. 3 meta fields.' ); let allFields = await PageObjects.unifiedFieldList.getAllFieldNames(); expect(allFields.includes('_invalid-runtimefield')).to.be(true); From 1ec43c94ec844364bd0b730e338e562d452915f2 Mon Sep 17 00:00:00 2001 From: Chris Cowan Date: Tue, 13 Feb 2024 08:30:00 -0700 Subject: [PATCH 72/83] [Obs][kbn-data-forge] Adding example config (#176727) ## Summary This PR copies the example configurations from the High Cardinality Indexer project into `kbn-data-forge` Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../change_point_detection.yaml | 26 ++++++++ .../anomalies_by_type/concept_drift.yaml | 35 ++++++++++ .../anomalies_by_type/contextual_anomaly.yaml | 38 +++++++++++ .../anomalies_by_type/point_anomaly.yaml | 40 +++++++++++ .../changing_log_volume_example.yaml | 23 +++++++ .../example_config/fake_logs_sine.yaml | 31 +++++++++ .../example_config/fake_stack.yaml | 39 +++++++++++ .../example_config/full_example.yaml | 41 ++++++++++++ .../example_config/future_example.yaml | 20 ++++++ .../example_config/good_to_bad_to_good.yaml | 20 ++++++ .../example_config/log_drop.yml | 39 +++++++++++ .../log_spike_scenarios/scenario0_logs.yaml | 29 ++++++++ .../scenario1_spike_logs.yaml | 35 ++++++++++ .../scenario2_spike_logs_host.yaml | 35 ++++++++++ .../scenario3_spike_errors.yaml | 33 ++++++++++ .../scenario4_spike_errors_with_recovery.yaml | 37 +++++++++++ .../scenario5_spike_logs_linear.yaml | 40 +++++++++++ .../example_config/metric_example.yaml | 31 +++++++++ .../example_config/ramp_up_then_down.yaml | 59 +++++++++++++++++ ...io0_paralell_metrics_drop_step_change.yaml | 26 ++++++++ ...paralell_metrics_increase_step_change.yaml | 35 ++++++++++ .../scenario2_divergent_metrics.yaml | 43 ++++++++++++ .../scenario3_chained_metrics_change.yaml | 59 +++++++++++++++++ .../custom_threshold_log_count.yaml | 27 ++++++++ .../custom_threshold_log_count_groupby.yaml | 27 ++++++++ .../custom_threshold_log_count_nodata.yaml | 23 +++++++ .../custom_threshold_metric_avg.yaml | 44 +++++++++++++ .../custom_threshold_metric_avg_groupby.yaml | 45 +++++++++++++ .../custom_threshold_metric_avg_nodata.yaml | 20 ++++++ .../rule_tests/slo_burn_rate.yaml | 22 +++++++ .../example_config/transition_example.yaml | 45 +++++++++++++ .../transitioning_templates_example.yaml | 66 +++++++++++++++++++ 32 files changed, 1133 insertions(+) create mode 100644 x-pack/packages/kbn-data-forge/example_config/anomalies_by_type/change_point_detection.yaml create mode 100644 x-pack/packages/kbn-data-forge/example_config/anomalies_by_type/concept_drift.yaml create mode 100644 x-pack/packages/kbn-data-forge/example_config/anomalies_by_type/contextual_anomaly.yaml create mode 100644 x-pack/packages/kbn-data-forge/example_config/anomalies_by_type/point_anomaly.yaml create mode 100644 x-pack/packages/kbn-data-forge/example_config/changing_log_volume_example.yaml create mode 100644 x-pack/packages/kbn-data-forge/example_config/fake_logs_sine.yaml create mode 100644 x-pack/packages/kbn-data-forge/example_config/fake_stack.yaml create mode 100644 x-pack/packages/kbn-data-forge/example_config/full_example.yaml create mode 100644 x-pack/packages/kbn-data-forge/example_config/future_example.yaml create mode 100644 x-pack/packages/kbn-data-forge/example_config/good_to_bad_to_good.yaml create mode 100644 x-pack/packages/kbn-data-forge/example_config/log_drop.yml create mode 100644 x-pack/packages/kbn-data-forge/example_config/log_spike_scenarios/scenario0_logs.yaml create mode 100644 x-pack/packages/kbn-data-forge/example_config/log_spike_scenarios/scenario1_spike_logs.yaml create mode 100644 x-pack/packages/kbn-data-forge/example_config/log_spike_scenarios/scenario2_spike_logs_host.yaml create mode 100644 x-pack/packages/kbn-data-forge/example_config/log_spike_scenarios/scenario3_spike_errors.yaml create mode 100644 x-pack/packages/kbn-data-forge/example_config/log_spike_scenarios/scenario4_spike_errors_with_recovery.yaml create mode 100644 x-pack/packages/kbn-data-forge/example_config/log_spike_scenarios/scenario5_spike_logs_linear.yaml create mode 100644 x-pack/packages/kbn-data-forge/example_config/metric_example.yaml create mode 100644 x-pack/packages/kbn-data-forge/example_config/ramp_up_then_down.yaml create mode 100644 x-pack/packages/kbn-data-forge/example_config/related_events_metrics/scenario0_paralell_metrics_drop_step_change.yaml create mode 100644 x-pack/packages/kbn-data-forge/example_config/related_events_metrics/scenario1_paralell_metrics_increase_step_change.yaml create mode 100644 x-pack/packages/kbn-data-forge/example_config/related_events_metrics/scenario2_divergent_metrics.yaml create mode 100644 x-pack/packages/kbn-data-forge/example_config/related_events_metrics/scenario3_chained_metrics_change.yaml create mode 100644 x-pack/packages/kbn-data-forge/example_config/rule_tests/custom_threshold_log_count.yaml create mode 100644 x-pack/packages/kbn-data-forge/example_config/rule_tests/custom_threshold_log_count_groupby.yaml create mode 100644 x-pack/packages/kbn-data-forge/example_config/rule_tests/custom_threshold_log_count_nodata.yaml create mode 100644 x-pack/packages/kbn-data-forge/example_config/rule_tests/custom_threshold_metric_avg.yaml create mode 100644 x-pack/packages/kbn-data-forge/example_config/rule_tests/custom_threshold_metric_avg_groupby.yaml create mode 100644 x-pack/packages/kbn-data-forge/example_config/rule_tests/custom_threshold_metric_avg_nodata.yaml create mode 100644 x-pack/packages/kbn-data-forge/example_config/rule_tests/slo_burn_rate.yaml create mode 100644 x-pack/packages/kbn-data-forge/example_config/transition_example.yaml create mode 100644 x-pack/packages/kbn-data-forge/example_config/transitioning_templates_example.yaml diff --git a/x-pack/packages/kbn-data-forge/example_config/anomalies_by_type/change_point_detection.yaml b/x-pack/packages/kbn-data-forge/example_config/anomalies_by_type/change_point_detection.yaml new file mode 100644 index 0000000000000..034bad86a0bec --- /dev/null +++ b/x-pack/packages/kbn-data-forge/example_config/anomalies_by_type/change_point_detection.yaml @@ -0,0 +1,26 @@ +--- +elasticsearch: + installKibanaUser: false + +kibana: + installAssets: false + +indexing: + eventsPerCycle: 5000 + dataset: "fake_logs" + +schedule: + # Start with normal logs + - template: "good" + start: "now-45m" + end: "now+1m" + randomness: 0.1 + eventsPerCycle: 5000 + # Sudden change into new number of logs + - template: "good" + start: "now+1m" + end: "now+10m" + randomness: 0.1 + eventsPerCycle: 1000 + + diff --git a/x-pack/packages/kbn-data-forge/example_config/anomalies_by_type/concept_drift.yaml b/x-pack/packages/kbn-data-forge/example_config/anomalies_by_type/concept_drift.yaml new file mode 100644 index 0000000000000..3f4184bad949d --- /dev/null +++ b/x-pack/packages/kbn-data-forge/example_config/anomalies_by_type/concept_drift.yaml @@ -0,0 +1,35 @@ +--- +elasticsearch: + installKibanaUser: false + +kibana: + installAssets: false + +indexing: + eventsPerCycle: 1000 + dataset: "fake_logs" + +schedule: + # Start with normal logs + - template: "good" + start: "now-10m" + end: "now+1m" + randomness: 0.1 + eventsPerCycle: 1000 + # Progresively change into a new number of logs + - template: "good" + start: "now+1m" + end: "now+5m" + randomness: 0.1 + eventsPerCycle: + start: 1000 + end: 5000 + method: "linear" + # Stay at the new number of logs + - template: "good" + start: "now+5m" + end: "now+10m" + randomness: 0.1 + eventsPerCycle: 5000 + + diff --git a/x-pack/packages/kbn-data-forge/example_config/anomalies_by_type/contextual_anomaly.yaml b/x-pack/packages/kbn-data-forge/example_config/anomalies_by_type/contextual_anomaly.yaml new file mode 100644 index 0000000000000..bee9bc319631e --- /dev/null +++ b/x-pack/packages/kbn-data-forge/example_config/anomalies_by_type/contextual_anomaly.yaml @@ -0,0 +1,38 @@ +--- +# Define the connection to Elasticsearch +elasticsearch: + installKibanaUser: false + +# Define the connection to Kibana +kibana: + host: "http://localhost:5601" + username: "elastic" + password: "changeme" + installAssets: false + +# Define the indexing options +indexing: + dataset: "fake_logs" + eventsPerCycle: 2000 + interval: 6000 + +# Define the schedule +schedule: + - template: "bad" + start: "now-5m" + end: "now+1m" + eventsPerCycle: + start: 5000 + end: 3000 + method: "sine" + - template: "good" + start: "now+1m" + end: "now+2m" + eventsPerCycle: 3000 + - template: "bad" + start: "now+2m" + end: "now+10m" + eventsPerCycle: + start: 5000 + end: 3000 + method: "sine" diff --git a/x-pack/packages/kbn-data-forge/example_config/anomalies_by_type/point_anomaly.yaml b/x-pack/packages/kbn-data-forge/example_config/anomalies_by_type/point_anomaly.yaml new file mode 100644 index 0000000000000..b3f91a5f18e79 --- /dev/null +++ b/x-pack/packages/kbn-data-forge/example_config/anomalies_by_type/point_anomaly.yaml @@ -0,0 +1,40 @@ +--- +# Define the connection to Elasticsearch +elasticsearch: + installKibanaUser: false + +# Define the connection to Kibana +kibana: + installAssets: false + +# Define the indexing options +indexing: + dataset: "fake_logs" + eventsPerCycle: 5000 + +# Define the schedule +schedule: + # Normal logs + - template: "good" + eventsPerCycle: 5000 + start: "now-5m" + end: "now+5m" + randomness: 0.1 + # Spike in logs + - template: "bad" + eventsPerCycle: 10000 + start: "now+1m" + end: "now+2m" + randomness: 0.1 + # Drop in logs, stop normal logs and send few logs + - template: "bad" + eventsPerCycle: 500 + start: "now+5m" + end: "now+7m" + randomness: 0.1 + # Return to normal logs + - template: "good" + eventsPerCycle: 5000 + start: "now+7m" + end: "now+17m" + randomness: 0.1 diff --git a/x-pack/packages/kbn-data-forge/example_config/changing_log_volume_example.yaml b/x-pack/packages/kbn-data-forge/example_config/changing_log_volume_example.yaml new file mode 100644 index 0000000000000..5e71a2f8a11d8 --- /dev/null +++ b/x-pack/packages/kbn-data-forge/example_config/changing_log_volume_example.yaml @@ -0,0 +1,23 @@ +--- +elasticsearch: + installKibanaUser: false + +kibana: + installAssets: true + +indexing: + eventsPerCycle: 1000 + dataset: "fake_logs" + +schedule: + - template: "good" + start: "now-2h" + end: "now-1h" + - template: "bad" + start: "now-1h" + end: "now-15m" + eventsPerCycle: 5000 + - template: "good" + start: "now-15m" + end: false + diff --git a/x-pack/packages/kbn-data-forge/example_config/fake_logs_sine.yaml b/x-pack/packages/kbn-data-forge/example_config/fake_logs_sine.yaml new file mode 100644 index 0000000000000..49c4ed4f7f2ef --- /dev/null +++ b/x-pack/packages/kbn-data-forge/example_config/fake_logs_sine.yaml @@ -0,0 +1,31 @@ +--- +# Define the connection to Elasticsearch +elasticsearch: + installKibanaUser: false + +# Define the connection to Kibana +kibana: + host: "http://localhost:5601" + username: "elastic" + password: "changeme" + installAssets: false + +# Define the indexing options +indexing: + dataset: "fake_logs" + eventsPerCycle: 2000 + interval: 6000 + scenario: "sine_logs" + +# Define the schedule +schedule: + - template: "bad" + start: "now-5m" + end: "now+10m" + eventsPerCycle: + start: 2000 + end: 3000 + method: "sine" + options: + period: 60 + randomness: 0.1 diff --git a/x-pack/packages/kbn-data-forge/example_config/fake_stack.yaml b/x-pack/packages/kbn-data-forge/example_config/fake_stack.yaml new file mode 100644 index 0000000000000..8397c37f039fc --- /dev/null +++ b/x-pack/packages/kbn-data-forge/example_config/fake_stack.yaml @@ -0,0 +1,39 @@ +--- +elasticsearch: + installKibanaUser: false + +kibana: + installAssets: true + +indexing: + dataset: "fake_stack" + eventsPerCycle: 200 + reduceWeekendTrafficBy: 0.5 + +schedule: + # Start with good events + - template: "good" + start: "now-14d" + end: "now-6d-5h-3m" + eventsPerCycle: 200 + randomness: 0.2 + - template: "connectionTimeout" + start: "now-6d-5h-3m" + end: "now-6d-4h-44m" + eventsPerCycle: 200 + randomness: 0.2 + - template: "good" + start: "now-6d-4h-44m" + end: "now-1d" + eventsPerCycle: 200 + randomness: 0.2 + - template: "bad" + start: "now-1d" + end: "now-1d+45m" + eventsPerCycle: 200 + randomness: 0.2 + - template: "good" + start: "now-1d+45m" + end: false + eventsPerCycle: 200 + randomness: 0.2 diff --git a/x-pack/packages/kbn-data-forge/example_config/full_example.yaml b/x-pack/packages/kbn-data-forge/example_config/full_example.yaml new file mode 100644 index 0000000000000..a7af3d5c7b8b6 --- /dev/null +++ b/x-pack/packages/kbn-data-forge/example_config/full_example.yaml @@ -0,0 +1,41 @@ +--- +# Define the connection to Elasticsearch +elasticsearch: + host: "http://localhost:9200" + username: "elastic" + password: "changeme" + installKibanaUser: false + +# Define the connection to Kibana +kibana: + host: "http://localhost:5601" + username: "elastic" + password: "changeme" + installAssets: true + +# Define the indexing options +indexing: + dataset: "fake_stack" + interval: 6000 + eventsPerCycle: 1 + payloadSize: 10000 + concurrency: 5 + +# Define the schedule +schedule: + - template: "good" + start: "now-1h" + end: "now-15m" + - template: "bad" + start: "now-15m" + end: "now" + # This schedule will run indefinitely + - template: "good" + start: "now" + end: false + # This will add a 2 minute delay to the indexing every 5 mintes. + # Once the 2 minutes is up, the queue will back fill the events it collected + # during the delay. This only makes sense if `end` is `false` + delayInMinutes: 2 + delayEveryMinutes: 5 + diff --git a/x-pack/packages/kbn-data-forge/example_config/future_example.yaml b/x-pack/packages/kbn-data-forge/example_config/future_example.yaml new file mode 100644 index 0000000000000..c879ee34c3959 --- /dev/null +++ b/x-pack/packages/kbn-data-forge/example_config/future_example.yaml @@ -0,0 +1,20 @@ +--- +elasticsearch: + installKibanaUser: false + +kibana: + installAssets: true + +indexing: + dataset: "fake_stack" + +schedule: + - template: "good" + start: "now" + end: "now+1h" + - template: "bad" + start: "now+1h" + end: "now+90m" + - template: "good" + start: "now+90m" + end: "now+2h" diff --git a/x-pack/packages/kbn-data-forge/example_config/good_to_bad_to_good.yaml b/x-pack/packages/kbn-data-forge/example_config/good_to_bad_to_good.yaml new file mode 100644 index 0000000000000..f460227af6bce --- /dev/null +++ b/x-pack/packages/kbn-data-forge/example_config/good_to_bad_to_good.yaml @@ -0,0 +1,20 @@ +--- +elasticsearch: + installKibanaUser: false + +kibana: + installAssets: true + +indexing: + dataset: "fake_stack" + +schedule: + - template: "good" + start: "now-2h" + end: "now-1h" + - template: "bad" + start: "now-1h" + end: "now-15m" + - template: "good" + start: "now-15m" + end: false diff --git a/x-pack/packages/kbn-data-forge/example_config/log_drop.yml b/x-pack/packages/kbn-data-forge/example_config/log_drop.yml new file mode 100644 index 0000000000000..a46956e2b7ddf --- /dev/null +++ b/x-pack/packages/kbn-data-forge/example_config/log_drop.yml @@ -0,0 +1,39 @@ +--- +# Define the connection to Elasticsearch +elasticsearch: + installKibanaUser: false + +# Define the connection to Kibana +kibana: + host: "http://localhost:5601" + username: "elastic" + password: "changeme" + installAssets: false + +indexing: + eventsPerCycle: 100 + dataset: "fake_logs" + scenario: "log_drop" + +schedule: + # Start with good events at 100 rate + - template: "good" + start: "now-30m" + end: "now-5m" + eventsPerCycle: 50 + randomness: 0.1 + # Create a ramp-up of bad events + - template: "good" + start: "now-5m" + end: "now+5m" + eventsPerCycle: + start: 50 + end: 100 + method: "linear" + randomness: 0.1 + # Drop to very little good events + - template: "good" + start: "now+5m" + end: false + eventsPerCycle: 10 + randomness: 0.1 \ No newline at end of file diff --git a/x-pack/packages/kbn-data-forge/example_config/log_spike_scenarios/scenario0_logs.yaml b/x-pack/packages/kbn-data-forge/example_config/log_spike_scenarios/scenario0_logs.yaml new file mode 100644 index 0000000000000..6a4ee8b184557 --- /dev/null +++ b/x-pack/packages/kbn-data-forge/example_config/log_spike_scenarios/scenario0_logs.yaml @@ -0,0 +1,29 @@ +--- +# Define the connection to Elasticsearch +elasticsearch: + host: "http://localhost:9200" + username: "elastic" + password: "changeme" + installKibanaUser: false + +# Define the connection to Kibana +kibana: + host: "http://localhost:5601" + username: "elastic" + password: "changeme" + installAssets: false + +# Define the indexing options +indexing: + dataset: "fake_logs" + eventsPerCycle: 2000 + interval: 6000 + +# Define the schedule +schedule: + # This step send 2000 logs every 30 seconds + - template: "good" + eventsPerCycle: 2000 + start: "now-5m" + end: "now+5m" + diff --git a/x-pack/packages/kbn-data-forge/example_config/log_spike_scenarios/scenario1_spike_logs.yaml b/x-pack/packages/kbn-data-forge/example_config/log_spike_scenarios/scenario1_spike_logs.yaml new file mode 100644 index 0000000000000..e12462aebe363 --- /dev/null +++ b/x-pack/packages/kbn-data-forge/example_config/log_spike_scenarios/scenario1_spike_logs.yaml @@ -0,0 +1,35 @@ +--- +# Define the connection to Elasticsearch +elasticsearch: + host: "http://localhost:9200" + username: "elastic" + password: "changeme" + installKibanaUser: false + +# Define the connection to Kibana +kibana: + host: "http://localhost:5601" + username: "elastic" + password: "changeme" + installAssets: false + +# Define the indexing options +indexing: + dataset: "fake_logs" + eventsPerCycle: 2000 + interval: 6000 + +# Define the schedule +schedule: + # This step send 2000 logs every 30 seconds + - template: "good" + eventsPerCycle: 2000 + start: "now-2m" + end: "now+1m" + # This step send 6000 logs every 30 seconds + - template: "bad" + start: "now+1m" + end: "now+10m" + eventsPerCycle: 6000 + randomness: 0.1 + diff --git a/x-pack/packages/kbn-data-forge/example_config/log_spike_scenarios/scenario2_spike_logs_host.yaml b/x-pack/packages/kbn-data-forge/example_config/log_spike_scenarios/scenario2_spike_logs_host.yaml new file mode 100644 index 0000000000000..bd52900d6fb5d --- /dev/null +++ b/x-pack/packages/kbn-data-forge/example_config/log_spike_scenarios/scenario2_spike_logs_host.yaml @@ -0,0 +1,35 @@ +--- +# Define the connection to Elasticsearch +elasticsearch: + host: "http://localhost:9200" + username: "elastic" + password: "changeme" + installKibanaUser: false + +# Define the connection to Kibana +kibana: + host: "http://localhost:5601" + username: "elastic" + password: "changeme" + installAssets: false + +# Define the indexing options +indexing: + dataset: "fake_logs" + eventsPerCycle: 2000 + interval: 6000 + concurrency: 4 + +# Define the schedule +schedule: + # This step send 2000 logs every 30 seconds for 4 hosts + - template: "good" + eventsPerCycle: 2000 + start: "now-2m" + end: "now+1m" + # This step send 8000 logs every 30 seconds for 4 hosts + - template: "bad" + start: "now+1m" + end: "now+5m" + eventsPerCycle: 8000 + randomness: 0.1 diff --git a/x-pack/packages/kbn-data-forge/example_config/log_spike_scenarios/scenario3_spike_errors.yaml b/x-pack/packages/kbn-data-forge/example_config/log_spike_scenarios/scenario3_spike_errors.yaml new file mode 100644 index 0000000000000..6cfa6b7b76b39 --- /dev/null +++ b/x-pack/packages/kbn-data-forge/example_config/log_spike_scenarios/scenario3_spike_errors.yaml @@ -0,0 +1,33 @@ +--- +# Define the connection to Elasticsearch +elasticsearch: + host: "http://localhost:9200" + username: "elastic" + password: "changeme" + installKibanaUser: false + +# Define the connection to Kibana +kibana: + host: "http://localhost:5601" + username: "elastic" + password: "changeme" + installAssets: false + +# Define the indexing options +indexing: + dataset: "fake_stack" + interval: 6000 + eventsPerCycle: 1 + payloadSize: 10000 + concurrency: 5 + +# Define the schedule +schedule: + # This step will send "good" events + - template: "good" + start: "now-2m" + end: "now+1m" + # This step will send "bad" events, with errors + - template: "bad" + start: "now+1m" + end: "now+10m" diff --git a/x-pack/packages/kbn-data-forge/example_config/log_spike_scenarios/scenario4_spike_errors_with_recovery.yaml b/x-pack/packages/kbn-data-forge/example_config/log_spike_scenarios/scenario4_spike_errors_with_recovery.yaml new file mode 100644 index 0000000000000..5c7906889ed29 --- /dev/null +++ b/x-pack/packages/kbn-data-forge/example_config/log_spike_scenarios/scenario4_spike_errors_with_recovery.yaml @@ -0,0 +1,37 @@ +--- +# Define the connection to Elasticsearch +elasticsearch: + host: "http://localhost:9200" + username: "elastic" + password: "changeme" + installKibanaUser: false + +# Define the connection to Kibana +kibana: + host: "http://localhost:5601" + username: "elastic" + password: "changeme" + installAssets: false + +# Define the indexing options +indexing: + dataset: "fake_stack" + interval: 6000 + eventsPerCycle: 1 + payloadSize: 10000 + concurrency: 5 + +# Define the schedule +schedule: + # This step will send "good" events + - template: "good" + start: "now-2m" + end: "now+1m" + # This step will send "bad" events, with errors + - template: "bad" + start: "now+1m" + end: "now+5m" + # Recover to good events + - template: "good" + start: "now+5m" + end: "now+10m" diff --git a/x-pack/packages/kbn-data-forge/example_config/log_spike_scenarios/scenario5_spike_logs_linear.yaml b/x-pack/packages/kbn-data-forge/example_config/log_spike_scenarios/scenario5_spike_logs_linear.yaml new file mode 100644 index 0000000000000..2dd5c4283893e --- /dev/null +++ b/x-pack/packages/kbn-data-forge/example_config/log_spike_scenarios/scenario5_spike_logs_linear.yaml @@ -0,0 +1,40 @@ +--- +# Define the connection to Elasticsearch +elasticsearch: + installKibanaUser: false + +# Define the connection to Kibana +kibana: + host: "http://localhost:5601" + username: "elastic" + password: "changeme" + installAssets: false + +# Define the indexing options +indexing: + dataset: "fake_logs" + eventsPerCycle: 2000 + interval: 6000 + +# Define the schedule +schedule: + # This step send 2000 logs every 30 seconds + - template: "good" + eventsPerCycle: 2000 + start: "now-2m" + end: "now+1m" + - template: "good" + start: "now+1m" + end: "now+5m" + randomness: 0.1 + eventsPerCycle: + start: 2000 + end: 6000 + method: "linear" + # This step send 6000 logs every 30 seconds + - template: "bad" + start: "now+5m" + end: "now+10m" + eventsPerCycle: 6000 + randomness: 0.1 + diff --git a/x-pack/packages/kbn-data-forge/example_config/metric_example.yaml b/x-pack/packages/kbn-data-forge/example_config/metric_example.yaml new file mode 100644 index 0000000000000..d4b10d52fcb55 --- /dev/null +++ b/x-pack/packages/kbn-data-forge/example_config/metric_example.yaml @@ -0,0 +1,31 @@ +--- +elasticsearch: + installKibanaUser: false + +kibana: + installAssets: true + +indexing: + dataset: "fake_hosts" + eventsPerCycle: 1 + interval: 10000 + +schedule: + # Start with good events + - template: "good" + start: "now-2h" + end: false + eventsPerCycle: 1 + metrics: + - name: "system.cpu.user.pct" + method: "sine" + start: 1 + end: 4 + period: 2500 + randomness: 0.1 + - name: "system.cpu.system.pct" + method: "exp" + start: 1 + end: 4 + randomness: 0.1 + diff --git a/x-pack/packages/kbn-data-forge/example_config/ramp_up_then_down.yaml b/x-pack/packages/kbn-data-forge/example_config/ramp_up_then_down.yaml new file mode 100644 index 0000000000000..a62b5064350f9 --- /dev/null +++ b/x-pack/packages/kbn-data-forge/example_config/ramp_up_then_down.yaml @@ -0,0 +1,59 @@ +--- +elasticsearch: + installKibanaUser: false + +kibana: + installAssets: false + +indexing: + dataset: "fake_stack" + eventsPerCycle: 100 + +schedule: + # Ramp up + - template: "good" + start: "now" + end: "now+60m" + eventsPerCycle: + start: 100 + end: 1 + method: 'linear' + randomness: 0.1 + - template: "bad" + start: "now" + end: "now+60m" + eventsPerCycle: + start: 1 + end: 100 + method: 'linear' + randomness: 0.1 + # Level off + - template: "bad" + start: "now+60m" + end: "now+90m" + eventsPerCycle: 100 + randomness: 0.1 + # Ramp down + - template: "good" + start: "now+90m" + end: "now+150m" + eventsPerCycle: + start: 1 + end: 100 + method: 'linear' + randomness: 0.1 + - template: "bad" + start: "now+90m" + end: "now+150m" + eventsPerCycle: + start: 100 + end: 1 + method: 'linear' + randomness: 0.1 + # Back to normal + - template: "good" + start: "now+150m" + end: "now+210m" + eventsPerCycle: 100 + randomness: 0.1 + diff --git a/x-pack/packages/kbn-data-forge/example_config/related_events_metrics/scenario0_paralell_metrics_drop_step_change.yaml b/x-pack/packages/kbn-data-forge/example_config/related_events_metrics/scenario0_paralell_metrics_drop_step_change.yaml new file mode 100644 index 0000000000000..7a153ad80fc28 --- /dev/null +++ b/x-pack/packages/kbn-data-forge/example_config/related_events_metrics/scenario0_paralell_metrics_drop_step_change.yaml @@ -0,0 +1,26 @@ +--- +elasticsearch: + installKibanaUser: false + +kibana: + installAssets: true + +indexing: + dataset: "fake_hosts" + scenario: "scenario0" + eventsPerCycle: 1 + interval: 10000 + +schedule: + # Start events at 100 rate + - template: "good" + start: "now-30m" + end: "now+2m" + eventsPerCycle: 100 + randomness: 0.1 + # Step change to events at 10 rate + - template: "good" + start: "now+2m" + end: "now+10m" + eventsPerCycle: 10 + diff --git a/x-pack/packages/kbn-data-forge/example_config/related_events_metrics/scenario1_paralell_metrics_increase_step_change.yaml b/x-pack/packages/kbn-data-forge/example_config/related_events_metrics/scenario1_paralell_metrics_increase_step_change.yaml new file mode 100644 index 0000000000000..fb80dc2bc3362 --- /dev/null +++ b/x-pack/packages/kbn-data-forge/example_config/related_events_metrics/scenario1_paralell_metrics_increase_step_change.yaml @@ -0,0 +1,35 @@ +--- +elasticsearch: + installKibanaUser: false + +kibana: + installAssets: true + +indexing: + dataset: "fake_hosts" + scenario: "scenario1" + eventsPerCycle: 1 + interval: 10000 + +schedule: + # Start events at 100 rate + - template: "good" + start: "now-30m" + end: "now+2m" + eventsPerCycle: 50 + randomness: 0.1 + # Step change to events at 20 rate + - template: "good" + start: "now+2m" + end: "now+3m" + eventsPerCycle: + start: 50 + end: 200 + method: "linear" + randomness: 0.1 + - template: "good" + start: "now+3m" + end: "now+15m" + eventsPerCycle: 200 + randomness: 0.1 + diff --git a/x-pack/packages/kbn-data-forge/example_config/related_events_metrics/scenario2_divergent_metrics.yaml b/x-pack/packages/kbn-data-forge/example_config/related_events_metrics/scenario2_divergent_metrics.yaml new file mode 100644 index 0000000000000..1cdff47bb2348 --- /dev/null +++ b/x-pack/packages/kbn-data-forge/example_config/related_events_metrics/scenario2_divergent_metrics.yaml @@ -0,0 +1,43 @@ +--- +elasticsearch: + installKibanaUser: false + +kibana: + installAssets: true + +indexing: + dataset: "fake_hosts" + scenario: "scenario2" + eventsPerCycle: 50 + interval: 10000 + +schedule: + # Start with good events + - template: "good" + start: "now-30m" + end: "now+2m" + randomness: 0.1 + - template: "good" + start: "now+2m" + end: "now+5m" + metrics: + - name: "system.cpu.user.pct" + method: "exp" + start: 10 + end: 2.5 + randomness: 0.1 + - name: "system.memory.actual.used.pct" + method: "exp" + start: 2.5 + end: 0.5 + randomness: 0.1 + - name: "system.filesystem.used.pct" + method: "linear" + start: 2.5 + end: 0.5 + randomness: 0.1 + - template: "good" + start: "now+5m" + end: "now+15m" + randomness: 0.1 + diff --git a/x-pack/packages/kbn-data-forge/example_config/related_events_metrics/scenario3_chained_metrics_change.yaml b/x-pack/packages/kbn-data-forge/example_config/related_events_metrics/scenario3_chained_metrics_change.yaml new file mode 100644 index 0000000000000..687e718919cfb --- /dev/null +++ b/x-pack/packages/kbn-data-forge/example_config/related_events_metrics/scenario3_chained_metrics_change.yaml @@ -0,0 +1,59 @@ +--- +elasticsearch: + installKibanaUser: false + +kibana: + installAssets: true + +indexing: + dataset: "fake_hosts" + scenario: "scenario3" + eventsPerCycle: 50 + interval: 10000 + +schedule: + # Start with good events + - template: "good" + start: "now-30m" + end: "now+2m" + randomness: 0.1 + # Create a spike on a metric + - template: "good" + start: "now+1m" + end: "now+2m" + randomness: 0.1 + metrics: + - name: "system.load.1" + method: "linear" + start: 2.5 + end: 30 + randomness: 0.1 + - template: "good" + start: "now+2m" + end: "now+3m" + randomness: 0.1 + metrics: + - name: "system.cpu.user.pct" + method: "exp" + start: 2.5 + end: 10 + randomness: 0.1 + - template: "good" + start: "now+3m" + end: "now+5m" + metrics: + - name: "system.memory.actual.used.pct" + method: "exp" + start: 2.5 + end: 0.5 + randomness: 0.1 + - name: "system.filesystem.used.pct" + method: "linear" + start: 2.5 + end: 0.5 + randomness: 0.1 + - template: "good" + start: "now+5m" + end: "now+15m" + randomness: 0.1 + diff --git a/x-pack/packages/kbn-data-forge/example_config/rule_tests/custom_threshold_log_count.yaml b/x-pack/packages/kbn-data-forge/example_config/rule_tests/custom_threshold_log_count.yaml new file mode 100644 index 0000000000000..65f84997f6815 --- /dev/null +++ b/x-pack/packages/kbn-data-forge/example_config/rule_tests/custom_threshold_log_count.yaml @@ -0,0 +1,27 @@ +--- +elasticsearch: + installKibanaUser: false + +kibana: + installAssets: false + +indexing: + dataset: "fake_hosts" + scenario: "custom_threshold_log_count" + eventsPerCycle: 50 + concurrency: 5 + +# Conditions: count logs < 100 in the last minute +schedule: + # Start events at 100 rate + - template: "good" + start: "now-30m" + end: "now-1m" + eventsPerCycle: 100 + randomness: 0.1 + # Step change to events at 10 rate + - template: "good" + start: "now-1m" + end: "now+20m" + eventsPerCycle: 10 + diff --git a/x-pack/packages/kbn-data-forge/example_config/rule_tests/custom_threshold_log_count_groupby.yaml b/x-pack/packages/kbn-data-forge/example_config/rule_tests/custom_threshold_log_count_groupby.yaml new file mode 100644 index 0000000000000..101ddde3e93d5 --- /dev/null +++ b/x-pack/packages/kbn-data-forge/example_config/rule_tests/custom_threshold_log_count_groupby.yaml @@ -0,0 +1,27 @@ +--- +elasticsearch: + installKibanaUser: false + +kibana: + installAssets: true + +indexing: + dataset: "fake_hosts" + scenario: "custom_threshold_log_count_groupby" + eventsPerCycle: 50 + concurrency: 5 + +# Conditions: count logs < 40 in the last minute, grouped by event.dataset +schedule: + # Start events at 100 rate + - template: "good" + start: "now-30m" + end: "now-1m" + eventsPerCycle: 100 + randomness: 0.1 + # Step change to events at 10 rate + - template: "good" + start: "now-1m" + end: "now+20m" + eventsPerCycle: 10 + diff --git a/x-pack/packages/kbn-data-forge/example_config/rule_tests/custom_threshold_log_count_nodata.yaml b/x-pack/packages/kbn-data-forge/example_config/rule_tests/custom_threshold_log_count_nodata.yaml new file mode 100644 index 0000000000000..7023f24bb3def --- /dev/null +++ b/x-pack/packages/kbn-data-forge/example_config/rule_tests/custom_threshold_log_count_nodata.yaml @@ -0,0 +1,23 @@ +--- +elasticsearch: + installKibanaUser: false + +kibana: + installAssets: false + +indexing: + dataset: "fake_hosts" + scenario: "custom_threshold_log_count_nodata" + eventsPerCycle: 50 + concurrency: 5 + +# Conditions: count logs < 5 in the last minute (will trigger no data after 1 min) +schedule: + # Start events at 10 rate + - template: "good" + start: "now-30m" + end: "now-1m" + eventsPerCycle: 10 + randomness: 0.1 + # Stop data + diff --git a/x-pack/packages/kbn-data-forge/example_config/rule_tests/custom_threshold_metric_avg.yaml b/x-pack/packages/kbn-data-forge/example_config/rule_tests/custom_threshold_metric_avg.yaml new file mode 100644 index 0000000000000..d8cb165e78c70 --- /dev/null +++ b/x-pack/packages/kbn-data-forge/example_config/rule_tests/custom_threshold_metric_avg.yaml @@ -0,0 +1,44 @@ +--- +elasticsearch: + installKibanaUser: false + +kibana: + installAssets: false + +indexing: + dataset: "fake_hosts" + scenario: "custom_threshold_metric_avg" + eventsPerCycle: 50 + +# Conditions: avg. system.cpu.user.pct > 80 in the last minute +schedule: + # Start with avg. system.cpu.user.pct = 2.5 + - template: "good" + start: "now-30m" + end: "now-3m" + randomness: 0.1 + # Transition to avg. system.cpu.user.pct = 90 + - template: "good" + start: "now-3m" + end: "now-2m" + metrics: + - name: "system.cpu.user.pct" + method: "linear" + start: 2.5 + end: 90 + randomness: 0.1 + - template: "good" + start: "now-2m" + end: "now+13m" + metrics: + - name: "system.cpu.user.pct" + method: "linear" + start: 90 + end: 90 + randomness: 0.05 + # Go back to avg. system.cpu.user.pct = 2.5 + - template: "good" + start: "now+13m" + end: "now+25m" + randomness: 0.1 + diff --git a/x-pack/packages/kbn-data-forge/example_config/rule_tests/custom_threshold_metric_avg_groupby.yaml b/x-pack/packages/kbn-data-forge/example_config/rule_tests/custom_threshold_metric_avg_groupby.yaml new file mode 100644 index 0000000000000..280a85dd1437c --- /dev/null +++ b/x-pack/packages/kbn-data-forge/example_config/rule_tests/custom_threshold_metric_avg_groupby.yaml @@ -0,0 +1,45 @@ +--- +elasticsearch: + installKibanaUser: false + +kibana: + installAssets: false + +indexing: + dataset: "fake_hosts" + scenario: "custom_threshold_metric_avg_groupby" + eventsPerCycle: 5 + interval: 5000 + +# Conditions: avg. system.cpu.user.pct > 80 in the last 5 minutes, grouped by host.name +schedule: + # Start with avg. system.cpu.user.pct = 2.5 + - template: "good" + start: "now-30m" + end: "now-6m" + randomness: 0.1 + # Transition to avg. system.cpu.user.pct = 90 + - template: "good" + start: "now-6m" + end: "now-5m" + metrics: + - name: "system.cpu.user.pct" + method: "linear" + start: 2.5 + end: 90 + randomness: 0.1 + - template: "good" + start: "now-5m" + end: "now+23m" + metrics: + - name: "system.cpu.user.pct" + method: "linear" + start: 90 + end: 90 + randomness: 0.05 + # Go back to avg. system.cpu.user.pct = 2.5 + - template: "good" + start: "now+23m" + end: "now+45m" + randomness: 0.1 + diff --git a/x-pack/packages/kbn-data-forge/example_config/rule_tests/custom_threshold_metric_avg_nodata.yaml b/x-pack/packages/kbn-data-forge/example_config/rule_tests/custom_threshold_metric_avg_nodata.yaml new file mode 100644 index 0000000000000..ed964fe97f347 --- /dev/null +++ b/x-pack/packages/kbn-data-forge/example_config/rule_tests/custom_threshold_metric_avg_nodata.yaml @@ -0,0 +1,20 @@ +--- +elasticsearch: + installKibanaUser: false + +kibana: + installAssets: false + +indexing: + dataset: "fake_hosts" + scenario: "custom_threshold_metric_avg_nodata" + eventsPerCycle: 50 + +# Conditions: avg. system.cpu.user.pct < 1 in the last minute (will trigger no data after 2 min) +schedule: + # Start with avg. system.cpu.user.pct = 2.5 + - template: "good" + start: "now-30m" + end: "now-1m" + randomness: 0.1 + # Stop data diff --git a/x-pack/packages/kbn-data-forge/example_config/rule_tests/slo_burn_rate.yaml b/x-pack/packages/kbn-data-forge/example_config/rule_tests/slo_burn_rate.yaml new file mode 100644 index 0000000000000..8bf7d058760e1 --- /dev/null +++ b/x-pack/packages/kbn-data-forge/example_config/rule_tests/slo_burn_rate.yaml @@ -0,0 +1,22 @@ +--- +elasticsearch: + installKibanaUser: false + +kibana: + installAssets: false + +indexing: + dataset: "fake_stack" + interval: 3600000 + +schedule: + # Start with good events + - template: "good" + start: "now-7d" + end: "now-45m" + - template: "bad" + start: "now-2h" + end: "now+25m" + randomness: 0.2 + interval: 60000 + diff --git a/x-pack/packages/kbn-data-forge/example_config/transition_example.yaml b/x-pack/packages/kbn-data-forge/example_config/transition_example.yaml new file mode 100644 index 0000000000000..b161d019bef5d --- /dev/null +++ b/x-pack/packages/kbn-data-forge/example_config/transition_example.yaml @@ -0,0 +1,45 @@ +--- +elasticsearch: + installKibanaUser: false + +kibana: + installAssets: true + +indexing: + eventsPerCycle: 1000 + dataset: "fake_logs" + +schedule: + - template: "good" + start: "now-90m" + end: "now-75m" + eventsPerCycle: 1000 + randomness: 0.1 + - template: "bad" + start: "now-75m" + end: "now-60m" + randomness: 0.1 + eventsPerCycle: + start: 1000 + end: 5000 + method: "exp" + - template: "bad" + start: "now-60m" + end: "now-45m" + eventsPerCycle: 5000 + randomness: 0.1 + - template: "good" + start: "now-45m" + end: "now-30m" + eventsPerCycle: + start: 5000 + end: 1000 + method: "exp" + randomness: 0.1 + - template: "good" + start: "now-30m" + end: false + eventsPerCycle: 1000 + randomness: 0.1 + + diff --git a/x-pack/packages/kbn-data-forge/example_config/transitioning_templates_example.yaml b/x-pack/packages/kbn-data-forge/example_config/transitioning_templates_example.yaml new file mode 100644 index 0000000000000..f5d3cbb50fef9 --- /dev/null +++ b/x-pack/packages/kbn-data-forge/example_config/transitioning_templates_example.yaml @@ -0,0 +1,66 @@ +--- +elasticsearch: + installKibanaUser: false + +kibana: + installAssets: true + +indexing: + eventsPerCycle: 100 + dataset: "fake_stack" + +schedule: + # Start with good events + - template: "good" + start: "now-90m" + end: "now-45m" + eventsPerCycle: 100 + randomness: 0.1 + # Transition from good to bad by setting up two schedules witht he same + # time frames but different templates to transition between 0 to 100 and vice + # versa + - template: "good" + start: "now-45m" + end: "now-35m" + eventsPerCycle: + start: 100 + end: 1 + method: "linear" + randomness: 0.1 + - template: "bad" + start: "now-45m" + end: "now-35m" + eventsPerCycle: + start: 1 + end: 100 + method: "linear" + randomness: 0.1 + # Bad for 10 minutes + - template: "bad" + start: "now-35m" + end: "now-25m" + eventsPerCycle: 100 + randomness: 0.1 + # Transition back from bad to good + - template: "good" + start: "now-25m" + end: "now-15m" + eventsPerCycle: + start: 1 + end: 100 + method: "linear" + randomness: 0.1 + - template: "bad" + start: "now-25m" + end: "now-15m" + eventsPerCycle: + start: 100 + end: 1 + method: "linear" + randomness: 0.1 + # continue with good + - template: "good" + start: "now-15m" + end: false + eventsPerCycle: 100 + randomness: 0.1 From 934a06ccf7c599685e04469ff0801e461ee9c2d5 Mon Sep 17 00:00:00 2001 From: Maxim Palenov Date: Tue, 13 Feb 2024 16:30:25 +0100 Subject: [PATCH 73/83] [Security Solution] Fix importing rules referencing preconfigured connectors (#176284) **Fixes:** https://github.com/elastic/kibana/issues/157253 ## Summary This PR fixes rules import with `overwrite_action_connectors` set to true when ndjson contains rules with actions referencing preconfigured action connectors. ## Details A user can preconfigure action connectors as described [here](https://www.elastic.co/guide/en/kibana/current/pre-configured-connectors.html). At the same time Elastic Could instances have Elastic-cloud-SMTP connector preconfigured. In particular import doesn't work as expected in Elastic Cloud for rules having actions referencing the preconfigured Elastic-cloud-SMTP connector. This is fixed by filtering out preconfigured connector ids so importing logic only handles custom action connectors. On top of this functional tests have been added to make sure the problem won't come back. ### 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 - [x] [Ran](https://buildkite.com/elastic/kibana-flaky-test-suite-runner/builds/5139) in Flaky test runner for ESS and Serverless and no flakiness has been revealed --- .../import_rules/rule_to_import.mock.ts | 95 ++-- .../import_rules/rule_to_import.test.ts | 268 ++++----- .../rule_to_import_validation.test.ts | 20 +- .../import_rule_action_connectors.test.ts | 224 +++++--- .../import_rule_action_connectors.ts | 70 ++- .../check_rule_exception_references.test.ts | 22 +- .../gather_referenced_exceptions.test.ts | 70 +-- .../logic/import/import_rules_utils.test.ts | 50 +- .../import_connectors.ts | 516 ++++++++++++++++++ .../trial_license_complete_tier/index.ts | 1 + .../utils/combine_to_ndjson.ts | 10 + .../utils/connectors/create_connector.ts | 27 + .../utils/connectors/delete_connector.ts | 15 + .../utils/connectors/get_connector.ts | 21 + .../utils/connectors/index.ts | 10 + .../detections_response/utils/index.ts | 1 + 16 files changed, 1020 insertions(+), 400 deletions(-) create mode 100644 x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_import_export/trial_license_complete_tier/import_connectors.ts create mode 100644 x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/combine_to_ndjson.ts create mode 100644 x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/connectors/create_connector.ts create mode 100644 x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/connectors/delete_connector.ts create mode 100644 x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/connectors/get_connector.ts create mode 100644 x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/connectors/index.ts diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/import_rules/rule_to_import.mock.ts b/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/import_rules/rule_to_import.mock.ts index 6161e2a00f960..2b36645363edc 100644 --- a/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/import_rules/rule_to_import.mock.ts +++ b/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/import_rules/rule_to_import.mock.ts @@ -7,17 +7,19 @@ import type { RuleToImport } from './rule_to_import'; -export const getImportRulesSchemaMock = (ruleId = 'rule-1'): RuleToImport => ({ - description: 'some description', - name: 'Query with a rule id', - query: 'user.name: root or user.name: admin', - severity: 'high', - type: 'query', - risk_score: 55, - language: 'kuery', - rule_id: ruleId, - immutable: false, -}); +export const getImportRulesSchemaMock = (rewrites?: Partial): RuleToImport => + ({ + description: 'some description', + name: 'Query with a rule id', + query: 'user.name: root or user.name: admin', + severity: 'high', + type: 'query', + risk_score: 55, + language: 'kuery', + rule_id: 'rule-1', + immutable: false, + ...rewrites, + } as RuleToImport); export const getImportRulesWithIdSchemaMock = (ruleId = 'rule-1'): RuleToImport => ({ id: '6afb8ce1-ea94-4790-8653-fd0b021d2113', @@ -47,42 +49,46 @@ export const rulesToNdJsonString = (rules: RuleToImport[]) => { * @param ruleIds Array of ruleIds with which to generate rule JSON */ export const ruleIdsToNdJsonString = (ruleIds: string[]) => { - const rules = ruleIds.map((ruleId) => getImportRulesSchemaMock(ruleId)); + const rules = ruleIds.map((ruleId) => getImportRulesSchemaMock({ rule_id: ruleId })); return rulesToNdJsonString(rules); }; -export const getImportThreatMatchRulesSchemaMock = (ruleId = 'rule-1'): RuleToImport => ({ - description: 'some description', - name: 'Query with a rule id', - query: 'user.name: root or user.name: admin', - severity: 'high', - type: 'threat_match', - risk_score: 55, - language: 'kuery', - rule_id: ruleId, - threat_index: ['index-123'], - threat_mapping: [{ entries: [{ field: 'host.name', type: 'mapping', value: 'host.name' }] }], - threat_query: '*:*', - threat_filters: [ - { - bool: { - must: [ - { - query_string: { - query: 'host.name: linux', - analyze_wildcard: true, - time_zone: 'Zulu', +export const getImportThreatMatchRulesSchemaMock = ( + rewrites?: Partial +): RuleToImport => + ({ + description: 'some description', + name: 'Query with a rule id', + query: 'user.name: root or user.name: admin', + severity: 'high', + type: 'threat_match', + risk_score: 55, + language: 'kuery', + rule_id: 'rule-1', + threat_index: ['index-123'], + threat_mapping: [{ entries: [{ field: 'host.name', type: 'mapping', value: 'host.name' }] }], + threat_query: '*:*', + threat_filters: [ + { + bool: { + must: [ + { + query_string: { + query: 'host.name: linux', + analyze_wildcard: true, + time_zone: 'Zulu', + }, }, - }, - ], - filter: [], - should: [], - must_not: [], + ], + filter: [], + should: [], + must_not: [], + }, }, - }, - ], - immutable: false, -}); + ], + immutable: false, + ...rewrites, + } as RuleToImport); export const webHookConnector = { id: 'cabc78e0-9031-11ed-b076-53cc4d57aaf1', @@ -104,8 +110,7 @@ export const webHookConnector = { export const ruleWithConnectorNdJSON = (): string => { const items = [ - { - ...getImportRulesSchemaMock(), + getImportRulesSchemaMock({ actions: [ { group: 'default', @@ -114,7 +119,7 @@ export const ruleWithConnectorNdJSON = (): string => { params: {}, }, ], - }, + }), webHookConnector, ]; const stringOfExceptions = items.map((item) => JSON.stringify(item)); diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/import_rules/rule_to_import.test.ts b/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/import_rules/rule_to_import.test.ts index 3f364c6619db6..2894da32593fa 100644 --- a/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/import_rules/rule_to_import.test.ts +++ b/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/import_rules/rule_to_import.test.ts @@ -27,10 +27,10 @@ describe('RuleToImport', () => { }); test('extra properties are removed', () => { - const payload: RuleToImportInput & { madeUp: string } = { - ...getImportRulesSchemaMock(), + const payload: RuleToImportInput = getImportRulesSchemaMock({ + // @ts-expect-error add an unknown field madeUp: 'hi', - }; + }); const result = RuleToImport.safeParse(payload); expectParseSuccess(result); @@ -241,10 +241,7 @@ describe('RuleToImport', () => { }); test('You can send in an empty array to threat', () => { - const payload: RuleToImportInput = { - ...getImportRulesSchemaMock(), - threat: [], - }; + const payload: RuleToImportInput = getImportRulesSchemaMock({ threat: [] }); const result = RuleToImport.safeParse(payload); @@ -289,10 +286,7 @@ describe('RuleToImport', () => { }); test('allows references to be sent as valid', () => { - const payload: RuleToImportInput = { - ...getImportRulesSchemaMock(), - references: ['index-1'], - }; + const payload: RuleToImportInput = getImportRulesSchemaMock({ references: ['index-1'] }); const result = RuleToImport.safeParse(payload); @@ -307,10 +301,10 @@ describe('RuleToImport', () => { }); test('references cannot be numbers', () => { - const payload: Omit & { references: number[] } = { - ...getImportRulesSchemaMock(), + const payload = getImportRulesSchemaMock({ + // @ts-expect-error assign wrong type value references: [5], - }; + }); const result = RuleToImport.safeParse(payload); expectParseError(result); @@ -321,10 +315,10 @@ describe('RuleToImport', () => { }); test('indexes cannot be numbers', () => { - const payload: Omit & { index: number[] } = { - ...getImportRulesSchemaMock(), + const payload = getImportRulesSchemaMock({ + // @ts-expect-error assign wrong type value index: [5], - }; + }); const result = RuleToImport.safeParse(payload); expectParseError(result); @@ -358,10 +352,9 @@ describe('RuleToImport', () => { }); test('saved_query type can have filters with it', () => { - const payload = { - ...getImportRulesSchemaMock(), + const payload = getImportRulesSchemaMock({ filters: [], - }; + }); const result = RuleToImport.safeParse(payload); @@ -369,10 +362,10 @@ describe('RuleToImport', () => { }); test('filters cannot be a string', () => { - const payload: Omit & { filters: string } = { - ...getImportRulesSchemaMock(), + const payload = getImportRulesSchemaMock({ + // @ts-expect-error assign wrong type value filters: 'some string', - }; + }); const result = RuleToImport.safeParse(payload); expectParseError(result); @@ -383,10 +376,7 @@ describe('RuleToImport', () => { }); test('language validates with kuery', () => { - const payload = { - ...getImportRulesSchemaMock(), - language: 'kuery', - }; + const payload = getImportRulesSchemaMock({ language: 'kuery' }); const result = RuleToImport.safeParse(payload); @@ -394,10 +384,7 @@ describe('RuleToImport', () => { }); test('language validates with lucene', () => { - const payload = { - ...getImportRulesSchemaMock(), - language: 'lucene', - }; + const payload = getImportRulesSchemaMock({ language: 'lucene' }); const result = RuleToImport.safeParse(payload); @@ -405,10 +392,10 @@ describe('RuleToImport', () => { }); test('language does not validate with something made up', () => { - const payload: Omit & { language: string } = { - ...getImportRulesSchemaMock(), + const payload = getImportRulesSchemaMock({ + // @ts-expect-error assign unsupported value language: 'something-made-up', - }; + }); const result = RuleToImport.safeParse(payload); expectParseError(result); @@ -419,10 +406,7 @@ describe('RuleToImport', () => { }); test('max_signals cannot be negative', () => { - const payload: RuleToImportInput = { - ...getImportRulesSchemaMock(), - max_signals: -1, - }; + const payload: RuleToImportInput = getImportRulesSchemaMock({ max_signals: -1 }); const result = RuleToImport.safeParse(payload); expectParseError(result); @@ -433,10 +417,7 @@ describe('RuleToImport', () => { }); test('max_signals cannot be zero', () => { - const payload: RuleToImportInput = { - ...getImportRulesSchemaMock(), - max_signals: 0, - }; + const payload: RuleToImportInput = getImportRulesSchemaMock({ max_signals: 0 }); const result = RuleToImport.safeParse(payload); expectParseError(result); @@ -447,10 +428,7 @@ describe('RuleToImport', () => { }); test('max_signals can be 1', () => { - const payload: RuleToImportInput = { - ...getImportRulesSchemaMock(), - max_signals: 1, - }; + const payload: RuleToImportInput = getImportRulesSchemaMock({ max_signals: 1 }); const result = RuleToImport.safeParse(payload); @@ -458,10 +436,7 @@ describe('RuleToImport', () => { }); test('You can optionally send in an array of tags', () => { - const payload: RuleToImportInput = { - ...getImportRulesSchemaMock(), - tags: ['tag_1', 'tag_2'], - }; + const payload: RuleToImportInput = getImportRulesSchemaMock({ tags: ['tag_1', 'tag_2'] }); const result = RuleToImport.safeParse(payload); @@ -469,10 +444,10 @@ describe('RuleToImport', () => { }); test('You cannot send in an array of tags that are numbers', () => { - const payload: Omit & { tags: number[] } = { - ...getImportRulesSchemaMock(), + const payload = getImportRulesSchemaMock({ + // @ts-expect-error assign unsupported value tags: [0, 1, 2], - }; + }); const result = RuleToImport.safeParse(payload); expectParseError(result); @@ -483,11 +458,9 @@ describe('RuleToImport', () => { }); test('You cannot send in an array of threat that are missing "framework"', () => { - const payload: Omit & { - threat: Array>>; - } = { - ...getImportRulesSchemaMock(), + const payload = getImportRulesSchemaMock({ threat: [ + // @ts-expect-error assign unsupported value { tactic: { id: 'fakeId', @@ -503,7 +476,7 @@ describe('RuleToImport', () => { ], }, ], - }; + }); const result = RuleToImport.safeParse(payload); expectParseError(result); @@ -512,11 +485,9 @@ describe('RuleToImport', () => { }); test('You cannot send in an array of threat that are missing "tactic"', () => { - const payload: Omit & { - threat: Array>>; - } = { - ...getImportRulesSchemaMock(), + const payload = getImportRulesSchemaMock({ threat: [ + // @ts-expect-error assign unsupported value { framework: 'fake', technique: [ @@ -528,7 +499,7 @@ describe('RuleToImport', () => { ], }, ], - }; + }); const result = RuleToImport.safeParse(payload); expectParseError(result); @@ -537,10 +508,7 @@ describe('RuleToImport', () => { }); test('You can send in an array of threat that are missing "technique"', () => { - const payload: Omit & { - threat: Array>>; - } = { - ...getImportRulesSchemaMock(), + const payload = getImportRulesSchemaMock({ threat: [ { framework: 'fake', @@ -551,7 +519,7 @@ describe('RuleToImport', () => { }, }, ], - }; + }); const result = RuleToImport.safeParse(payload); @@ -559,10 +527,9 @@ describe('RuleToImport', () => { }); test('You can optionally send in an array of false positives', () => { - const payload: RuleToImportInput = { - ...getImportRulesSchemaMock(), + const payload: RuleToImportInput = getImportRulesSchemaMock({ false_positives: ['false_1', 'false_2'], - }; + }); const result = RuleToImport.safeParse(payload); @@ -570,10 +537,10 @@ describe('RuleToImport', () => { }); test('You cannot send in an array of false positives that are numbers', () => { - const payload: Omit & { false_positives: number[] } = { - ...getImportRulesSchemaMock(), + const payload = getImportRulesSchemaMock({ + // @ts-expect-error assign unsupported value false_positives: [5, 4], - }; + }); const result = RuleToImport.safeParse(payload); expectParseError(result); @@ -584,10 +551,10 @@ describe('RuleToImport', () => { }); test('You cannot set the immutable to a number when trying to create a rule', () => { - const payload: Omit & { immutable: number } = { - ...getImportRulesSchemaMock(), + const payload = getImportRulesSchemaMock({ + // @ts-expect-error assign unsupported value immutable: 5, - }; + }); const result = RuleToImport.safeParse(payload); expectParseError(result); @@ -598,10 +565,9 @@ describe('RuleToImport', () => { }); test('You can optionally set the immutable to be false', () => { - const payload: RuleToImportInput = { - ...getImportRulesSchemaMock(), + const payload: RuleToImportInput = getImportRulesSchemaMock({ immutable: false, - }; + }); const result = RuleToImport.safeParse(payload); @@ -609,10 +575,10 @@ describe('RuleToImport', () => { }); test('You cannot set the immutable to be true', () => { - const payload: Omit & { immutable: true } = { - ...getImportRulesSchemaMock(), + const payload = getImportRulesSchemaMock({ + // @ts-expect-error assign unsupported value immutable: true, - }; + }); const result = RuleToImport.safeParse(payload); expectParseError(result); @@ -623,10 +589,10 @@ describe('RuleToImport', () => { }); test('You cannot set the immutable to be a number', () => { - const payload: Omit & { immutable: number } = { - ...getImportRulesSchemaMock(), + const payload = getImportRulesSchemaMock({ + // @ts-expect-error assign unsupported value immutable: 5, - }; + }); const result = RuleToImport.safeParse(payload); expectParseError(result); @@ -637,10 +603,9 @@ describe('RuleToImport', () => { }); test('You cannot set the risk_score to 101', () => { - const payload: RuleToImportInput = { - ...getImportRulesSchemaMock(), + const payload: RuleToImportInput = getImportRulesSchemaMock({ risk_score: 101, - }; + }); const result = RuleToImport.safeParse(payload); expectParseError(result); @@ -651,10 +616,9 @@ describe('RuleToImport', () => { }); test('You cannot set the risk_score to -1', () => { - const payload: RuleToImportInput = { - ...getImportRulesSchemaMock(), + const payload: RuleToImportInput = getImportRulesSchemaMock({ risk_score: -1, - }; + }); const result = RuleToImport.safeParse(payload); expectParseError(result); @@ -665,10 +629,9 @@ describe('RuleToImport', () => { }); test('You can set the risk_score to 0', () => { - const payload: RuleToImportInput = { - ...getImportRulesSchemaMock(), + const payload: RuleToImportInput = getImportRulesSchemaMock({ risk_score: 0, - }; + }); const result = RuleToImport.safeParse(payload); @@ -676,10 +639,9 @@ describe('RuleToImport', () => { }); test('You can set the risk_score to 100', () => { - const payload: RuleToImportInput = { - ...getImportRulesSchemaMock(), + const payload: RuleToImportInput = getImportRulesSchemaMock({ risk_score: 100, - }; + }); const result = RuleToImport.safeParse(payload); @@ -687,12 +649,11 @@ describe('RuleToImport', () => { }); test('You can set meta to any object you want', () => { - const payload: RuleToImportInput = { - ...getImportRulesSchemaMock(), + const payload: RuleToImportInput = getImportRulesSchemaMock({ meta: { somethingMadeUp: { somethingElse: true }, }, - }; + }); const result = RuleToImport.safeParse(payload); @@ -700,10 +661,10 @@ describe('RuleToImport', () => { }); test('You cannot create meta as a string', () => { - const payload: Omit & { meta: string } = { - ...getImportRulesSchemaMock(), + const payload = getImportRulesSchemaMock({ + // @ts-expect-error assign unsupported value meta: 'should not work', - }; + }); const result = RuleToImport.safeParse(payload); expectParseError(result); @@ -714,11 +675,10 @@ describe('RuleToImport', () => { }); test('validates with timeline_id and timeline_title', () => { - const payload: RuleToImportInput = { - ...getImportRulesSchemaMock(), + const payload: RuleToImportInput = getImportRulesSchemaMock({ timeline_id: 'timeline-id', timeline_title: 'timeline-title', - }; + }); const result = RuleToImport.safeParse(payload); @@ -726,10 +686,9 @@ describe('RuleToImport', () => { }); test('rule_id is required and you cannot get by with just id', () => { - const payload: RuleToImportInput = { - ...getImportRulesSchemaMock(), + const payload: RuleToImportInput = getImportRulesSchemaMock({ id: 'c4e80a0d-e20f-4efc-84c1-08112da5a612', - }; + }); // @ts-expect-error delete payload.rule_id; @@ -740,13 +699,12 @@ describe('RuleToImport', () => { }); test('it validates with created_at, updated_at, created_by, updated_by values', () => { - const payload: RuleToImportInput = { - ...getImportRulesSchemaMock(), + const payload: RuleToImportInput = getImportRulesSchemaMock({ created_at: '2020-01-09T06:15:24.749Z', updated_at: '2020-01-09T06:15:24.749Z', created_by: 'Braden Hassanabad', updated_by: 'Evan Hassanabad', - }; + }); const result = RuleToImport.safeParse(payload); @@ -754,10 +712,7 @@ describe('RuleToImport', () => { }); test('it does not validate with epoch strings for created_at', () => { - const payload: RuleToImportInput = { - ...getImportRulesSchemaMock(), - created_at: '1578550728650', - }; + const payload: RuleToImportInput = getImportRulesSchemaMock({ created_at: '1578550728650' }); const result = RuleToImport.safeParse(payload); expectParseError(result); @@ -766,10 +721,7 @@ describe('RuleToImport', () => { }); test('it does not validate with epoch strings for updated_at', () => { - const payload: RuleToImportInput = { - ...getImportRulesSchemaMock(), - updated_at: '1578550728650', - }; + const payload: RuleToImportInput = getImportRulesSchemaMock({ updated_at: '1578550728650' }); const result = RuleToImport.safeParse(payload); expectParseError(result); @@ -800,10 +752,10 @@ describe('RuleToImport', () => { }); test('You cannot set the severity to a value other than low, medium, high, or critical', () => { - const payload: Omit & { severity: string } = { - ...getImportRulesSchemaMock(), + const payload = getImportRulesSchemaMock({ + // @ts-expect-error assign unsupported value severity: 'junk', - }; + }); const result = RuleToImport.safeParse(payload); expectParseError(result); @@ -825,10 +777,12 @@ describe('RuleToImport', () => { }); test('You cannot send in an array of actions that are missing "group"', () => { - const payload: Omit = { - ...getImportRulesSchemaMock(), - actions: [{ id: 'id', action_type_id: 'action_type_id', params: {} }], - }; + const payload = getImportRulesSchemaMock({ + actions: [ + // @ts-expect-error assign unsupported value + { id: 'id', action_type_id: 'action_type_id', params: {} }, + ], + }); const result = RuleToImport.safeParse(payload); expectParseError(result); @@ -837,10 +791,12 @@ describe('RuleToImport', () => { }); test('You cannot send in an array of actions that are missing "id"', () => { - const payload: Omit = { - ...getImportRulesSchemaMock(), - actions: [{ group: 'group', action_type_id: 'action_type_id', params: {} }], - }; + const payload = getImportRulesSchemaMock({ + actions: [ + // @ts-expect-error assign unsupported value + { group: 'group', action_type_id: 'action_type_id', params: {} }, + ], + }); const result = RuleToImport.safeParse(payload); expectParseError(result); @@ -849,10 +805,12 @@ describe('RuleToImport', () => { }); test('You cannot send in an array of actions that are missing "action_type_id"', () => { - const payload: Omit = { - ...getImportRulesSchemaMock(), - actions: [{ group: 'group', id: 'id', params: {} }], - }; + const payload = getImportRulesSchemaMock({ + actions: [ + // @ts-expect-error assign unsupported value + { group: 'group', id: 'id', params: {} }, + ], + }); const result = RuleToImport.safeParse(payload); expectParseError(result); @@ -863,10 +821,12 @@ describe('RuleToImport', () => { }); test('You cannot send in an array of actions that are missing "params"', () => { - const payload: Omit = { - ...getImportRulesSchemaMock(), - actions: [{ group: 'group', id: 'id', action_type_id: 'action_type_id' }], - }; + const payload = getImportRulesSchemaMock({ + actions: [ + // @ts-expect-error assign unsupported value + { group: 'group', id: 'id', action_type_id: 'action_type_id' }, + ], + }); const result = RuleToImport.safeParse(payload); expectParseError(result); @@ -875,17 +835,17 @@ describe('RuleToImport', () => { }); test('You cannot send in an array of actions that are including "actionTypeId"', () => { - const payload: Omit = { - ...getImportRulesSchemaMock(), + const payload = getImportRulesSchemaMock({ actions: [ { group: 'group', id: 'id', + // @ts-expect-error assign unsupported value actionTypeId: 'actionTypeId', params: {}, }, ], - }; + }); const result = RuleToImport.safeParse(payload); expectParseError(result); @@ -907,32 +867,28 @@ describe('RuleToImport', () => { describe('note', () => { test('You can set note to a string', () => { - const payload: RuleToImport = { - ...getImportRulesSchemaMock(), + const payload: RuleToImport = getImportRulesSchemaMock({ note: '# documentation markdown here', - }; + }); const result = RuleToImport.safeParse(payload); expectParseSuccess(result); }); test('You can set note to an empty string', () => { - const payload: RuleToImportInput = { - ...getImportRulesSchemaMock(), - note: '', - }; + const payload: RuleToImportInput = getImportRulesSchemaMock({ note: '' }); const result = RuleToImport.safeParse(payload); expectParseSuccess(result); }); test('You cannot create note as an object', () => { - const payload: Omit & { note: {} } = { - ...getImportRulesSchemaMock(), + const payload = getImportRulesSchemaMock({ + // @ts-expect-error assign unsupported value note: { somethingHere: 'something else', }, - }; + }); const result = RuleToImport.safeParse(payload); expectParseError(result); @@ -1102,10 +1058,10 @@ describe('RuleToImport', () => { }); test('data_view_id cannot be a number', () => { - const payload: Omit & { data_view_id: number } = { - ...getImportRulesSchemaMock(), + const payload = getImportRulesSchemaMock({ + // @ts-expect-error assign unsupported value data_view_id: 5, - }; + }); const result = RuleToImport.safeParse(payload); expectParseError(result); diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/import_rules/rule_to_import_validation.test.ts b/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/import_rules/rule_to_import_validation.test.ts index 31ac993eb4053..597dcf0cc3bcb 100644 --- a/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/import_rules/rule_to_import_validation.test.ts +++ b/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/import_rules/rule_to_import_validation.test.ts @@ -12,40 +12,34 @@ import { validateRuleToImport } from './rule_to_import_validation'; describe('Rule to import schema, additional validation', () => { describe('validateRuleToImport', () => { test('You cannot omit timeline_title when timeline_id is present', () => { - const schema: RuleToImport = { - ...getImportRulesSchemaMock(), + const schema: RuleToImport = getImportRulesSchemaMock({ timeline_id: '123', - }; + }); delete schema.timeline_title; const errors = validateRuleToImport(schema); expect(errors).toEqual(['when "timeline_id" exists, "timeline_title" must also exist']); }); test('You cannot have empty string for timeline_title when timeline_id is present', () => { - const schema: RuleToImport = { - ...getImportRulesSchemaMock(), + const schema: RuleToImport = getImportRulesSchemaMock({ timeline_id: '123', timeline_title: '', - }; + }); const errors = validateRuleToImport(schema); expect(errors).toEqual(['"timeline_title" cannot be an empty string']); }); test('You cannot have timeline_title with an empty timeline_id', () => { - const schema: RuleToImport = { - ...getImportRulesSchemaMock(), + const schema: RuleToImport = getImportRulesSchemaMock({ timeline_id: '', timeline_title: 'some-title', - }; + }); const errors = validateRuleToImport(schema); expect(errors).toEqual(['"timeline_id" cannot be an empty string']); }); test('You cannot have timeline_title without timeline_id', () => { - const schema: RuleToImport = { - ...getImportRulesSchemaMock(), - timeline_title: 'some-title', - }; + const schema: RuleToImport = getImportRulesSchemaMock({ timeline_title: 'some-title' }); delete schema.timeline_id; const errors = validateRuleToImport(schema); expect(errors).toEqual(['when "timeline_title" exists, "timeline_id" must also exist']); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/import/action_connectors/import_rule_action_connectors.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/import/action_connectors/import_rule_action_connectors.test.ts index 8afecd245e2a9..84352c1ea0f1e 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/import/action_connectors/import_rule_action_connectors.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/import/action_connectors/import_rule_action_connectors.test.ts @@ -13,8 +13,7 @@ import { importRuleActionConnectors } from './import_rule_action_connectors'; import { coreMock } from '@kbn/core/server/mocks'; const rules = [ - { - ...getImportRulesSchemaMock(), + getImportRulesSchemaMock({ actions: [ { group: 'default', @@ -23,14 +22,9 @@ const rules = [ params: {}, }, ], - }, -]; -const rulesWithoutActions = [ - { - ...getImportRulesSchemaMock(), - actions: [], - }, + }), ]; +const rulesWithoutActions = [getImportRulesSchemaMock({ actions: [] })]; const actionConnectors = [webHookConnector]; const actionsClient = actionsClientMock.create(); actionsClient.getAll.mockResolvedValue([]); @@ -115,8 +109,7 @@ describe('importRuleActionConnectors', () => { const actionsImporter = core.savedObjects.getImporter; const ruleWith2Connectors = [ - { - ...getImportRulesSchemaMock(), + getImportRulesSchemaMock({ actions: [ { group: 'default', @@ -135,7 +128,7 @@ describe('importRuleActionConnectors', () => { action_type_id: '.slack', }, ], - }, + }), ]; const res = await importRuleActionConnectors({ actionConnectors, @@ -189,8 +182,7 @@ describe('importRuleActionConnectors', () => { actionsClient, actionsImporter: actionsImporter(), rules: [ - { - ...getImportRulesSchemaMock(), + getImportRulesSchemaMock({ actions: [ { group: 'default', @@ -205,7 +197,7 @@ describe('importRuleActionConnectors', () => { params: {}, }, ], - }, + }), ], overwrite: false, }); @@ -235,8 +227,8 @@ describe('importRuleActionConnectors', () => { actionsClient, actionsImporter: actionsImporter(), rules: [ - { - ...getImportRulesSchemaMock(), + getImportRulesSchemaMock({ + rule_id: 'rule-1', actions: [ { group: 'default', @@ -245,9 +237,9 @@ describe('importRuleActionConnectors', () => { params: {}, }, ], - }, - { - ...getImportRulesSchemaMock('rule-2'), + }), + getImportRulesSchemaMock({ + rule_id: 'rule-2', actions: [ { group: 'default', @@ -256,7 +248,7 @@ describe('importRuleActionConnectors', () => { params: {}, }, ], - }, + }), ], overwrite: false, }); @@ -340,45 +332,6 @@ describe('importRuleActionConnectors', () => { expect(actionsImporter2Importer.import).not.toBeCalled(); }); - it('should not skip importing the action-connectors if all connectors have been imported/created before when overwrite is true', async () => { - core.savedObjects.getImporter = jest.fn().mockReturnValueOnce({ - import: jest.fn().mockResolvedValue({ - success: true, - successCount: 1, - errors: [], - warnings: [], - }), - }); - const actionsImporter = core.savedObjects.getImporter; - - actionsClient.getAll.mockResolvedValue([ - { - actionTypeId: '.webhook', - name: 'webhook', - isPreconfigured: true, - id: 'cabc78e0-9031-11ed-b076-53cc4d57aaf1', - referencedByCount: 1, - isDeprecated: false, - isSystemAction: false, - }, - ]); - - const res = await importRuleActionConnectors({ - actionConnectors, - actionsClient, - actionsImporter: actionsImporter(), - rules, - overwrite: true, - }); - - expect(res).toEqual({ - success: true, - successCount: 1, - errors: [], - warnings: [], - }); - }); - it('should import one rule with connector successfully even if it was exported from different namespaces by generating destinationId and replace the old actionId with it', async () => { const successResults = [ { @@ -441,8 +394,8 @@ describe('importRuleActionConnectors', () => { it('should import multiple rules with connectors successfully even if they were exported from different namespaces by generating destinationIds and replace the old actionIds with them', async () => { const multipleRules = [ - { - ...getImportRulesSchemaMock(), + getImportRulesSchemaMock({ + rule_id: 'rule_1', actions: [ { group: 'default', @@ -451,9 +404,8 @@ describe('importRuleActionConnectors', () => { params: {}, }, ], - }, - { - ...getImportRulesSchemaMock(), + }), + getImportRulesSchemaMock({ rule_id: 'rule_2', id: '0abc78e0-7031-11ed-b076-53cc4d57aaf1', actions: [ @@ -464,7 +416,7 @@ describe('importRuleActionConnectors', () => { params: {}, }, ], - }, + }), ]; const successResults = [ { @@ -535,7 +487,7 @@ describe('importRuleActionConnectors', () => { name: 'Query with a rule id', query: 'user.name: root or user.name: admin', risk_score: 55, - rule_id: 'rule-1', + rule_id: 'rule_1', severity: 'high', type: 'query', }, @@ -569,4 +521,142 @@ describe('importRuleActionConnectors', () => { rulesWithMigratedActions, }); }); + + describe('overwrite is set to "true"', () => { + it('should return an error when action connectors are missing in ndjson import file', async () => { + const rulesToImport = [ + getImportRulesSchemaMock({ + rule_id: 'rule-with-missed-action-connector', + actions: [ + { + group: 'default', + id: 'some-connector-id', + params: {}, + action_type_id: '.webhook', + }, + ], + }), + ]; + + actionsClient.getAll.mockResolvedValue([]); + + const res = await importRuleActionConnectors({ + actionConnectors: [], + actionsClient, + actionsImporter: core.savedObjects.getImporter(), + rules: rulesToImport, + overwrite: true, + }); + + expect(res).toEqual({ + success: false, + successCount: 0, + errors: [ + { + error: { + message: '1 connector is missing. Connector id missing is: some-connector-id', + status_code: 404, + }, + id: 'some-connector-id', + rule_id: 'rule-with-missed-action-connector', + }, + ], + warnings: [], + }); + }); + + it('should NOT return an error when a missing action connector in ndjson import file is a preconfigured one', async () => { + const rulesToImport = [ + getImportRulesSchemaMock({ + rule_id: 'rule-with-missed-action-connector', + actions: [ + { + group: 'default', + id: 'prebuilt-connector-id', + params: {}, + action_type_id: '.webhook', + }, + ], + }), + ]; + + actionsClient.getAll.mockResolvedValue([ + { + actionTypeId: '.webhook', + name: 'webhook', + isPreconfigured: true, + id: 'prebuilt-connector-id', + referencedByCount: 1, + isDeprecated: false, + isSystemAction: false, + }, + ]); + + const res = await importRuleActionConnectors({ + actionConnectors: [], + actionsClient, + actionsImporter: core.savedObjects.getImporter(), + rules: rulesToImport, + overwrite: true, + }); + + expect(res).toEqual({ + success: true, + successCount: 0, + errors: [], + warnings: [], + }); + }); + + it('should not skip importing the action-connectors if all connectors have been imported/created before', async () => { + const rulesToImport = [ + getImportRulesSchemaMock({ + actions: [ + { + group: 'default', + id: 'connector-id', + action_type_id: '.webhook', + params: {}, + }, + ], + }), + ]; + + core.savedObjects.getImporter = jest.fn().mockReturnValueOnce({ + import: jest.fn().mockResolvedValue({ + success: true, + successCount: 1, + errors: [], + warnings: [], + }), + }); + + actionsClient.getAll.mockResolvedValue([ + { + actionTypeId: '.webhook', + name: 'webhook', + isPreconfigured: true, + id: 'connector-id', + referencedByCount: 1, + isDeprecated: false, + isSystemAction: false, + }, + ]); + + const res = await importRuleActionConnectors({ + actionConnectors, + actionsClient, + actionsImporter: core.savedObjects.getImporter(), + rules: rulesToImport, + overwrite: true, + }); + + expect(res).toEqual({ + success: true, + successCount: 0, + errors: [], + warnings: [], + }); + }); + }); }); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/import/action_connectors/import_rule_action_connectors.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/import/action_connectors/import_rule_action_connectors.ts index dfbacdcfd8ce2..db86ad158289c 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/import/action_connectors/import_rule_action_connectors.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/import/action_connectors/import_rule_action_connectors.ts @@ -8,6 +8,8 @@ import { Readable } from 'stream'; import type { SavedObjectsImportResponse } from '@kbn/core-saved-objects-common'; import type { SavedObject } from '@kbn/core-saved-objects-server'; +import type { ActionsClient } from '@kbn/actions-plugin/server'; +import type { ConnectorWithExtraFindData } from '@kbn/actions-plugin/server/application/connector/types'; import type { RuleToImport } from '../../../../../../../common/api/detection_engine/rule_management'; import type { WarningSchema } from '../../../../../../../common/api/detection_engine'; @@ -22,6 +24,13 @@ import { } from './utils'; import type { ImportRuleActionConnectorsParams, ImportRuleActionConnectorsResult } from './types'; +const NO_ACTION_RESULT = { + success: true, + errors: [], + successCount: 0, + warnings: [], +}; + export const importRuleActionConnectors = async ({ actionConnectors, actionsClient, @@ -30,41 +39,40 @@ export const importRuleActionConnectors = async ({ overwrite, }: ImportRuleActionConnectorsParams): Promise => { try { - const actionConnectorRules = getActionConnectorRules(rules); - const actionsIds: string[] = Object.keys(actionConnectorRules); + const connectorIdToRuleIdsMap = getActionConnectorRules(rules); + const referencedConnectorIds = await filterOutPreconfiguredConnectors( + actionsClient, + Object.keys(connectorIdToRuleIdsMap) + ); - if (!actionsIds.length) - return { - success: true, - errors: [], - successCount: 0, - warnings: [], - }; + if (!referencedConnectorIds.length) { + return NO_ACTION_RESULT; + } - if (overwrite && !actionConnectors.length) - return handleActionsHaveNoConnectors(actionsIds, actionConnectorRules); + if (overwrite && !actionConnectors.length) { + return handleActionsHaveNoConnectors(referencedConnectorIds, connectorIdToRuleIdsMap); + } let actionConnectorsToImport: SavedObject[] = actionConnectors; if (!overwrite) { - const newIdsToAdd = await filterExistingActionConnectors(actionsClient, actionsIds); + const newIdsToAdd = await filterExistingActionConnectors( + actionsClient, + referencedConnectorIds + ); const foundMissingConnectors = checkIfActionsHaveMissingConnectors( actionConnectors, newIdsToAdd, - actionConnectorRules + connectorIdToRuleIdsMap ); if (foundMissingConnectors) return foundMissingConnectors; // filter out existing connectors actionConnectorsToImport = actionConnectors.filter(({ id }) => newIdsToAdd.includes(id)); } - if (!actionConnectorsToImport.length) - return { - success: true, - errors: [], - successCount: 0, - warnings: [], - }; + if (!actionConnectorsToImport.length) { + return NO_ACTION_RESULT; + } const readStream = Readable.from(actionConnectorsToImport); const { success, successCount, successResults, warnings, errors }: SavedObjectsImportResponse = @@ -93,3 +101,25 @@ export const importRuleActionConnectors = async ({ return returnErroredImportResult(error); } }; + +async function fetchPreconfiguredActionConnectors( + actionsClient: ActionsClient +): Promise { + const knownConnectors = await actionsClient.getAll(); + + return knownConnectors.filter((c) => c.isPreconfigured); +} + +async function filterOutPreconfiguredConnectors( + actionsClient: ActionsClient, + connectorsIds: string[] +): Promise { + if (connectorsIds.length === 0) { + return []; + } + + const preconfiguredActionConnectors = await fetchPreconfiguredActionConnectors(actionsClient); + const preconfiguredActionConnectorIds = new Set(preconfiguredActionConnectors.map((c) => c.id)); + + return connectorsIds.filter((id) => !preconfiguredActionConnectorIds.has(id)); +} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/import/check_rule_exception_references.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/import/check_rule_exception_references.test.ts index 2b7996c5094ab..2a249e7d9383a 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/import/check_rule_exception_references.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/import/check_rule_exception_references.test.ts @@ -13,7 +13,7 @@ describe('checkRuleExceptionReferences', () => { it('returns empty array if rule has no exception list references', () => { const result = checkRuleExceptionReferences({ existingLists: {}, - rule: { ...getImportRulesSchemaMock(), exceptions_list: [] }, + rule: getImportRulesSchemaMock({ exceptions_list: [] }), }); expect(result).toEqual([[], []]); @@ -29,12 +29,11 @@ describe('checkRuleExceptionReferences', () => { type: 'detection', }, }, - rule: { - ...getImportRulesSchemaMock(), + rule: getImportRulesSchemaMock({ exceptions_list: [ { id: '123', list_id: 'my-list', namespace_type: 'single', type: 'detection' }, ], - }, + }), }); expect(result).toEqual([ @@ -53,12 +52,11 @@ describe('checkRuleExceptionReferences', () => { it('removes an exception reference if list not found to exist', () => { const result = checkRuleExceptionReferences({ existingLists: {}, - rule: { - ...getImportRulesSchemaMock(), + rule: getImportRulesSchemaMock({ exceptions_list: [ { id: '123', list_id: 'my-list', namespace_type: 'single', type: 'detection' }, ], - }, + }), }); expect(result).toEqual([ @@ -86,12 +84,11 @@ describe('checkRuleExceptionReferences', () => { type: 'detection', }, }, - rule: { - ...getImportRulesSchemaMock(), + rule: getImportRulesSchemaMock({ exceptions_list: [ { id: '123', list_id: 'my-list', namespace_type: 'single', type: 'detection' }, ], - }, + }), }); expect(result).toEqual([ [ @@ -118,12 +115,11 @@ describe('checkRuleExceptionReferences', () => { type: 'endpoint', }, }, - rule: { - ...getImportRulesSchemaMock(), + rule: getImportRulesSchemaMock({ exceptions_list: [ { id: '123', list_id: 'my-list', namespace_type: 'single', type: 'detection' }, ], - }, + }), }); expect(result).toEqual([ [ diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/import/gather_referenced_exceptions.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/import/gather_referenced_exceptions.test.ts index df136fe6cfc8d..1605c745256b2 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/import/gather_referenced_exceptions.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/import/gather_referenced_exceptions.test.ts @@ -53,12 +53,11 @@ describe('get referenced exceptions', () => { it('returns found referenced exception lists', async () => { const result = await getReferencedExceptionLists({ rules: [ - { - ...getImportRulesSchemaMock(), + getImportRulesSchemaMock({ exceptions_list: [ { id: '123', list_id: 'my-list', namespace_type: 'single', type: 'detection' }, ], - }, + }), ], savedObjectsClient, }); @@ -77,16 +76,14 @@ describe('get referenced exceptions', () => { it('returns found referenced exception lists when first exceptions list is empty array and second list has a value', async () => { const result = await getReferencedExceptionLists({ rules: [ - { - ...getImportRulesSchemaMock(), + getImportRulesSchemaMock({ exceptions_list: [], - }, - { - ...getImportRulesSchemaMock(), + }), + getImportRulesSchemaMock({ exceptions_list: [ { id: '123', list_id: 'my-list', namespace_type: 'single', type: 'detection' }, ], - }, + }), ], savedObjectsClient, }); @@ -105,18 +102,16 @@ describe('get referenced exceptions', () => { it('returns found referenced exception lists when two rules reference same list', async () => { const result = await getReferencedExceptionLists({ rules: [ - { - ...getImportRulesSchemaMock(), + getImportRulesSchemaMock({ exceptions_list: [ { id: '123', list_id: 'my-list', namespace_type: 'single', type: 'detection' }, ], - }, - { - ...getImportRulesSchemaMock(), + }), + getImportRulesSchemaMock({ exceptions_list: [ { id: '123', list_id: 'my-list', namespace_type: 'single', type: 'detection' }, ], - }, + }), ], savedObjectsClient, }); @@ -157,18 +152,16 @@ describe('get referenced exceptions', () => { const result = await getReferencedExceptionLists({ rules: [ - { - ...getImportRulesSchemaMock(), + getImportRulesSchemaMock({ exceptions_list: [ { id: '456', list_id: 'other-list', namespace_type: 'single', type: 'detection' }, ], - }, - { - ...getImportRulesSchemaMock(), + }), + getImportRulesSchemaMock({ exceptions_list: [ { id: '123', list_id: 'my-list', namespace_type: 'single', type: 'detection' }, ], - }, + }), ], savedObjectsClient, }); @@ -207,45 +200,38 @@ describe('get referenced exceptions', () => { describe('parseReferencdedExceptionsLists', () => { it('should return parsed lists when exception lists are not empty', () => { const res = parseReferencedExceptionsLists([ - { - ...getImportRulesSchemaMock(), + getImportRulesSchemaMock({ exceptions_list: [ { id: '123', list_id: 'my-list', namespace_type: 'single', type: 'detection' }, ], - }, + }), ]); expect(res).toEqual([[], [{ listId: 'my-list', namespaceType: 'single' }]]); }); it('should return parsed lists when one empty exception list and one non-empty list', () => { const res = parseReferencedExceptionsLists([ - { - ...getImportRulesSchemaMock(), - exceptions_list: [], - }, - { - ...getImportRulesSchemaMock(), + getImportRulesSchemaMock({ exceptions_list: [] }), + getImportRulesSchemaMock({ exceptions_list: [ { id: '123', list_id: 'my-list', namespace_type: 'single', type: 'detection' }, ], - }, + }), ]); expect(res).toEqual([[], [{ listId: 'my-list', namespaceType: 'single' }]]); }); it('should return parsed lists when two non-empty exception lists reference same list', () => { const res = parseReferencedExceptionsLists([ - { - ...getImportRulesSchemaMock(), + getImportRulesSchemaMock({ exceptions_list: [ { id: '123', list_id: 'my-list', namespace_type: 'single', type: 'detection' }, ], - }, - { - ...getImportRulesSchemaMock(), + }), + getImportRulesSchemaMock({ exceptions_list: [ { id: '123', list_id: 'my-list', namespace_type: 'single', type: 'detection' }, ], - }, + }), ]); expect(res).toEqual([ [], @@ -258,18 +244,16 @@ describe('get referenced exceptions', () => { it('should return parsed lists when two non-empty exception lists reference differet lists', () => { const res = parseReferencedExceptionsLists([ - { - ...getImportRulesSchemaMock(), + getImportRulesSchemaMock({ exceptions_list: [ { id: '123', list_id: 'my-list', namespace_type: 'single', type: 'detection' }, ], - }, - { - ...getImportRulesSchemaMock(), + }), + getImportRulesSchemaMock({ exceptions_list: [ { id: '456', list_id: 'other-list', namespace_type: 'single', type: 'detection' }, ], - }, + }), ]); expect(res).toEqual([ [], diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/import/import_rules_utils.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/import/import_rules_utils.test.ts index 0b601be81dd62..5b097bacf2d9c 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/import/import_rules_utils.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/import/import_rules_utils.test.ts @@ -74,14 +74,7 @@ describe('importRules', () => { it('creates rule if no matching existing rule found', async () => { const result = await importRules({ - ruleChunks: [ - [ - { - ...getImportRulesSchemaMock(), - rule_id: 'rule-1', - }, - ], - ], + ruleChunks: [[getImportRulesSchemaMock({ rule_id: 'rule-1' })]], rulesResponseAcc: [], mlAuthz, overwriteRules: false, @@ -98,14 +91,7 @@ describe('importRules', () => { clients.rulesClient.find.mockResolvedValue(getFindResultWithSingleHit()); const result = await importRules({ - ruleChunks: [ - [ - { - ...getImportRulesSchemaMock(), - rule_id: 'rule-1', - }, - ], - ], + ruleChunks: [[getImportRulesSchemaMock({ rule_id: 'rule-1' })]], rulesResponseAcc: [], mlAuthz, overwriteRules: false, @@ -129,10 +115,9 @@ describe('importRules', () => { const result = await importRules({ ruleChunks: [ [ - { - ...getImportRulesSchemaMock(), + getImportRulesSchemaMock({ rule_id: 'rule-1', - }, + }), ], ], rulesResponseAcc: [], @@ -151,14 +136,7 @@ describe('importRules', () => { clients.rulesClient.find.mockRejectedValue(new Error('error reading rule')); const result = await importRules({ - ruleChunks: [ - [ - { - ...getImportRulesSchemaMock(), - rule_id: 'rule-1', - }, - ], - ], + ruleChunks: [[getImportRulesSchemaMock({ rule_id: 'rule-1' })]], rulesResponseAcc: [], mlAuthz, overwriteRules: true, @@ -183,14 +161,7 @@ describe('importRules', () => { (createRules as jest.Mock).mockRejectedValue(new Error('error creating rule')); const result = await importRules({ - ruleChunks: [ - [ - { - ...getImportRulesSchemaMock(), - rule_id: 'rule-1', - }, - ], - ], + ruleChunks: [[getImportRulesSchemaMock({ rule_id: 'rule-1' })]], rulesResponseAcc: [], mlAuthz, overwriteRules: false, @@ -214,14 +185,7 @@ describe('importRules', () => { clients.rulesClient.find.mockResolvedValue(getFindResultWithSingleHit()); const result = await importRules({ - ruleChunks: [ - [ - { - ...getImportRulesSchemaMock(), - rule_id: 'rule-1', - }, - ], - ], + ruleChunks: [[getImportRulesSchemaMock({ rule_id: 'rule-1' })]], rulesResponseAcc: [], mlAuthz, overwriteRules: true, diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_import_export/trial_license_complete_tier/import_connectors.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_import_export/trial_license_complete_tier/import_connectors.ts new file mode 100644 index 0000000000000..89af5bb0987ed --- /dev/null +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_import_export/trial_license_complete_tier/import_connectors.ts @@ -0,0 +1,516 @@ +/* + * 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 expect from 'expect'; + +import { DETECTION_ENGINE_RULES_URL } from '@kbn/security-solution-plugin/common/constants'; +import { combineToNdJson, deleteAllRules, getCustomQueryRuleParams } from '../../../utils'; +import { createConnector, deleteConnector, getConnector } from '../../../utils/connectors'; +import { FtrProviderContext } from '../../../../../ftr_provider_context'; + +export default ({ getService }: FtrProviderContext): void => { + const supertest = getService('supertest'); + const log = getService('log'); + + describe('@ess @brokenInServerless @skipInQA import action connectors', () => { + const CONNECTOR_ID = '1be16246-642a-4ed8-bfd3-b47f8c7d7055'; + const ANOTHER_CONNECTOR_ID = 'abc16246-642a-4ed8-bfd3-b47f8c7d7055'; + const CUSTOM_ACTION_CONNECTOR = { + id: CONNECTOR_ID, + type: 'action', + updated_at: '2024-02-05T11:52:10.692Z', + created_at: '2024-02-05T11:52:10.692Z', + version: 'WzYsMV0=', + attributes: { + actionTypeId: '.email', + name: 'test-connector', + isMissingSecrets: false, + config: { + from: 'a@test.com', + service: 'other', + host: 'example.com', + port: 123, + secure: false, + hasAuth: false, + tenantId: null, + clientId: null, + oauthTokenUrl: null, + }, + secrets: {}, + }, + references: [], + managed: false, + coreMigrationVersion: '8.8.0', + typeMigrationVersion: '8.3.0', + }; + + beforeEach(async () => { + await deleteAllRules(supertest, log); + await deleteConnector(supertest, CONNECTOR_ID); + await deleteConnector(supertest, ANOTHER_CONNECTOR_ID); + }); + + describe('overwrite connectors is set to "false"', () => { + it('imports a rule with an action connector', async () => { + const ndjson = combineToNdJson( + getCustomQueryRuleParams({ + rule_id: 'rule-1', + name: 'Rule 1', + actions: [ + { + group: 'default', + id: CONNECTOR_ID, + params: { + message: 'Some message', + to: ['test@test.com'], + subject: 'Test', + }, + action_type_id: '.email', + uuid: 'fda6721b-d3a4-4d2c-ad0c-18893759e096', + frequency: { summary: true, notifyWhen: 'onActiveAlert', throttle: null }, + }, + ], + }), + CUSTOM_ACTION_CONNECTOR + ); + + const { body } = await supertest + .post(`${DETECTION_ENGINE_RULES_URL}/_import`) + .set('kbn-xsrf', 'true') + .set('elastic-api-version', '2023-10-31') + .attach('file', Buffer.from(ndjson), 'rules.ndjson') + .expect(200); + + expect(body).toMatchObject({ + errors: [], + success: true, + success_count: 1, + rules_count: 1, + action_connectors_success: true, + action_connectors_success_count: 1, + action_connectors_errors: [], + action_connectors_warnings: [], + }); + + expect(await getConnector(supertest, CONNECTOR_ID)).toMatchObject({ + id: CONNECTOR_ID, + name: 'test-connector', + }); + }); + + it('DOES NOT import an action connector without rules', async () => { + const ndjson = combineToNdJson(CUSTOM_ACTION_CONNECTOR); + + const { body } = await supertest + .post(`${DETECTION_ENGINE_RULES_URL}/_import`) + .set('kbn-xsrf', 'true') + .set('elastic-api-version', '2023-10-31') + .attach('file', Buffer.from(ndjson), 'rules.ndjson') + .expect(200); + + expect(body).toMatchObject({ + errors: [], + success: true, + success_count: 0, + rules_count: 0, + action_connectors_success: true, + action_connectors_success_count: 0, + action_connectors_errors: [], + action_connectors_warnings: [], + }); + + await supertest + .get(`/api/actions/connector/${CONNECTOR_ID}`) + .set('kbn-xsrf', 'foo') + .expect(404); + }); + + it('DOES NOT import an action connector when there are no rules referencing it', async () => { + const ndjson = combineToNdJson( + getCustomQueryRuleParams({ + rule_id: 'rule-1', + name: 'Rule 1', + actions: [ + { + group: 'default', + id: ANOTHER_CONNECTOR_ID, + params: { + message: 'Some message', + to: ['test@test.com'], + subject: 'Test', + }, + action_type_id: '.email', + uuid: 'fda6721b-d3a4-4d2c-ad0c-18893759e096', + frequency: { summary: true, notifyWhen: 'onActiveAlert', throttle: null }, + }, + ], + }), + { ...CUSTOM_ACTION_CONNECTOR, id: ANOTHER_CONNECTOR_ID }, + CUSTOM_ACTION_CONNECTOR + ); + + const { body } = await supertest + .post(`${DETECTION_ENGINE_RULES_URL}/_import`) + .set('kbn-xsrf', 'true') + .set('elastic-api-version', '2023-10-31') + .attach('file', Buffer.from(ndjson), 'rules.ndjson') + .expect(200); + + expect(body).toMatchObject({ + errors: [], + success: true, + success_count: 1, + rules_count: 1, + action_connectors_success: true, + action_connectors_success_count: 1, + action_connectors_errors: [], + action_connectors_warnings: [], + }); + + await supertest + .get(`/api/actions/connector/${CONNECTOR_ID}`) + .set('kbn-xsrf', 'foo') + .expect(404); + }); + + it('DOES NOT return an error when rule actions reference a preconfigured connector', async () => { + const ndjson = combineToNdJson( + getCustomQueryRuleParams({ + rule_id: 'rule-1', + name: 'Rule 1', + actions: [ + { + group: 'default', + id: 'my-test-email', + params: { + message: 'Some message', + to: ['test@test.com'], + subject: 'Test', + }, + action_type_id: '.email', + uuid: 'fda6721b-d3a4-4d2c-ad0c-18893759e096', + frequency: { summary: true, notifyWhen: 'onActiveAlert', throttle: null }, + }, + ], + }) + ); + + const { body } = await supertest + .post(`${DETECTION_ENGINE_RULES_URL}/_import`) + .set('kbn-xsrf', 'true') + .set('elastic-api-version', '2023-10-31') + .attach('file', Buffer.from(ndjson), 'rules.ndjson') + .expect(200); + + expect(body).toMatchObject({ + errors: [], + success: true, + success_count: 1, + rules_count: 1, + action_connectors_success: true, + action_connectors_success_count: 0, + action_connectors_errors: [], + action_connectors_warnings: [], + }); + }); + + /** + * When importing an action connector, if its `id` matches with an existing one, the type and config isn't checked. + * In fact, the connector being imported can have a different type and configuration, and its creation will be skipped. + */ + it('skips importing already existing action connectors', async () => { + await createConnector( + supertest, + { + connector_type_id: '.webhook', + name: 'test-connector', + config: { + // checkout `x-pack/test/security_solution_api_integration/config/ess/config.base.ts` for configuration + // `some.non.existent.com` must be set as an allowed host + url: 'https://some.non.existent.com', + method: 'post', + }, + secrets: {}, + }, + CONNECTOR_ID + ); + + const ndjson = combineToNdJson( + getCustomQueryRuleParams({ + rule_id: 'rule-1', + name: 'Rule 1', + actions: [ + { + group: 'default', + id: CONNECTOR_ID, + params: { + message: 'Some message', + to: ['test@test.com'], + subject: 'Test', + }, + action_type_id: '.email', + uuid: 'fda6721b-d3a4-4d2c-ad0c-18893759e096', + frequency: { summary: true, notifyWhen: 'onActiveAlert', throttle: null }, + }, + ], + }), + CUSTOM_ACTION_CONNECTOR + ); + + const { body } = await supertest + .post(`${DETECTION_ENGINE_RULES_URL}/_import`) + .set('kbn-xsrf', 'true') + .set('elastic-api-version', '2023-10-31') + .attach('file', Buffer.from(ndjson), 'rules.ndjson') + .expect(200); + + expect(body).toMatchObject({ + errors: [], + success: true, + success_count: 1, + rules_count: 1, + action_connectors_success: true, + action_connectors_success_count: 0, + action_connectors_errors: [], + action_connectors_warnings: [], + }); + + expect(await getConnector(supertest, CONNECTOR_ID)).toMatchObject({ + id: CONNECTOR_ID, + name: 'test-connector', + }); + }); + + it('returns an error when connector is missing in ndjson', async () => { + const ndjson = combineToNdJson( + getCustomQueryRuleParams({ + rule_id: 'rule-1', + name: 'Rule 1', + actions: [ + { + group: 'default', + id: CONNECTOR_ID, + params: { + message: 'Some message', + to: ['test@test.com'], + subject: 'Test', + }, + action_type_id: '.email', + uuid: 'fda6721b-d3a4-4d2c-ad0c-18893759e096', + frequency: { summary: true, notifyWhen: 'onActiveAlert', throttle: null }, + }, + ], + }) + ); + + const { body } = await supertest + .post(`${DETECTION_ENGINE_RULES_URL}/_import`) + .set('kbn-xsrf', 'true') + .set('elastic-api-version', '2023-10-31') + .attach('file', Buffer.from(ndjson), 'rules.ndjson') + .expect(200); + + expect(body).toMatchObject({ + errors: [ + { + error: { + message: `1 connector is missing. Connector id missing is: ${CONNECTOR_ID}`, + status_code: 404, + }, + id: CONNECTOR_ID, + rule_id: 'rule-1', + }, + ], + success: false, + success_count: 0, + rules_count: 1, + action_connectors_success: false, + action_connectors_success_count: 0, + action_connectors_errors: [ + { + error: { + message: `1 connector is missing. Connector id missing is: ${CONNECTOR_ID}`, + status_code: 404, + }, + id: CONNECTOR_ID, + rule_id: 'rule-1', + }, + ], + action_connectors_warnings: [], + }); + }); + }); + + describe('overwrite connectors is set to "true"', () => { + it('overwrites existing connector', async () => { + await createConnector( + supertest, + { + connector_type_id: '.webhook', + name: 'existing-connector', + config: { + // checkout `x-pack/test/security_solution_api_integration/config/ess/config.base.ts` for configuration + // `some.non.existent.com` must be set as an allowed host + url: 'https://some.non.existent.com', + method: 'post', + }, + secrets: {}, + }, + CONNECTOR_ID + ); + + const ndjson = combineToNdJson( + getCustomQueryRuleParams({ + rule_id: 'rule-1', + name: 'Rule 1', + actions: [ + { + group: 'default', + id: CONNECTOR_ID, + params: { + message: 'Some message', + to: ['test@test.com'], + subject: 'Test', + }, + action_type_id: '.email', + uuid: 'fda6721b-d3a4-4d2c-ad0c-18893759e096', + frequency: { summary: true, notifyWhen: 'onActiveAlert', throttle: null }, + }, + ], + }), + { + ...CUSTOM_ACTION_CONNECTOR, + attributes: { ...CUSTOM_ACTION_CONNECTOR.attributes, name: 'updated-connector' }, + } + ); + + const { body } = await supertest + .post(`${DETECTION_ENGINE_RULES_URL}/_import?overwrite_action_connectors=true`) + .set('kbn-xsrf', 'true') + .set('elastic-api-version', '2023-10-31') + .attach('file', Buffer.from(ndjson), 'rules.ndjson') + .expect(200); + + expect(body).toMatchObject({ + errors: [], + success: true, + success_count: 1, + rules_count: 1, + action_connectors_success: true, + action_connectors_success_count: 1, + action_connectors_errors: [], + action_connectors_warnings: [], + }); + + expect(await getConnector(supertest, CONNECTOR_ID)).toMatchObject({ + id: CONNECTOR_ID, + name: 'updated-connector', + }); + }); + + it('returns an error when connector is missing in ndjson', async () => { + const ndjson = combineToNdJson( + getCustomQueryRuleParams({ + rule_id: 'rule-1', + name: 'Rule 1', + actions: [ + { + group: 'default', + id: CONNECTOR_ID, + params: { + message: 'Some message', + to: ['test@test.com'], + subject: 'Test', + }, + action_type_id: '.email', + uuid: 'fda6721b-d3a4-4d2c-ad0c-18893759e096', + frequency: { summary: true, notifyWhen: 'onActiveAlert', throttle: null }, + }, + ], + }) + ); + + const { body } = await supertest + .post(`${DETECTION_ENGINE_RULES_URL}/_import?overwrite_action_connectors=true`) + .set('kbn-xsrf', 'true') + .set('elastic-api-version', '2023-10-31') + .attach('file', Buffer.from(ndjson), 'rules.ndjson') + .expect(200); + + expect(body).toMatchObject({ + errors: [ + { + error: { + message: `1 connector is missing. Connector id missing is: ${CONNECTOR_ID}`, + status_code: 404, + }, + id: CONNECTOR_ID, + rule_id: 'rule-1', + }, + ], + success: false, + success_count: 0, + rules_count: 1, + action_connectors_success: false, + action_connectors_success_count: 0, + action_connectors_errors: [ + { + error: { + message: `1 connector is missing. Connector id missing is: ${CONNECTOR_ID}`, + status_code: 404, + }, + id: CONNECTOR_ID, + rule_id: 'rule-1', + }, + ], + action_connectors_warnings: [], + }); + }); + + it('DOES NOT return an error when rule actions reference a preconfigured connector', async () => { + const ndjson = combineToNdJson( + getCustomQueryRuleParams({ + rule_id: 'rule-1', + name: 'Rule 1', + actions: [ + { + group: 'default', + id: 'my-test-email', + params: { + message: 'Some message', + to: ['test@test.com'], + subject: 'Test', + }, + action_type_id: '.email', + uuid: 'fda6721b-d3a4-4d2c-ad0c-18893759e096', + frequency: { summary: true, notifyWhen: 'onActiveAlert', throttle: null }, + }, + ], + }) + ); + + const { body } = await supertest + .post(`${DETECTION_ENGINE_RULES_URL}/_import?overwrite_action_connectors=true`) + .set('kbn-xsrf', 'true') + .set('elastic-api-version', '2023-10-31') + .attach('file', Buffer.from(ndjson), 'rules.ndjson') + .expect(200); + + expect(body).toMatchObject({ + errors: [], + success: true, + success_count: 1, + rules_count: 1, + action_connectors_success: true, + action_connectors_success_count: 0, + action_connectors_errors: [], + action_connectors_warnings: [], + }); + }); + }); + }); +}; diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_import_export/trial_license_complete_tier/index.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_import_export/trial_license_complete_tier/index.ts index f2240c96dfe5b..2fc4754a3f230 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_import_export/trial_license_complete_tier/index.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_import_export/trial_license_complete_tier/index.ts @@ -14,5 +14,6 @@ export default function ({ loadTestFile }: FtrProviderContext) { loadTestFile(require.resolve('./import_export_rules')); loadTestFile(require.resolve('./import_rules')); loadTestFile(require.resolve('./import_rules_ess')); + loadTestFile(require.resolve('./import_connectors')); }); } diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/combine_to_ndjson.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/combine_to_ndjson.ts new file mode 100644 index 0000000000000..fc2baff9c365f --- /dev/null +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/combine_to_ndjson.ts @@ -0,0 +1,10 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export function combineToNdJson(...parts: unknown[]): string { + return parts.map((p) => JSON.stringify(p)).join('\n'); +} diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/connectors/create_connector.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/connectors/create_connector.ts new file mode 100644 index 0000000000000..9c3f54e019653 --- /dev/null +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/connectors/create_connector.ts @@ -0,0 +1,27 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type SuperTest from 'supertest'; + +interface CreateConnectorBody { + readonly name: string; + readonly config: Record; + readonly connector_type_id: string; + readonly secrets: Record; +} + +export async function createConnector( + supertest: SuperTest.SuperTest, + connector: CreateConnectorBody, + id = '' +): Promise { + await supertest + .post(`/api/actions/connector/${id}`) + .set('kbn-xsrf', 'foo') + .send(connector) + .expect(200); +} diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/connectors/delete_connector.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/connectors/delete_connector.ts new file mode 100644 index 0000000000000..683f845fd8bf8 --- /dev/null +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/connectors/delete_connector.ts @@ -0,0 +1,15 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type SuperTest from 'supertest'; + +export function deleteConnector( + supertest: SuperTest.SuperTest, + connectorId: string +): SuperTest.Test { + return supertest.delete(`/api/actions/connector/${connectorId}`).set('kbn-xsrf', 'foo'); +} diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/connectors/get_connector.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/connectors/get_connector.ts new file mode 100644 index 0000000000000..8f7e4830372f9 --- /dev/null +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/connectors/get_connector.ts @@ -0,0 +1,21 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { Connector } from '@kbn/actions-plugin/server/application/connector/types'; +import type SuperTest from 'supertest'; + +export async function getConnector( + supertest: SuperTest.SuperTest, + connectorId: string +): Promise { + const response = await supertest + .get(`/api/actions/connector/${connectorId}`) + .set('kbn-xsrf', 'foo') + .expect(200); + + return response.body; +} diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/connectors/index.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/connectors/index.ts new file mode 100644 index 0000000000000..be89cd4a94d47 --- /dev/null +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/connectors/index.ts @@ -0,0 +1,10 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export * from './create_connector'; +export * from './get_connector'; +export * from './delete_connector'; diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/index.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/index.ts index d51fce39e3410..d86075bb41f60 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/index.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/index.ts @@ -23,3 +23,4 @@ export * from './get_stats'; export * from './get_detection_metrics_from_body'; export * from './get_stats_url'; export * from './retry'; +export * from './combine_to_ndjson'; From 28d46a8c02337f0b76634724177203127fbb2723 Mon Sep 17 00:00:00 2001 From: dkirchan <55240027+dkirchan@users.noreply.github.com> Date: Tue, 13 Feb 2024 17:35:45 +0200 Subject: [PATCH 74/83] [Security] Moved reset_creds call to reset_internal_creds (#176410) ## Summary Actions needed following the email that was sent about the breaking change : > API: rather than returning credentials for a privileged "elastic" user, [we'll return](https://elasticco.atlassian.net/browse/CP-5477) credentials for a much-less privileged "admin" user. Note this is the user that can be manipulated by customers. This new user won't be an "operator" user anymore: any test that relies on this user being able to do things such as retrieving the cluster health, role mappings, node stats, etc. would therefore break. > A second set of credentials can be retrieved for a privileged "testing-internal" user through a dedicated API endpoint. To retrieve credentials for that user, please update your automation with a small change: > 1. rather than calling the _reset-credentials endpoint, please call the[_reset-internal-credentials > 2. remove any hard-coded reference of the "elastic" user: the new username is returned in the API response --------- Co-authored-by: Gloria Hornero --- .../pipelines/security_solution/api_integration.yml | 1 + .../security_solution/security_solution_cypress.yml | 1 + .../api_integration/api-integration-tests.sh | 2 +- .../scripts/run_cypress/parallel_serverless.ts | 11 ++++++----- .../cypress/support/es_archiver.ts | 2 +- 5 files changed, 10 insertions(+), 7 deletions(-) diff --git a/.buildkite/pipelines/security_solution/api_integration.yml b/.buildkite/pipelines/security_solution/api_integration.yml index fd5ec303a7c66..68fee0777775d 100644 --- a/.buildkite/pipelines/security_solution/api_integration.yml +++ b/.buildkite/pipelines/security_solution/api_integration.yml @@ -23,6 +23,7 @@ steps: limit: 2 - group: 'Execute Tests' + key: test_execution depends_on: build_image steps: - label: Running exception_workflows:qa:serverless diff --git a/.buildkite/pipelines/security_solution/security_solution_cypress.yml b/.buildkite/pipelines/security_solution/security_solution_cypress.yml index 997e607ebb43f..69ac04e1c8a23 100644 --- a/.buildkite/pipelines/security_solution/security_solution_cypress.yml +++ b/.buildkite/pipelines/security_solution/security_solution_cypress.yml @@ -24,6 +24,7 @@ steps: - group: "Execute Tests" depends_on: build_image + key: test_execution steps: # - command: .buildkite/scripts/pipelines/security_solution_quality_gate/security_solution_cypress/mki_security_solution_cypress.sh cypress:run:qa:serverless:explore # label: 'Serverless MKI QA Explore - Security Solution Cypress Tests' diff --git a/.buildkite/scripts/pipelines/security_solution_quality_gate/api_integration/api-integration-tests.sh b/.buildkite/scripts/pipelines/security_solution_quality_gate/api_integration/api-integration-tests.sh index 096709dc5da43..9bbb9aa5b7442 100755 --- a/.buildkite/scripts/pipelines/security_solution_quality_gate/api_integration/api-integration-tests.sh +++ b/.buildkite/scripts/pipelines/security_solution_quality_gate/api_integration/api-integration-tests.sh @@ -51,7 +51,7 @@ KB_URL=$(echo $ENVIRONMENT_DETAILS | jq -r '.endpoints.kibana') sleep 5 # Resetting the credentials of the elastic user in the project -CREDS_BODY=$(curl -s --location --request POST "$QA_CONSOLE_URL/api/v1/serverless/projects/security/$ID/_reset-credentials" \ +CREDS_BODY=$(curl -s --location --request POST "$QA_CONSOLE_URL/api/v1/serverless/projects/security/$ID/_reset-internal-credentials" \ --header "Authorization: ApiKey $QA_API_KEY" \ --header 'Content-Type: application/json' | jq '.') USERNAME=$(echo $CREDS_BODY | jq -r '.username') diff --git a/x-pack/plugins/security_solution/scripts/run_cypress/parallel_serverless.ts b/x-pack/plugins/security_solution/scripts/run_cypress/parallel_serverless.ts index a80b0d9ce15a1..ece544ed836d9 100644 --- a/x-pack/plugins/security_solution/scripts/run_cypress/parallel_serverless.ts +++ b/x-pack/plugins/security_solution/scripts/run_cypress/parallel_serverless.ts @@ -179,7 +179,7 @@ async function resetCredentials( const fetchResetCredentialsStatusAttempt = async (attemptNum: number) => { const response = await axios.post( - `${BASE_ENV_URL}/api/v1/serverless/projects/security/${projectId}/_reset-credentials`, + `${BASE_ENV_URL}/api/v1/serverless/projects/security/${projectId}/_reset-internal-credentials`, {}, { headers: { @@ -199,7 +199,7 @@ async function resetCredentials( if (error instanceof AxiosError && error.code === 'ENOTFOUND') { log.info('Project is not reachable. A retry will be triggered soon..'); } else { - log.info(error); + log.error(`${error.message}`); } }, retries: 100, @@ -223,6 +223,7 @@ function waitForProjectInitialized(projectId: string, apiKey: string): Promise Date: Tue, 13 Feb 2024 16:36:09 +0100 Subject: [PATCH 75/83] [ML] AIOps: UI action for Change Point Detection embeddable to open in the ML app (#176694) ## Summary Closes #161248 Added a UI action to open change point detection in the AIOps labs. 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 - [x] Any UI touched in this PR is usable by keyboard only (learn more about [keyboard accessibility](https://webaim.org/techniques/keyboard/)) - [x] 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)) - [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) --- x-pack/plugins/aiops/kibana.jsonc | 3 +- .../plugins/aiops/public/ui_actions/index.ts | 5 ++ .../ui_actions/open_change_point_ml.tsx | 58 +++++++++++++++++++ x-pack/plugins/ml/common/types/locator.ts | 19 +++++- .../ml/public/locator/formatters/aiops.ts | 49 ++++++++++++++++ .../ml/public/locator/ml_locator.test.ts | 51 ++++++++++++++++ .../plugins/ml/public/locator/ml_locator.ts | 9 ++- 7 files changed, 191 insertions(+), 3 deletions(-) create mode 100644 x-pack/plugins/aiops/public/ui_actions/open_change_point_ml.tsx create mode 100644 x-pack/plugins/ml/public/locator/formatters/aiops.ts diff --git a/x-pack/plugins/aiops/kibana.jsonc b/x-pack/plugins/aiops/kibana.jsonc index 192fa65cd9a2b..895a0f5a20bf6 100644 --- a/x-pack/plugins/aiops/kibana.jsonc +++ b/x-pack/plugins/aiops/kibana.jsonc @@ -17,7 +17,8 @@ "presentationUtil", "dashboard", "fieldFormats", - "unifiedSearch" + "unifiedSearch", + "share" ], "optionalPlugins": [ "cases", diff --git a/x-pack/plugins/aiops/public/ui_actions/index.ts b/x-pack/plugins/aiops/public/ui_actions/index.ts index 14e1879027fff..ca92f03de3e92 100644 --- a/x-pack/plugins/aiops/public/ui_actions/index.ts +++ b/x-pack/plugins/aiops/public/ui_actions/index.ts @@ -13,6 +13,7 @@ import { } from '@kbn/ml-ui-actions/src/aiops/ui_actions'; import type { CoreStart } from '@kbn/core/public'; +import { createOpenChangePointInMlAppAction } from './open_change_point_ml'; import type { AiopsPluginStartDeps } from '../types'; import { createEditChangePointChartsPanelAction } from './edit_change_point_charts_panel'; import { createCategorizeFieldAction } from '../components/log_categorization'; @@ -27,6 +28,8 @@ export function registerAiopsUiActions( coreStart, pluginStart ); + const openChangePointInMlAppAction = createOpenChangePointInMlAppAction(coreStart, pluginStart); + // // Register actions and triggers uiActions.addTriggerAction(CONTEXT_MENU_TRIGGER, editChangePointChartPanelAction); @@ -36,4 +39,6 @@ export function registerAiopsUiActions( CATEGORIZE_FIELD_TRIGGER, createCategorizeFieldAction(coreStart, pluginStart) ); + + uiActions.addTriggerAction(CONTEXT_MENU_TRIGGER, openChangePointInMlAppAction); } diff --git a/x-pack/plugins/aiops/public/ui_actions/open_change_point_ml.tsx b/x-pack/plugins/aiops/public/ui_actions/open_change_point_ml.tsx new file mode 100644 index 0000000000000..37ee4fcc57590 --- /dev/null +++ b/x-pack/plugins/aiops/public/ui_actions/open_change_point_ml.tsx @@ -0,0 +1,58 @@ +/* + * 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 { UiActionsActionDefinition } from '@kbn/ui-actions-plugin/public'; +import { i18n } from '@kbn/i18n'; +import type { CoreStart } from '@kbn/core/public'; +import type { AiopsPluginStartDeps } from '../types'; +import type { EditChangePointChartsPanelContext } from '../embeddable/types'; +import { EMBEDDABLE_CHANGE_POINT_CHART_TYPE } from '../../common/constants'; + +export const OPEN_CHANGE_POINT_IN_ML_APP_ACTION = 'openChangePointInMlAppAction'; + +export function createOpenChangePointInMlAppAction( + coreStart: CoreStart, + pluginStart: AiopsPluginStartDeps +): UiActionsActionDefinition { + return { + id: 'open-change-point-in-ml-app', + type: OPEN_CHANGE_POINT_IN_ML_APP_ACTION, + getIconType(context): string { + return 'link'; + }, + getDisplayName: () => + i18n.translate('xpack.aiops.actions.openChangePointInMlAppName', { + defaultMessage: 'Open in AIOps Labs', + }), + async getHref(context): Promise { + const locator = pluginStart.share.url.locators.get('ML_APP_LOCATOR')!; + + const { timeRange, metricField, fn, splitField, dataViewId } = context.embeddable.getInput(); + + return locator.getUrl({ + page: 'aiops/change_point_detection', + pageState: { + index: dataViewId, + timeRange, + fieldConfigs: [{ fn, metricField, ...(splitField ? { splitField } : {}) }], + }, + }); + }, + async execute(context) { + if (!context.embeddable) { + throw new Error('Not possible to execute an action without the embeddable context'); + } + const aiopsChangePointUrl = await this.getHref!(context); + if (aiopsChangePointUrl) { + await coreStart.application.navigateToUrl(aiopsChangePointUrl!); + } + }, + async isCompatible({ embeddable }) { + return embeddable.type === EMBEDDABLE_CHANGE_POINT_CHART_TYPE; + }, + }; +} diff --git a/x-pack/plugins/ml/common/types/locator.ts b/x-pack/plugins/ml/common/types/locator.ts index 10b6122910b71..dd18edc96022b 100644 --- a/x-pack/plugins/ml/common/types/locator.ts +++ b/x-pack/plugins/ml/common/types/locator.ts @@ -124,6 +124,7 @@ export interface ExplorerAppState { query?: any; mlShowCharts?: boolean; } + export interface ExplorerGlobalState { ml: { jobIds: JobId[] }; time?: TimeRange; @@ -279,7 +280,8 @@ export type MlLocatorState = | MlGenericUrlState | NotificationsUrlState | TrainedModelsUrlState - | MemoryUsageUrlState; + | MemoryUsageUrlState + | ChangePointDetectionUrlState; export type MlLocatorParams = MlLocatorState & SerializableRecord; @@ -303,3 +305,18 @@ export type NotificationsUrlState = MLPageState< typeof ML_PAGES.NOTIFICATIONS, NotificationsQueryState | undefined >; + +export interface ChangePointDetectionQueryState { + index: string; + timeRange?: TimeRange; + fieldConfigs: Array<{ + fn: string; + splitField?: string; + metricField: string; + }>; +} + +export type ChangePointDetectionUrlState = MLPageState< + typeof ML_PAGES.AIOPS_CHANGE_POINT_DETECTION, + ChangePointDetectionQueryState +>; diff --git a/x-pack/plugins/ml/public/locator/formatters/aiops.ts b/x-pack/plugins/ml/public/locator/formatters/aiops.ts new file mode 100644 index 0000000000000..941eecfd711bd --- /dev/null +++ b/x-pack/plugins/ml/public/locator/formatters/aiops.ts @@ -0,0 +1,49 @@ +/* + * 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 { setStateToKbnUrl } from '@kbn/kibana-utils-plugin/public'; +import { ML_PAGES } from '../../../common/constants/locator'; +import type { ChangePointDetectionUrlState } from '../../../common/types/locator'; + +/** + * Creates URL to the Change Point Detection page + */ +export function formatChangePointDetectionUrl( + appBasePath: string, + params: ChangePointDetectionUrlState['pageState'] +): string { + let url = `${appBasePath}/${ML_PAGES.AIOPS_CHANGE_POINT_DETECTION}`; + + if (!params?.fieldConfigs) { + throw new Error('Field configs are required to create a change point detection URL'); + } + + if (!params.index) { + throw new Error('Data view is required to create a change point detection URL'); + } + + url = `${url}?index=${params.index}`; + + const { timeRange, fieldConfigs } = params; + + const appState = { + fieldConfigs, + }; + const queryState = { + time: timeRange, + }; + + url = setStateToKbnUrl('_g', queryState, { useHash: false, storeInHashQuery: false }, url); + url = setStateToKbnUrl( + '_a', + { changePoint: appState }, + { useHash: false, storeInHashQuery: false }, + url + ); + + return url; +} diff --git a/x-pack/plugins/ml/public/locator/ml_locator.test.ts b/x-pack/plugins/ml/public/locator/ml_locator.test.ts index ab2d89dee2106..4682ce68088af 100644 --- a/x-pack/plugins/ml/public/locator/ml_locator.test.ts +++ b/x-pack/plugins/ml/public/locator/ml_locator.test.ts @@ -340,5 +340,56 @@ describe('ML locator', () => { }); }); }); + + describe('AIOps labs', () => { + it('should throw an error for invalid Change point detection page state', async () => { + await expect( + definition.getLocation({ + page: ML_PAGES.AIOPS_CHANGE_POINT_DETECTION, + pageState: { + index: '123123', + }, + }) + ).rejects.toThrow('Field configs are required to create a change point detection URL'); + + await expect( + definition.getLocation({ + page: ML_PAGES.AIOPS_CHANGE_POINT_DETECTION, + pageState: { + fieldConfigs: [ + { + fn: 'max', + metricField: 'CPUUtilization', + splitField: 'instance', + }, + ], + }, + }) + ).rejects.toThrow('Data view is required to create a change point detection URL'); + }); + + it('should generate valid URL for the Change point detection page', async () => { + const location = await definition.getLocation({ + page: ML_PAGES.AIOPS_CHANGE_POINT_DETECTION, + pageState: { + index: 'test-index', + timeRange: { from: '2019-10-28T00:00:00.000Z', to: '2019-11-11T13:31:00.000Z' }, + fieldConfigs: [ + { + fn: 'max', + metricField: 'CPUUtilization', + splitField: 'instance', + }, + ], + }, + }); + + expect(location).toMatchObject({ + app: 'ml', + path: "/aiops/change_point_detection?index=test-index&_g=(time:(from:'2019-10-28T00:00:00.000Z',to:'2019-11-11T13:31:00.000Z'))&_a=(changePoint:(fieldConfigs:!((fn:max,metricField:CPUUtilization,splitField:instance))))", + state: {}, + }); + }); + }); }); }); diff --git a/x-pack/plugins/ml/public/locator/ml_locator.ts b/x-pack/plugins/ml/public/locator/ml_locator.ts index 1034101d3211e..d18785e131d89 100644 --- a/x-pack/plugins/ml/public/locator/ml_locator.ts +++ b/x-pack/plugins/ml/public/locator/ml_locator.ts @@ -6,11 +6,13 @@ */ import type { LocatorDefinition, KibanaLocation } from '@kbn/share-plugin/public'; +import { formatChangePointDetectionUrl } from './formatters/aiops'; import { formatNotificationsUrl } from './formatters/notifications'; import { DataFrameAnalyticsExplorationUrlState, MlLocatorParams, MlLocator, + ChangePointDetectionQueryState, } from '../../common/types/locator'; import { ML_APP_LOCATOR, ML_PAGES } from '../../common/constants/locator'; import { @@ -77,6 +79,12 @@ export class MlLocatorDefinition implements LocatorDefinition { case ML_PAGES.MEMORY_USAGE: path = formatMemoryUsageUrl('', params.pageState); break; + case ML_PAGES.AIOPS_CHANGE_POINT_DETECTION: + path = formatChangePointDetectionUrl( + '', + params.pageState as ChangePointDetectionQueryState + ); + break; case ML_PAGES.DATA_DRIFT_INDEX_SELECT: case ML_PAGES.DATA_DRIFT_CUSTOM: case ML_PAGES.DATA_DRIFT: @@ -96,7 +104,6 @@ export class MlLocatorDefinition implements LocatorDefinition { case ML_PAGES.AIOPS_LOG_RATE_ANALYSIS_INDEX_SELECT: case ML_PAGES.AIOPS_LOG_CATEGORIZATION: case ML_PAGES.AIOPS_LOG_CATEGORIZATION_INDEX_SELECT: - case ML_PAGES.AIOPS_CHANGE_POINT_DETECTION: case ML_PAGES.AIOPS_CHANGE_POINT_DETECTION_INDEX_SELECT: case ML_PAGES.OVERVIEW: case ML_PAGES.SETTINGS: From 2645865000e56d7359c4ca555075f5888fad0020 Mon Sep 17 00:00:00 2001 From: Tiago Costa Date: Tue, 13 Feb 2024 15:54:52 +0000 Subject: [PATCH 76/83] skip flaky suite (#172599) --- .../security_roles_privileges.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/x-pack/test/reporting_functional/reporting_and_deprecated_security/security_roles_privileges.ts b/x-pack/test/reporting_functional/reporting_and_deprecated_security/security_roles_privileges.ts index f9b84469c1eee..67e0012df4e26 100644 --- a/x-pack/test/reporting_functional/reporting_and_deprecated_security/security_roles_privileges.ts +++ b/x-pack/test/reporting_functional/reporting_and_deprecated_security/security_roles_privileges.ts @@ -24,7 +24,8 @@ export default function ({ getService }: FtrProviderContext) { await reportingFunctional.teardownEcommerce(); }); - describe('Dashboard: Download CSV file', () => { + // FLAKY: https://github.com/elastic/kibana/issues/172599 + describe.skip('Dashboard: Download CSV file', () => { it('does not allow user that does not have reporting_user role', async () => { await reportingFunctional.loginDataAnalyst(); await reportingFunctional.openSavedDashboard(DASHBOARD_TITLE); From ad20a07bb6ac33e76c26de4d08144753630a1eec Mon Sep 17 00:00:00 2001 From: Sandra G Date: Tue, 13 Feb 2024 10:55:24 -0500 Subject: [PATCH 77/83] [Infra] add services to host detail ui (#176539) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary Closes https://github.com/elastic/kibana/issues/168641. - Adds a new UI component to the host detail in a new collapsible, 'Services'. On both the flyout and asset details page when viewing an asset of type host only, this component should exist and list related services to the host. - Removes "APM Services" link that used to exist in the detail view Screenshot 2024-02-09 at 12 26 15 PM ## Manual Testing ### Host with services - Go to the host view and find a host with services using the edge cluster, by opening the flyout of various hosts - Clicking on "show all" should take you to APM services inventory filtered by the host and showing the same services with time range persisted - Clicking on an individual service from the flyout should take you to the APM service overview with timerange persisted - Same should work on the node details ### Host with no services Screenshot 2024-02-09 at 6 02 12 PM - Go to the host view and find a host with no services using the edge cluster - Open the host flyout and there should be a message that no services were found - Same should work on the node details ### APM Services link removed The link seen below (top right) should not exist Screenshot 2024-02-09 at 5 51 11 PM --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> --- .../host_details/get_infra_services.ts | 6 +- .../context/fixtures/asset_details_props.ts | 2 +- .../components/section_titles.tsx | 13 +++ .../components/services_tooltip_content.tsx | 41 +++++++ .../asset_details/hooks/use_page_header.tsx | 3 +- .../asset_details/hooks/use_services.ts | 58 ++++++++++ .../links/link_to_apm_service.tsx | 54 +++++++++ .../links/link_to_apm_services.tsx | 6 +- .../asset_details/tabs/overview/overview.tsx | 8 +- .../asset_details/tabs/overview/services.tsx | 103 ++++++++++++++++++ .../public/components/asset_details/types.ts | 2 +- .../host_details_flyout/flyout_wrapper.tsx | 2 +- .../metric_detail/asset_detail_page.tsx | 1 - .../server/lib/host_details/get_services.ts | 34 +++++- x-pack/plugins/infra/tsconfig.json | 3 +- x-pack/test/functional/apps/infra/helpers.ts | 48 ++++++++ .../test/functional/apps/infra/hosts_view.ts | 80 +++++++++++--- .../functional/apps/infra/node_details.ts | 1 + .../functional/page_objects/asset_details.ts | 30 +++++ 19 files changed, 464 insertions(+), 31 deletions(-) create mode 100644 x-pack/plugins/infra/public/components/asset_details/components/services_tooltip_content.tsx create mode 100644 x-pack/plugins/infra/public/components/asset_details/hooks/use_services.ts create mode 100644 x-pack/plugins/infra/public/components/asset_details/links/link_to_apm_service.tsx create mode 100644 x-pack/plugins/infra/public/components/asset_details/tabs/overview/services.tsx create mode 100644 x-pack/test/functional/apps/infra/helpers.ts diff --git a/x-pack/plugins/infra/common/http_api/host_details/get_infra_services.ts b/x-pack/plugins/infra/common/http_api/host_details/get_infra_services.ts index 56b5a4757bdb2..718513416dad7 100644 --- a/x-pack/plugins/infra/common/http_api/host_details/get_infra_services.ts +++ b/x-pack/plugins/infra/common/http_api/host_details/get_infra_services.ts @@ -67,8 +67,8 @@ export const ServicesAPIQueryAggregationRT = rt.type({ export type ServicesAPIQueryAggregation = rt.TypeOf; export const ServiceRT = rt.type({ - 'service.name': rt.string, - 'agent.name': AgentNameRT, + serviceName: rt.string, + agentName: AgentNameRT, }); export type Service = rt.TypeOf; @@ -76,3 +76,5 @@ export type Service = rt.TypeOf; export const ServicesAPIResponseRT = rt.type({ services: rt.array(ServiceRT), }); + +export type ServicesAPIResponse = rt.TypeOf; diff --git a/x-pack/plugins/infra/public/components/asset_details/__stories__/context/fixtures/asset_details_props.ts b/x-pack/plugins/infra/public/components/asset_details/__stories__/context/fixtures/asset_details_props.ts index 9d6b4efb96ec2..8cca6dd66d027 100644 --- a/x-pack/plugins/infra/public/components/asset_details/__stories__/context/fixtures/asset_details_props.ts +++ b/x-pack/plugins/infra/public/components/asset_details/__stories__/context/fixtures/asset_details_props.ts @@ -8,7 +8,7 @@ import { i18n } from '@kbn/i18n'; import { type AssetDetailsProps, ContentTabIds, type Tab } from '../../../types'; -const links: AssetDetailsProps['links'] = ['alertRule', 'nodeDetails', 'apmServices']; +const links: AssetDetailsProps['links'] = ['alertRule', 'nodeDetails']; const tabs: Tab[] = [ { id: ContentTabIds.OVERVIEW, diff --git a/x-pack/plugins/infra/public/components/asset_details/components/section_titles.tsx b/x-pack/plugins/infra/public/components/asset_details/components/section_titles.tsx index b1b083bf3f0b5..7067909d56ee9 100644 --- a/x-pack/plugins/infra/public/components/asset_details/components/section_titles.tsx +++ b/x-pack/plugins/infra/public/components/asset_details/components/section_titles.tsx @@ -11,6 +11,7 @@ import { i18n } from '@kbn/i18n'; import { HostMetricsExplanationContent } from '../../lens'; import { Popover } from '../tabs/common/popover'; import { AlertsTooltipContent } from './alerts_tooltip_content'; +import { ServicesTooltipContent } from './services_tooltip_content'; const SectionTitle = ({ title, @@ -94,3 +95,15 @@ export const AlertsSectionTitle = () => { ); }; + +export const ServicesSectionTitle = () => ( + + + +); diff --git a/x-pack/plugins/infra/public/components/asset_details/components/services_tooltip_content.tsx b/x-pack/plugins/infra/public/components/asset_details/components/services_tooltip_content.tsx new file mode 100644 index 0000000000000..5efcfc1ea4f56 --- /dev/null +++ b/x-pack/plugins/infra/public/components/asset_details/components/services_tooltip_content.tsx @@ -0,0 +1,41 @@ +/* + * 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 { EuiText, EuiLink } from '@elastic/eui'; +import { RedirectAppLinks } from '@kbn/shared-ux-link-redirect-app'; +import { FormattedMessage } from '@kbn/i18n-react'; +import { useLinkProps } from '@kbn/observability-shared-plugin/public'; +import { useKibanaContextForPlugin } from '../../../hooks/use_kibana'; + +export const ServicesTooltipContent = () => { + const { services } = useKibanaContextForPlugin(); + const linkProps = useLinkProps({ + app: 'home', + hash: '/tutorial/apm', + }); + return ( + + + + + + + ), + }} + /> + + ); +}; diff --git a/x-pack/plugins/infra/public/components/asset_details/hooks/use_page_header.tsx b/x-pack/plugins/infra/public/components/asset_details/hooks/use_page_header.tsx index 905def3ab0bc0..fc4577227f615 100644 --- a/x-pack/plugins/infra/public/components/asset_details/hooks/use_page_header.tsx +++ b/x-pack/plugins/infra/public/components/asset_details/hooks/use_page_header.tsx @@ -20,7 +20,7 @@ import { usePluginConfig } from '../../../containers/plugin_config_context'; import { useKibanaContextForPlugin } from '../../../hooks/use_kibana'; import { useProfilingIntegrationSetting } from '../../../hooks/use_profiling_integration_setting'; import { APM_HOST_FILTER_FIELD } from '../constants'; -import { LinkToAlertsRule, LinkToApmServices, LinkToNodeDetails } from '../links'; +import { LinkToAlertsRule, LinkToNodeDetails } from '../links'; import { ContentTabIds, type LinkOptions, type RouteState, type Tab, type TabIds } from '../types'; import { useAssetDetailsRenderPropsContext } from './use_asset_details_render_props'; import { useTabSwitcherContext } from './use_tab_switcher'; @@ -96,7 +96,6 @@ const useRightSideItems = (links?: LinkOptions[]) => { ), alertRule: , - apmServices: , }), [asset.id, asset.name, asset.type] ); diff --git a/x-pack/plugins/infra/public/components/asset_details/hooks/use_services.ts b/x-pack/plugins/infra/public/components/asset_details/hooks/use_services.ts new file mode 100644 index 0000000000000..dae8203b37325 --- /dev/null +++ b/x-pack/plugins/infra/public/components/asset_details/hooks/use_services.ts @@ -0,0 +1,58 @@ +/* + * 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 { fold } from 'fp-ts/lib/Either'; +import { identity } from 'fp-ts/lib/function'; +import { pipe } from 'fp-ts/lib/pipeable'; +import { useEffect, useMemo } from 'react'; +import { + ServicesAPIResponse, + ServicesAPIResponseRT, + ServicesAPIRequest, +} from '../../../../common/http_api/host_details'; +import { throwErrors, createPlainError } from '../../../../common/runtime_types'; +import { useHTTPRequest } from '../../../hooks/use_http_request'; +import { useRequestObservable } from './use_request_observable'; + +export function useServices(params: ServicesAPIRequest) { + const { request$ } = useRequestObservable(); + const decodeResponse = (response: any) => { + return pipe( + ServicesAPIResponseRT.decode(response), + fold(throwErrors(createPlainError), identity) + ); + }; + const fetchOptions = useMemo( + () => ({ query: { ...params, filters: JSON.stringify(params.filters) } }), + [params] + ); + const { error, loading, response, makeRequest } = useHTTPRequest( + `/api/infra/services`, + 'GET', + undefined, + decodeResponse, + undefined, + undefined, + true, + fetchOptions + ); + + useEffect(() => { + if (request$) { + request$.next(makeRequest); + } else { + makeRequest(); + } + }, [makeRequest, request$]); + + return { + error, + loading, + response, + makeRequest, + }; +} diff --git a/x-pack/plugins/infra/public/components/asset_details/links/link_to_apm_service.tsx b/x-pack/plugins/infra/public/components/asset_details/links/link_to_apm_service.tsx new file mode 100644 index 0000000000000..378697793d90b --- /dev/null +++ b/x-pack/plugins/infra/public/components/asset_details/links/link_to_apm_service.tsx @@ -0,0 +1,54 @@ +/* + * 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 { RedirectAppLinks } from '@kbn/shared-ux-link-redirect-app'; +import { EuiBadge, EuiText } from '@elastic/eui'; +import { AgentIcon } from '@kbn/custom-icons'; +import { AgentName } from '@kbn/elastic-agent-utils'; +import { i18n } from '@kbn/i18n'; +import { useLinkProps } from '@kbn/observability-shared-plugin/public'; +import { useKibanaContextForPlugin } from '../../../hooks/use_kibana'; + +export interface LinkToApmServiceProps { + serviceName: string; + agentName: string | null; + dateRange: { from: string; to: string }; +} + +export const LinkToApmService = ({ serviceName, agentName, dateRange }: LinkToApmServiceProps) => { + const { services } = useKibanaContextForPlugin(); + + const linkProps = useLinkProps({ + app: 'apm', + pathname: `/services/${serviceName}/overview`, + search: { + rangeFrom: dateRange.from, + rangeTo: dateRange.to, + }, + }); + return ( + + + + {agentName ? ( + + ) : null} + {serviceName} + + + + ); +}; diff --git a/x-pack/plugins/infra/public/components/asset_details/links/link_to_apm_services.tsx b/x-pack/plugins/infra/public/components/asset_details/links/link_to_apm_services.tsx index 59cd4ca72ef1e..a7a70d1466ac9 100644 --- a/x-pack/plugins/infra/public/components/asset_details/links/link_to_apm_services.tsx +++ b/x-pack/plugins/infra/public/components/asset_details/links/link_to_apm_services.tsx @@ -34,14 +34,16 @@ export const LinkToApmServices = ({ assetName, apmField }: LinkToApmServicesProp return ( diff --git a/x-pack/plugins/infra/public/components/asset_details/tabs/overview/overview.tsx b/x-pack/plugins/infra/public/components/asset_details/tabs/overview/overview.tsx index 29dcdec3ddbc3..4af9aa8ddae78 100644 --- a/x-pack/plugins/infra/public/components/asset_details/tabs/overview/overview.tsx +++ b/x-pack/plugins/infra/public/components/asset_details/tabs/overview/overview.tsx @@ -22,6 +22,7 @@ import { SectionSeparator } from './section_separator'; import { MetadataErrorCallout } from '../../components/metadata_error_callout'; import { useIntersectingState } from '../../hooks/use_intersecting_state'; import { CpuProfilingPrompt } from './kpis/cpu_profiling_prompt'; +import { ServicesContent } from './services'; export const Overview = () => { const ref = useRef(null); @@ -32,7 +33,6 @@ export const Overview = () => { loading: metadataLoading, error: fetchMetadataError, } = useMetadataStateContext(); - const { logs, metrics } = useDataViewsContext(); const isFullPageView = renderMode.mode !== 'flyout'; @@ -78,6 +78,12 @@ export const Overview = () => { /> + {asset.type === 'host' ? ( + + + + + ) : null} {metricsSection} ); diff --git a/x-pack/plugins/infra/public/components/asset_details/tabs/overview/services.tsx b/x-pack/plugins/infra/public/components/asset_details/tabs/overview/services.tsx new file mode 100644 index 0000000000000..e1caec807e8f8 --- /dev/null +++ b/x-pack/plugins/infra/public/components/asset_details/tabs/overview/services.tsx @@ -0,0 +1,103 @@ +/* + * 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, { useMemo } from 'react'; +import { i18n } from '@kbn/i18n'; +import { EuiFlexGroup, EuiFlexItem, EuiLoadingSpinner, EuiCallOut, EuiLink } from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n-react'; +import type { TimeRange } from '@kbn/es-query'; +import { useLinkProps } from '@kbn/observability-shared-plugin/public'; +import { CollapsibleSection } from './section/collapsible_section'; +import { ServicesSectionTitle } from '../../components/section_titles'; +import { useServices } from '../../hooks/use_services'; +import { HOST_FIELD } from '../../../../../common/constants'; +import { LinkToApmServices } from '../../links'; +import { APM_HOST_FILTER_FIELD } from '../../constants'; +import { LinkToApmService } from '../../links/link_to_apm_service'; + +export const ServicesContent = ({ + hostName, + dateRange, +}: { + hostName: string; + dateRange: TimeRange; +}) => { + const linkProps = useLinkProps({ + app: 'home', + hash: '/tutorial/apm', + }); + const params = useMemo( + () => ({ + filters: { [HOST_FIELD]: hostName }, + from: dateRange.from, + to: dateRange.to, + }), + [hostName, dateRange.from, dateRange.to] + ); + const { error, loading, response } = useServices(params); + const services = response?.services; + const hasServices = services?.length; + + return ( + } + > + {error ? ( + + {i18n.translate('xpack.infra.assetDetails.services.getServicesRequestError', { + defaultMessage: 'An error occurred while fetching services.', + })} + + ) : loading ? ( + + ) : hasServices ? ( + + {services.map((service, index) => ( + + + + ))} + + ) : ( +

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

+ )} +
+ ); +}; diff --git a/x-pack/plugins/infra/public/components/asset_details/types.ts b/x-pack/plugins/infra/public/components/asset_details/types.ts index 346eda2bb3c32..03ae24810e217 100644 --- a/x-pack/plugins/infra/public/components/asset_details/types.ts +++ b/x-pack/plugins/infra/public/components/asset_details/types.ts @@ -63,7 +63,7 @@ export interface Tab { append?: JSX.Element; } -export type LinkOptions = 'alertRule' | 'nodeDetails' | 'apmServices'; +export type LinkOptions = 'alertRule' | 'nodeDetails'; export interface AssetDetailsProps { assetId: string; diff --git a/x-pack/plugins/infra/public/pages/metrics/hosts/components/host_details_flyout/flyout_wrapper.tsx b/x-pack/plugins/infra/public/pages/metrics/hosts/components/host_details_flyout/flyout_wrapper.tsx index c377eb5043c79..77a929988f493 100644 --- a/x-pack/plugins/infra/public/pages/metrics/hosts/components/host_details_flyout/flyout_wrapper.tsx +++ b/x-pack/plugins/infra/public/pages/metrics/hosts/components/host_details_flyout/flyout_wrapper.tsx @@ -33,7 +33,7 @@ export const FlyoutWrapper = ({ node: { name }, closeFlyout }: Props) => { }, }} tabs={commonFlyoutTabs} - links={['apmServices', 'nodeDetails']} + links={['nodeDetails']} renderMode={{ mode: 'flyout', closeFlyout, diff --git a/x-pack/plugins/infra/public/pages/metrics/metric_detail/asset_detail_page.tsx b/x-pack/plugins/infra/public/pages/metrics/metric_detail/asset_detail_page.tsx index 81f577f1d553f..7414d2ab4bf93 100644 --- a/x-pack/plugins/infra/public/pages/metrics/metric_detail/asset_detail_page.tsx +++ b/x-pack/plugins/infra/public/pages/metrics/metric_detail/asset_detail_page.tsx @@ -65,7 +65,6 @@ export const AssetDetailPage = () => { assetId={nodeId} assetType={nodeType} tabs={commonFlyoutTabs} - links={['apmServices']} renderMode={{ mode: 'page', }} diff --git a/x-pack/plugins/infra/server/lib/host_details/get_services.ts b/x-pack/plugins/infra/server/lib/host_details/get_services.ts index f2a58cf72d4de..e037889e4fece 100644 --- a/x-pack/plugins/infra/server/lib/host_details/get_services.ts +++ b/x-pack/plugins/infra/server/lib/host_details/get_services.ts @@ -8,6 +8,7 @@ import { QueryDslQueryContainer } from '@elastic/elasticsearch/lib/api/types'; import { APMDataAccessConfig } from '@kbn/apm-data-access-plugin/server'; import { termQuery } from '@kbn/observability-plugin/server'; +import { PROCESSOR_EVENT } from '@kbn/observability-shared-plugin/common/field_names/elasticsearch'; import { ESSearchClient } from '../metrics/types'; import { ServicesAPIRequest, @@ -79,12 +80,35 @@ export const getServices = async ( filter: [ { term: { - 'metricset.name': 'transaction', + [PROCESSOR_EVENT]: 'metric', }, }, { - term: { - 'metricset.interval': '1m', // make this dynamic if we start returning time series data + bool: { + should: [ + { + term: { + 'metricset.name': 'app', + }, + }, + { + bool: { + must: [ + { + term: { + 'metricset.name': 'transaction', + }, + }, + { + term: { + 'metricset.interval': '1m', // make this dynamic if we start returning time series data + }, + }, + ], + }, + }, + ], + minimum_should_match: 1, }, }, ...commonFiltersList, @@ -136,8 +160,8 @@ export const getServices = async ( const services = Array.from(serviceMap) .slice(0, size) .map(([serviceName, { agentName }]) => ({ - 'service.name': serviceName, - 'agent.name': agentName, + serviceName, + agentName, })); return { services }; }; diff --git a/x-pack/plugins/infra/tsconfig.json b/x-pack/plugins/infra/tsconfig.json index fcda9c0de77a1..5a12a83b5fd58 100644 --- a/x-pack/plugins/infra/tsconfig.json +++ b/x-pack/plugins/infra/tsconfig.json @@ -85,7 +85,8 @@ "@kbn/management-settings-components-field-row", "@kbn/core-ui-settings-browser", "@kbn/core-saved-objects-api-server", - "@kbn/securitysolution-io-ts-utils" + "@kbn/securitysolution-io-ts-utils", + "@kbn/elastic-agent-utils" ], "exclude": ["target/**/*"] } diff --git a/x-pack/test/functional/apps/infra/helpers.ts b/x-pack/test/functional/apps/infra/helpers.ts new file mode 100644 index 0000000000000..51356acadf146 --- /dev/null +++ b/x-pack/test/functional/apps/infra/helpers.ts @@ -0,0 +1,48 @@ +/* + * 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 { apm, timerange } from '@kbn/apm-synthtrace-client'; + +const SERVICE_PREFIX = 'service'; +// generates traces, metrics for services +export function generateAddServicesToExistingHost({ + from, + to, + hostName, + servicesPerHost = 1, +}: { + from: string; + to: string; + hostName: string; + servicesPerHost?: number; +}) { + const range = timerange(from, to); + const services = Array(servicesPerHost) + .fill(null) + .map((_, serviceIdx) => + apm + .service({ + name: `${SERVICE_PREFIX}-${serviceIdx}`, + environment: 'production', + agentName: 'nodejs', + }) + .instance(hostName) + ); + + return range + .interval('1m') + .rate(1) + .generator((timestamp, index) => + services.map((service) => + service + .transaction({ transactionName: 'GET /foo' }) + .timestamp(timestamp) + .duration(500) + .success() + ) + ); +} diff --git a/x-pack/test/functional/apps/infra/hosts_view.ts b/x-pack/test/functional/apps/infra/hosts_view.ts index 96a258303eb30..9742eed4d2f30 100644 --- a/x-pack/test/functional/apps/infra/hosts_view.ts +++ b/x-pack/test/functional/apps/infra/hosts_view.ts @@ -7,7 +7,14 @@ import moment from 'moment'; import expect from '@kbn/expect'; -import { parse } from 'url'; +import { + ApmSynthtraceEsClient, + ApmSynthtraceKibanaClient, + createLogger, + LogLevel, +} from '@kbn/apm-synthtrace'; +import url from 'url'; +import { kbnTestConfig } from '@kbn/test'; import { enableInfrastructureHostsView } from '@kbn/observability-plugin/common'; import { ALERT_STATUS_ACTIVE, ALERT_STATUS_RECOVERED } from '@kbn/rule-data-utils'; import { WebElementWrapper } from '@kbn/ftr-common-functional-ui-services'; @@ -18,6 +25,7 @@ import { HOSTS_VIEW_PATH, DATE_PICKER_FORMAT, } from './constants'; +import { generateAddServicesToExistingHost } from './helpers'; const START_DATE = moment.utc(DATES.metricsAndLogs.hosts.min); const END_DATE = moment.utc(DATES.metricsAndLogs.hosts.max); @@ -90,6 +98,7 @@ const tableEntries = [ export default ({ getPageObjects, getService }: FtrProviderContext) => { const browser = getService('browser'); const esArchiver = getService('esArchiver'); + const esClient = getService('es'); const find = getService('find'); const kibanaServer = getService('kibanaServer'); const observability = getService('observability'); @@ -107,6 +116,18 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { ]); // Helpers + + const getKibanaServerUrl = () => { + const kibanaServerUrl = url.format(kbnTestConfig.getUrlParts() as url.UrlObject); + const kibanaServerUrlWithAuth = url + .format({ + ...url.parse(kibanaServerUrl), + auth: `elastic:${kbnTestConfig.getUrlParts().password}`, + }) + .slice(0, -1); + return kibanaServerUrlWithAuth; + }; + const setHostViewEnabled = (value: boolean = true) => kibanaServer.uiSettings.update({ [enableInfrastructureHostsView]: value }); @@ -127,8 +148,29 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { ); describe('Hosts View', function () { + let synthtraceApmClient: any; before(async () => { + const kibanaClient = new ApmSynthtraceKibanaClient({ + target: getKibanaServerUrl(), + logger: createLogger(LogLevel.debug), + }); + const kibanaVersion = await kibanaClient.fetchLatestApmPackageVersion(); + await kibanaClient.installApmPackage(kibanaVersion); + synthtraceApmClient = new ApmSynthtraceEsClient({ + client: esClient, + logger: createLogger(LogLevel.info), + version: kibanaVersion, + refreshAfterIndex: true, + }); await Promise.all([ + synthtraceApmClient.index( + generateAddServicesToExistingHost({ + from: DATES.metricsAndLogs.hosts.processesDataStartDate, + to: DATES.metricsAndLogs.hosts.processesDataEndDate, + hostName: 'Jennys-MBP.fritz.box', + servicesPerHost: 3, + }) + ), esArchiver.load('x-pack/test/functional/es_archives/infra/alerts'), esArchiver.load('x-pack/test/functional/es_archives/infra/metrics_and_logs'), esArchiver.load('x-pack/test/functional/es_archives/infra/metrics_hosts_processes'), @@ -139,6 +181,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { after(async () => { await Promise.all([ + synthtraceApmClient.clean(), esArchiver.unload('x-pack/test/functional/es_archives/infra/alerts'), esArchiver.unload('x-pack/test/functional/es_archives/infra/metrics_and_logs'), esArchiver.unload('x-pack/test/functional/es_archives/infra/metrics_hosts_processes'), @@ -209,16 +252,37 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { expect(hosts.length).to.equal(9); }); - it('should show all section as collapsable', async () => { + it('should show all section as collapsible', async () => { await pageObjects.assetDetails.metadataSectionCollapsibleExist(); await pageObjects.assetDetails.alertsSectionCollapsibleExist(); await pageObjects.assetDetails.metricsSectionCollapsibleExist(); + await pageObjects.assetDetails.servicesSectionCollapsibleExist(); }); it('should show alerts', async () => { await pageObjects.header.waitUntilLoadingHasFinished(); await pageObjects.assetDetails.overviewAlertsTitleExists(); }); + + it('should show 3 services each with an icon, service name, and url', async () => { + await pageObjects.assetDetails.servicesSectionCollapsibleExist(); + + const services = + await pageObjects.assetDetails.getAssetDetailsServicesWithIconsAndNames(); + + expect(services.length).to.equal(3); + + const currentUrl = await browser.getCurrentUrl(); + const parsedUrl = new URL(currentUrl); + const baseUrl = `${parsedUrl.protocol}//${parsedUrl.host}`; + + services.forEach((service, index) => { + expect(service.serviceName).to.equal(`service-${index}`); + expect(service.iconSrc).to.not.be.empty(); + const expectedUrlPattern = `${baseUrl}/app/apm/services/service-${index}/overview?rangeFrom=${DATES.metricsAndLogs.hosts.processesDataStartDate}&rangeTo=${DATES.metricsAndLogs.hosts.processesDataEndDate}`; + expect(service.serviceUrl).to.equal(expectedUrlPattern); + }); + }); }); describe('Metadata Tab', () => { @@ -270,18 +334,6 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { }); describe('Flyout links', () => { - it('should navigate to APM services after click', async () => { - await pageObjects.assetDetails.clickApmServicesLink(); - const url = parse(await browser.getCurrentUrl()); - const query = decodeURIComponent(url.query ?? ''); - const kuery = 'kuery=host.hostname:"Jennys-MBP.fritz.box"'; - - expect(url.pathname).to.eql('/app/apm/services'); - expect(query).to.contain(kuery); - - await returnTo(HOSTS_VIEW_PATH); - }); - it('should navigate to Host Details page after click', async () => { await pageObjects.assetDetails.clickOpenAsPageLink(); const dateRange = await pageObjects.timePicker.getTimeConfigAsAbsoluteTimes(); diff --git a/x-pack/test/functional/apps/infra/node_details.ts b/x-pack/test/functional/apps/infra/node_details.ts index 42147652f34a5..f79ac8a00d771 100644 --- a/x-pack/test/functional/apps/infra/node_details.ts +++ b/x-pack/test/functional/apps/infra/node_details.ts @@ -197,6 +197,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { await pageObjects.assetDetails.metadataSectionCollapsibleExist(); await pageObjects.assetDetails.alertsSectionCollapsibleExist(); await pageObjects.assetDetails.metricsSectionCollapsibleExist(); + await pageObjects.assetDetails.servicesSectionCollapsibleExist(); }); it('should show alerts', async () => { diff --git a/x-pack/test/functional/page_objects/asset_details.ts b/x-pack/test/functional/page_objects/asset_details.ts index cd34d9c2ca10b..a0e13e23d7570 100644 --- a/x-pack/test/functional/page_objects/asset_details.ts +++ b/x-pack/test/functional/page_objects/asset_details.ts @@ -43,6 +43,33 @@ export function AssetDetailsProvider({ getService }: FtrProviderContext) { ); }, + async getAssetDetailsServicesWithIconsAndNames() { + await testSubjects.existOrFail('infraAssetDetailsServicesContainer'); + const container = await testSubjects.find('infraAssetDetailsServicesContainer'); + const serviceLinks = await container.findAllByCssSelector('[data-test-subj="serviceLink"]'); + + const servicesWithIconsAndNames = await Promise.all( + serviceLinks.map(async (link, index) => { + const icon = await link.findByTagName('img'); + const iconSrc = await icon.getAttribute('src'); + await testSubjects.existOrFail(`serviceNameText-service-${index}`); + const serviceElement = await link.findByCssSelector( + `[data-test-subj="serviceNameText-service-${index}"]` + ); + const serviceName = await serviceElement.getVisibleText(); + const serviceUrl = await link.getAttribute('href'); + + return { + serviceName, + serviceUrl, + iconSrc, + }; + }) + ); + + return servicesWithIconsAndNames; + }, + async getAssetDetailsKubernetesMetricsCharts() { const container = await testSubjects.find('infraAssetDetailsKubernetesMetricsChartGrid'); return container.findAllByCssSelector( @@ -85,6 +112,9 @@ export function AssetDetailsProvider({ getService }: FtrProviderContext) { async alertsSectionCollapsibleExist() { return await testSubjects.existOrFail('infraAssetDetailsAlertsCollapsible'); }, + async servicesSectionCollapsibleExist() { + return await testSubjects.existOrFail('infraAssetDetailsServicesCollapsible'); + }, async metricsSectionCollapsibleExist() { return await testSubjects.existOrFail('infraAssetDetailsMetricsCollapsible'); }, From fbc0586c8c3e526bd14533858ecc7b3d615f43cd Mon Sep 17 00:00:00 2001 From: Tiago Costa Date: Tue, 13 Feb 2024 15:57:15 +0000 Subject: [PATCH 78/83] skip flaky suite (#175204) --- .../cases/public/components/create/description.test.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/x-pack/plugins/cases/public/components/create/description.test.tsx b/x-pack/plugins/cases/public/components/create/description.test.tsx index d0426731f97d9..f3fd7342db17e 100644 --- a/x-pack/plugins/cases/public/components/create/description.test.tsx +++ b/x-pack/plugins/cases/public/components/create/description.test.tsx @@ -17,7 +17,8 @@ import { MAX_DESCRIPTION_LENGTH } from '../../../common/constants'; import { FormTestComponent } from '../../common/test_utils'; import type { FormSchema } from '@kbn/index-management-plugin/public/shared_imports'; -describe('Description', () => { +// FLAKY: https://github.com/elastic/kibana/issues/175204 +describe.skip('Description', () => { let appMockRender: AppMockRenderer; const onSubmit = jest.fn(); const draftStorageKey = `cases.caseView.createCase.description.markdownEditor`; From 2d51f0bb412894652191f2650b54295513a002f4 Mon Sep 17 00:00:00 2001 From: Tiago Costa Date: Tue, 13 Feb 2024 16:07:44 +0000 Subject: [PATCH 79/83] skip flaky suite (#176119) --- .../tests/correlations/field_candidates.spec.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/x-pack/test/apm_api_integration/tests/correlations/field_candidates.spec.ts b/x-pack/test/apm_api_integration/tests/correlations/field_candidates.spec.ts index 448f90b1228b4..1984f7f652c27 100644 --- a/x-pack/test/apm_api_integration/tests/correlations/field_candidates.spec.ts +++ b/x-pack/test/apm_api_integration/tests/correlations/field_candidates.spec.ts @@ -37,7 +37,8 @@ export default function ApiTest({ getService }: FtrProviderContext) { }); }); - registry.when( + // FLAKY: https://github.com/elastic/kibana/issues/176119 + registry.when.skip( 'field candidates with data and default args', { config: 'trial', archives: ['8.0.0'] }, () => { From a52cddc072e08a1c29d19bb6c5bcea4cb47a9b14 Mon Sep 17 00:00:00 2001 From: Tiago Costa Date: Tue, 13 Feb 2024 16:09:11 +0000 Subject: [PATCH 80/83] skip flaky suite (#175601) --- .../functional/test_suites/search/default_dataview.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/x-pack/test_serverless/functional/test_suites/search/default_dataview.ts b/x-pack/test_serverless/functional/test_suites/search/default_dataview.ts index 2beb234f688f4..0a0212ba10919 100644 --- a/x-pack/test_serverless/functional/test_suites/search/default_dataview.ts +++ b/x-pack/test_serverless/functional/test_suites/search/default_dataview.ts @@ -13,7 +13,8 @@ export default function ({ getPageObject, getService }: FtrProviderContext) { const svlCommonNavigation = getPageObject('svlCommonNavigation'); const svlCommonPage = getPageObject('svlCommonPage'); - describe('default dataView', function () { + // FLAKY: https://github.com/elastic/kibana/issues/175601 + describe.skip('default dataView', function () { // Error: expected testSubject(kbnOverviewElasticsearchGettingStarted) to exist this.tags(['failsOnMKI']); before(async () => { From 557ab3c1a50fd5c23577e0ac8cae4d19bda46bc3 Mon Sep 17 00:00:00 2001 From: Tiago Costa Date: Tue, 13 Feb 2024 16:11:53 +0000 Subject: [PATCH 81/83] skip flaky suite (#176529) --- .../assignments/assignments_serverless_essentials.cy.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/detection_alerts/assignments/assignments_serverless_essentials.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/detection_alerts/assignments/assignments_serverless_essentials.cy.ts index f8bbf2620a542..0405fb7dc5976 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/detection_alerts/assignments/assignments_serverless_essentials.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/detection_alerts/assignments/assignments_serverless_essentials.cy.ts @@ -20,7 +20,8 @@ import { loadPageAs, } from '../../../../../tasks/alert_assignments'; -describe( +// FLAKY: https://github.com/elastic/kibana/issues/176529 +describe.skip( 'Alert user assignment - Serverless Essentials', { tags: ['@serverless'], From 8a120ebfb60a89cbcbdf00422bd822dc348a63c9 Mon Sep 17 00:00:00 2001 From: Tiago Costa Date: Tue, 13 Feb 2024 16:13:19 +0000 Subject: [PATCH 82/83] skip flaky suite (#175608) --- .../functional/test_suites/security/ftr/navigation.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/x-pack/test_serverless/functional/test_suites/security/ftr/navigation.ts b/x-pack/test_serverless/functional/test_suites/security/ftr/navigation.ts index 69bc9d517e7e8..fc75dab7160f5 100644 --- a/x-pack/test_serverless/functional/test_suites/security/ftr/navigation.ts +++ b/x-pack/test_serverless/functional/test_suites/security/ftr/navigation.ts @@ -19,7 +19,8 @@ export default function ({ getPageObject, getService }: FtrProviderContext) { const headerPage = getPageObject('header'); const retry = getService('retry'); - describe('navigation', function () { + // FLAKY: https://github.com/elastic/kibana/issues/175608 + describe.skip('navigation', function () { before(async () => { await svlCommonPage.login(); await svlSecNavigation.navigateToLandingPage(); From c6b1c75f931730afba5b1f7138229ba13e5f85c3 Mon Sep 17 00:00:00 2001 From: Tiago Costa Date: Tue, 13 Feb 2024 16:16:00 +0000 Subject: [PATCH 83/83] skip flaky suite (#176643) --- .../public/components/user_profiles/user_tooltip.test.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/x-pack/plugins/cases/public/components/user_profiles/user_tooltip.test.tsx b/x-pack/plugins/cases/public/components/user_profiles/user_tooltip.test.tsx index d105cc1db0053..5fdf94f96c266 100644 --- a/x-pack/plugins/cases/public/components/user_profiles/user_tooltip.test.tsx +++ b/x-pack/plugins/cases/public/components/user_profiles/user_tooltip.test.tsx @@ -10,7 +10,8 @@ import { render, screen, fireEvent, waitFor } from '@testing-library/react'; import React from 'react'; import { UserToolTip } from './user_tooltip'; -describe('UserToolTip', () => { +// FLAKY: https://github.com/elastic/kibana/issues/176643 +describe.skip('UserToolTip', () => { it('renders the tooltip when hovering', async () => { const profile: UserProfileWithAvatar = { uid: '1',