From 47f0eb8803eff1652e95af8a5fb6b5168dd1eba7 Mon Sep 17 00:00:00 2001 From: Walter Rafelsberger Date: Tue, 18 Apr 2023 17:01:36 +0200 Subject: [PATCH 01/78] [ML] `@kbn/ml-random-sampler-utils`: Fix random sampler threshold (#154803) Fixes the check `isValidProbability` to include `0.5`. --- .../src/random_sampler_wrapper.test.ts | 18 ++++++++++++------ .../src/random_sampler_wrapper.ts | 2 +- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/x-pack/packages/ml/random_sampler_utils/src/random_sampler_wrapper.test.ts b/x-pack/packages/ml/random_sampler_utils/src/random_sampler_wrapper.test.ts index 58ab666b967ee..2a6a01b23cb8d 100644 --- a/x-pack/packages/ml/random_sampler_utils/src/random_sampler_wrapper.test.ts +++ b/x-pack/packages/ml/random_sampler_utils/src/random_sampler_wrapper.test.ts @@ -14,22 +14,28 @@ describe('createRandomSamplerWrapper', () => { }, }; - const wrappedTestAggs = { + const getWrappedTestAggs = (probability: number) => ({ sample: { random_sampler: { - probability: 0.01, + probability, }, aggs: testAggs, }, - }; + }); it('returns the un-sampled aggregation as-is for a probability of 1', () => { expect(createRandomSamplerWrapper({ probability: 1 }).wrap(testAggs)).toEqual(testAggs); }); + it('returns wrapped random sampler aggregation for probability of 0.5', () => { + expect(createRandomSamplerWrapper({ probability: 0.5 }).wrap(testAggs)).toEqual( + getWrappedTestAggs(0.5) + ); + }); + it('returns wrapped random sampler aggregation for probability of 0.01', () => { expect(createRandomSamplerWrapper({ probability: 0.01 }).wrap(testAggs)).toEqual( - wrappedTestAggs + getWrappedTestAggs(0.01) ); }); @@ -39,9 +45,9 @@ describe('createRandomSamplerWrapper', () => { expect(randomSamplerWrapper.wrap(testAggs)).toEqual(testAggs); }); - it('returns probability of 0.01 and does not wrap when used for 5000000 docs', () => { + it('returns probability of 0.01 and does wrap when used for 5000000 docs', () => { const randomSamplerWrapper = createRandomSamplerWrapper({ totalNumDocs: 5000000 }); expect(randomSamplerWrapper.probability).toBe(0.01); - expect(randomSamplerWrapper.wrap(testAggs)).toEqual(wrappedTestAggs); + expect(randomSamplerWrapper.wrap(testAggs)).toEqual(getWrappedTestAggs(0.01)); }); }); diff --git a/x-pack/packages/ml/random_sampler_utils/src/random_sampler_wrapper.ts b/x-pack/packages/ml/random_sampler_utils/src/random_sampler_wrapper.ts index b6f51a901ea36..8912dc5470521 100644 --- a/x-pack/packages/ml/random_sampler_utils/src/random_sampler_wrapper.ts +++ b/x-pack/packages/ml/random_sampler_utils/src/random_sampler_wrapper.ts @@ -40,7 +40,7 @@ type RandomSamplerOptions = RandomSamplerOptionProbability | RandomSamplerOption * @returns {boolean} */ export function isValidProbability(p: unknown): p is number { - return typeof p === 'number' && p > 0 && p < 0.5; + return typeof p === 'number' && p > 0 && p <= 0.5; } /** From 9ab0c454541526b1f1dffee28be3a97b16da7826 Mon Sep 17 00:00:00 2001 From: Pablo Machado Date: Tue, 18 Apr 2023 17:01:53 +0200 Subject: [PATCH 02/78] [Security Solutions] Create a new User Details Flyout (#154310) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit issue: https://github.com/elastic/security-team/issues/6154 ## Summary Screenshot 2023-04-11 at 12 50 53Screenshot 2023-04-11 at 12 51 09 Screenshot 2023-04-11 at 12 51 01Screenshot 2023-04-11 at 12 47 21 ### Main changes * Creates a new user details flyout displayed on the Alerts page and timeline. * Introduce a new experimental feature `newUserDetailsFlyout` (disabled by default) * Create `managedUserDetails` API which fetches data from the index created by the Azure integration. ### Miscellaneous * Delete unused `lastSeen` and `first_seen` types. * Delete unused `jobKey`property from anomaly score components * Rename `userDetails` API and hook to `observedUserDetails`. * Add `filterQuery` property to `useFirstLastSeen `. * To use it inside the flyout, since the user flyout show data in the time range. * Create a simplified `TestProvidersComponent` for Storybook named `StorybookProviders` * It should allow us to render more complex components that require access to the redux store, theme, and kibana context. * Add `experimentalFeatures` property to `queryFactory.buildDsl`. ### Out of scope: * The user can Snooze or Dismiss this prompt. * Displaying integration errors inside the flyout * User page ## Storybook Please check the "💚 Build Succeeded" message ## How to test it * You need a kibana instance with user data and alerts * Enable the experimental feature `newUserDetailsFlyout` * Go to the alerts page or timeline * Open the user flyout ## How to install the new Azure integration _The integration is under development, so you have to follow a series of steps:_ 1. Install docker desktop for Mac (only for macOS) 2. Install elastic-package https://github.com/elastic/elastic-package 3. Add elastic-package to $PATH 4. Download the integration source code from GitHub branch https://github.com/taylor-swanson/integrations/tree/entityanalytics_azure 5. Start the local K8 cluster `elastic-package stack up -vd --version 8.8.0-SNAPSHOT` 6. Enter the integration folder `cd packages/entityanalytics_azure/` 7. Build the integration `elastic-package build` 8. Update the registry with the new integration build `elastic-package stack up -vd --services package-registry` 9. Open kibana integrations Screenshot 2023-04-11 at 11 24 14 10. Find entity analytics Azure integration (you need to check the 'display beta integrations' box) Screenshot 2023-04-11 at 11 24 29 11. Configured the integration with Azure tenant id, application id, and secret (ask @machadoum) 12. Configured the integration with login URL, Login scopes, and API URL (ask @machadoum) ### Checklist Delete any items that are not applicable to this PR. - [x] Any text added follows [EUI's writing guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses sentence case text and includes [i18n support](https://github.com/elastic/kibana/blob/main/packages/kbn-i18n/README.md) - [x] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios - [x] Any UI touched in this PR is usable by keyboard only (learn more about [keyboard accessibility](https://webaim.org/techniques/keyboard/)) [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> --- .../common/experimental_features.ts | 6 + .../security_solution/index.ts | 21 +- .../security_solution/users/common/index.ts | 14 +- .../security_solution/users/index.ts | 7 +- .../users/managed_details/index.ts | 33 +++ .../{details => observed_details}/index.ts | 4 +- .../ml/anomaly/anomaly_table_provider.tsx | 4 +- .../__snapshots__/anomaly_score.test.tsx.snap | 1 + .../anomaly_scores.test.tsx.snap | 3 +- .../ml/score/anomaly_score.test.tsx | 3 - .../components/ml/score/anomaly_score.tsx | 4 +- .../components/ml/score/anomaly_scores.tsx | 3 +- .../use_first_last_seen.test.ts | 19 ++ .../use_first_last_seen.tsx | 6 +- .../common/mock/storybook_providers.tsx | 68 +++++ .../stat_items/use_kpi_matrix_status.ts | 4 +- .../explore/components/stat_items/utils.tsx | 8 +- .../components/kpi_hosts/common/index.tsx | 4 +- .../index.test.tsx | 8 +- .../{details => observed_details}/index.tsx | 19 +- .../translations.ts | 0 .../explore/users/pages/details/index.tsx | 4 +- .../components/user_entity_overview.test.tsx | 7 +- .../right/components/user_entity_overview.tsx | 4 +- .../timelines/components/side_panel/index.tsx | 6 + .../new_user_detail/__mocks__/index.ts | 105 ++++++++ .../side_panel/new_user_detail/columns.tsx | 133 ++++++++++ .../side_panel/new_user_detail/constants.ts | 12 + .../side_panel/new_user_detail/hooks.test.ts | 61 +++++ .../side_panel/new_user_detail/hooks.ts | 236 ++++++++++++++++++ .../new_user_detail/managed_user.test.tsx | 81 ++++++ .../new_user_detail/managed_user.tsx | 157 ++++++++++++ .../new_user_detail/observed_user.test.tsx | 63 +++++ .../new_user_detail/observed_user.tsx | 119 +++++++++ .../new_user_detail/risk_score_field.test.tsx | 46 ++++ .../new_user_detail/risk_score_field.tsx | 68 +++++ .../new_user_detail/translations.ts | 222 ++++++++++++++++ .../side_panel/new_user_detail/types.ts | 53 ++++ .../user_details_content.stories.tsx | 157 ++++++++++++ .../user_details_content.test.tsx | 130 ++++++++++ .../new_user_detail/user_details_content.tsx | 182 ++++++++++++++ .../user_details/expandable_user.tsx | 4 +- .../side_panel/user_details/index.tsx | 27 ++ .../side_panel/user_details/translations.ts | 15 ++ .../side_panel/user_details/types.ts | 1 + .../query.first_or_last_seen.dsl.ts | 4 +- .../factory/users/all/index.ts | 1 - .../security_solution/factory/users/index.ts | 6 +- .../__snapshots__/index.test.tsx.snap | 173 +++++++++++++ ...uery.managed_user_details.dsl.test.ts.snap | 36 +++ .../users/managed_details/index.test.tsx | 112 +++++++++ .../factory/users/managed_details/index.ts | 40 +++ .../query.managed_user_details.dsl.test.ts | 20 ++ .../query.managed_user_details.dsl.ts | 31 +++ .../__mocks__/index.ts | 16 +- .../__snapshots__/index.test.tsx.snap | 29 ++- ...ry.observed_user_details.dsl.test.ts.snap} | 19 ++ .../helper.test.ts | 4 - .../{details => observed_details}/helpers.ts | 7 +- .../index.test.tsx | 14 +- .../{details => observed_details}/index.ts | 18 +- .../query.observed_user_details.dsl.test.ts} | 4 +- .../query.observed_user_details.dsl.ts} | 12 +- .../plugins/security_solution/tsconfig.json | 3 +- 64 files changed, 2565 insertions(+), 116 deletions(-) create mode 100644 x-pack/plugins/security_solution/common/search_strategy/security_solution/users/managed_details/index.ts rename x-pack/plugins/security_solution/common/search_strategy/security_solution/users/{details => observed_details}/index.ts (78%) create mode 100644 x-pack/plugins/security_solution/public/common/mock/storybook_providers.tsx rename x-pack/plugins/security_solution/public/explore/users/containers/users/{details => observed_details}/index.test.tsx (88%) rename x-pack/plugins/security_solution/public/explore/users/containers/users/{details => observed_details}/index.tsx (71%) rename x-pack/plugins/security_solution/public/explore/users/containers/users/{details => observed_details}/translations.ts (100%) create mode 100644 x-pack/plugins/security_solution/public/timelines/components/side_panel/new_user_detail/__mocks__/index.ts create mode 100644 x-pack/plugins/security_solution/public/timelines/components/side_panel/new_user_detail/columns.tsx create mode 100644 x-pack/plugins/security_solution/public/timelines/components/side_panel/new_user_detail/constants.ts create mode 100644 x-pack/plugins/security_solution/public/timelines/components/side_panel/new_user_detail/hooks.test.ts create mode 100644 x-pack/plugins/security_solution/public/timelines/components/side_panel/new_user_detail/hooks.ts create mode 100644 x-pack/plugins/security_solution/public/timelines/components/side_panel/new_user_detail/managed_user.test.tsx create mode 100644 x-pack/plugins/security_solution/public/timelines/components/side_panel/new_user_detail/managed_user.tsx create mode 100644 x-pack/plugins/security_solution/public/timelines/components/side_panel/new_user_detail/observed_user.test.tsx create mode 100644 x-pack/plugins/security_solution/public/timelines/components/side_panel/new_user_detail/observed_user.tsx create mode 100644 x-pack/plugins/security_solution/public/timelines/components/side_panel/new_user_detail/risk_score_field.test.tsx create mode 100644 x-pack/plugins/security_solution/public/timelines/components/side_panel/new_user_detail/risk_score_field.tsx create mode 100644 x-pack/plugins/security_solution/public/timelines/components/side_panel/new_user_detail/translations.ts create mode 100644 x-pack/plugins/security_solution/public/timelines/components/side_panel/new_user_detail/types.ts create mode 100644 x-pack/plugins/security_solution/public/timelines/components/side_panel/new_user_detail/user_details_content.stories.tsx create mode 100644 x-pack/plugins/security_solution/public/timelines/components/side_panel/new_user_detail/user_details_content.test.tsx create mode 100644 x-pack/plugins/security_solution/public/timelines/components/side_panel/new_user_detail/user_details_content.tsx create mode 100644 x-pack/plugins/security_solution/public/timelines/components/side_panel/user_details/translations.ts create mode 100644 x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/managed_details/__snapshots__/index.test.tsx.snap create mode 100644 x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/managed_details/__snapshots__/query.managed_user_details.dsl.test.ts.snap create mode 100644 x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/managed_details/index.test.tsx create mode 100644 x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/managed_details/index.ts create mode 100644 x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/managed_details/query.managed_user_details.dsl.test.ts create mode 100644 x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/managed_details/query.managed_user_details.dsl.ts rename x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/{details => observed_details}/__mocks__/index.ts (91%) rename x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/{details => observed_details}/__snapshots__/index.test.tsx.snap (94%) rename x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/{details/__snapshots__/query.user_details.dsl.test.ts.snap => observed_details/__snapshots__/query.observed_user_details.dsl.test.ts.snap} (85%) rename x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/{details => observed_details}/helper.test.ts (85%) rename x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/{details => observed_details}/helpers.ts (86%) rename x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/{details => observed_details}/index.test.tsx (59%) rename x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/{details => observed_details}/index.ts (64%) rename x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/{details/query.user_details.dsl.test.ts => observed_details/query.observed_user_details.dsl.test.ts} (71%) rename x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/{details/query.user_details.dsl.ts => observed_details/query.observed_user_details.dsl.ts} (65%) diff --git a/x-pack/plugins/security_solution/common/experimental_features.ts b/x-pack/plugins/security_solution/common/experimental_features.ts index c0020b822b760..731d9bfc1177f 100644 --- a/x-pack/plugins/security_solution/common/experimental_features.ts +++ b/x-pack/plugins/security_solution/common/experimental_features.ts @@ -123,6 +123,12 @@ export const allowedExperimentalValues = Object.freeze({ * **/ alertsPageFiltersEnabled: true, + + /* + * Enables the new user details flyout displayed on the Alerts page and timeline. + * + **/ + newUserDetailsFlyout: false, }); type ExperimentalConfigKeys = Array; diff --git a/x-pack/plugins/security_solution/common/search_strategy/security_solution/index.ts b/x-pack/plugins/security_solution/common/search_strategy/security_solution/index.ts index 3153f5546ed91..399f0a6db1007 100644 --- a/x-pack/plugins/security_solution/common/search_strategy/security_solution/index.ts +++ b/x-pack/plugins/security_solution/common/search_strategy/security_solution/index.ts @@ -75,7 +75,10 @@ import type { RiskScoreRequestOptions, } from './risk_score'; import type { UsersQueries } from './users'; -import type { UserDetailsRequestOptions, UserDetailsStrategyResponse } from './users/details'; +import type { + ObservedUserDetailsRequestOptions, + ObservedUserDetailsStrategyResponse, +} from './users/observed_details'; import type { TotalUsersKpiRequestOptions, TotalUsersKpiStrategyResponse, @@ -96,6 +99,10 @@ import type { FirstLastSeenRequestOptions, FirstLastSeenStrategyResponse, } from './first_last_seen'; +import type { + ManagedUserDetailsRequestOptions, + ManagedUserDetailsStrategyResponse, +} from './users/managed_details'; export * from './cti'; export * from './hosts'; @@ -144,8 +151,10 @@ export type StrategyResponseType = T extends HostsQ ? HostsKpiHostsStrategyResponse : T extends HostsKpiQueries.kpiUniqueIps ? HostsKpiUniqueIpsStrategyResponse - : T extends UsersQueries.details - ? UserDetailsStrategyResponse + : T extends UsersQueries.observedDetails + ? ObservedUserDetailsStrategyResponse + : T extends UsersQueries.managedDetails + ? ManagedUserDetailsStrategyResponse : T extends UsersQueries.kpiTotalUsers ? TotalUsersKpiStrategyResponse : T extends UsersQueries.authentications @@ -210,8 +219,10 @@ export type StrategyRequestType = T extends HostsQu ? HostsKpiUniqueIpsRequestOptions : T extends UsersQueries.authentications ? UserAuthenticationsRequestOptions - : T extends UsersQueries.details - ? UserDetailsRequestOptions + : T extends UsersQueries.observedDetails + ? ObservedUserDetailsRequestOptions + : T extends UsersQueries.managedDetails + ? ManagedUserDetailsRequestOptions : T extends UsersQueries.kpiTotalUsers ? TotalUsersKpiRequestOptions : T extends UsersQueries.users diff --git a/x-pack/plugins/security_solution/common/search_strategy/security_solution/users/common/index.ts b/x-pack/plugins/security_solution/common/search_strategy/security_solution/users/common/index.ts index c281ae45cf55f..9f526309e2c5a 100644 --- a/x-pack/plugins/security_solution/common/search_strategy/security_solution/users/common/index.ts +++ b/x-pack/plugins/security_solution/common/search_strategy/security_solution/users/common/index.ts @@ -18,8 +18,6 @@ export interface UserRiskScoreItem { export interface UserItem { user?: Maybe; host?: Maybe; - lastSeen?: Maybe; - firstSeen?: Maybe; } export type SortableUsersFields = Exclude; @@ -27,9 +25,9 @@ export type SortableUsersFields = Exclude; export enum UsersFields { - lastSeen = 'lastSeen', name = 'name', domain = 'domain', + lastSeen = 'lastSeen', } export interface UserAggEsItem { @@ -39,8 +37,6 @@ export interface UserAggEsItem { host_os_name?: UserBuckets; host_ip?: UserBuckets; host_os_family?: UserBuckets; - first_seen?: { value_as_string: string }; - last_seen?: { value_as_string: string }; } export interface UserBuckets { @@ -68,3 +64,11 @@ interface UsersDomainHitsItem { }>; }; } + +export const EVENT_KIND_ASSET_FILTER = { term: { 'event.kind': 'asset' } }; + +export const NOT_EVENT_KIND_ASSET_FILTER = { + bool: { + must_not: [EVENT_KIND_ASSET_FILTER], + }, +}; diff --git a/x-pack/plugins/security_solution/common/search_strategy/security_solution/users/index.ts b/x-pack/plugins/security_solution/common/search_strategy/security_solution/users/index.ts index c83b0bcbb3e41..9683b71babf7a 100644 --- a/x-pack/plugins/security_solution/common/search_strategy/security_solution/users/index.ts +++ b/x-pack/plugins/security_solution/common/search_strategy/security_solution/users/index.ts @@ -10,15 +10,16 @@ import type { TotalUsersKpiStrategyResponse } from './kpi/total_users'; export * from './all'; export * from './common'; export * from './kpi'; -export * from './details'; +export * from './observed_details'; export * from './authentications'; export enum UsersQueries { - details = 'userDetails', + observedDetails = 'observedUserDetails', + managedDetails = 'managedUserDetails', kpiTotalUsers = 'usersKpiTotalUsers', users = 'allUsers', authentications = 'authentications', kpiAuthentications = 'usersKpiAuthentications', } -export type UserskKpiStrategyResponse = Omit; +export type UsersKpiStrategyResponse = Omit; diff --git a/x-pack/plugins/security_solution/common/search_strategy/security_solution/users/managed_details/index.ts b/x-pack/plugins/security_solution/common/search_strategy/security_solution/users/managed_details/index.ts new file mode 100644 index 0000000000000..06fdac65bc155 --- /dev/null +++ b/x-pack/plugins/security_solution/common/search_strategy/security_solution/users/managed_details/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 { IEsSearchRequest, IEsSearchResponse } from '@kbn/data-plugin/common'; +import type { EcsBase, EcsEvent, EcsHost, EcsUser, EcsAgent } from '@kbn/ecs'; +import type { Inspect, Maybe } from '../../../common'; +import type { RequestBasicOptions } from '../..'; + +export interface ManagedUserDetailsStrategyResponse extends IEsSearchResponse { + userDetails?: AzureManagedUser; + inspect?: Maybe; +} + +export interface ManagedUserDetailsRequestOptions + extends Pick, + IEsSearchRequest { + userName: string; +} + +export interface AzureManagedUser extends Pick { + agent: EcsAgent; + host: EcsHost; + event: EcsEvent; + user: EcsUser & { + last_name?: string; + first_name?: string; + phone?: string[]; + }; +} diff --git a/x-pack/plugins/security_solution/common/search_strategy/security_solution/users/details/index.ts b/x-pack/plugins/security_solution/common/search_strategy/security_solution/users/observed_details/index.ts similarity index 78% rename from x-pack/plugins/security_solution/common/search_strategy/security_solution/users/details/index.ts rename to x-pack/plugins/security_solution/common/search_strategy/security_solution/users/observed_details/index.ts index e1a7255f71195..a8c74932b0492 100644 --- a/x-pack/plugins/security_solution/common/search_strategy/security_solution/users/details/index.ts +++ b/x-pack/plugins/security_solution/common/search_strategy/security_solution/users/observed_details/index.ts @@ -11,12 +11,12 @@ import type { Inspect, Maybe, TimerangeInput } from '../../../common'; import type { UserItem } from '../common'; import type { RequestBasicOptions } from '../..'; -export interface UserDetailsStrategyResponse extends IEsSearchResponse { +export interface ObservedUserDetailsStrategyResponse extends IEsSearchResponse { userDetails: UserItem; inspect?: Maybe; } -export interface UserDetailsRequestOptions extends Partial { +export interface ObservedUserDetailsRequestOptions extends Partial { userName: string; skip?: boolean; timerange: TimerangeInput; diff --git a/x-pack/plugins/security_solution/public/common/components/ml/anomaly/anomaly_table_provider.tsx b/x-pack/plugins/security_solution/public/common/components/ml/anomaly/anomaly_table_provider.tsx index 6fb2e807ccae1..fdea3d011c623 100644 --- a/x-pack/plugins/security_solution/public/common/components/ml/anomaly/anomaly_table_provider.tsx +++ b/x-pack/plugins/security_solution/public/common/components/ml/anomaly/anomaly_table_provider.tsx @@ -10,7 +10,7 @@ import { useInstalledSecurityJobNameById } from '../hooks/use_installed_security import type { InfluencerInput, Anomalies, CriteriaFields } from '../types'; import { useAnomaliesTableData } from './use_anomalies_table_data'; -interface ChildrenArgs { +export interface AnomalyTableProviderChildrenProps { isLoadingAnomaliesData: boolean; anomaliesData: Anomalies | null; jobNameById: Record; @@ -21,7 +21,7 @@ interface Props { startDate: string; endDate: string; criteriaFields?: CriteriaFields[]; - children: (args: ChildrenArgs) => React.ReactNode; + children: (args: AnomalyTableProviderChildrenProps) => React.ReactNode; skip: boolean; } diff --git a/x-pack/plugins/security_solution/public/common/components/ml/score/__snapshots__/anomaly_score.test.tsx.snap b/x-pack/plugins/security_solution/public/common/components/ml/score/__snapshots__/anomaly_score.test.tsx.snap index eb1247dded8a0..60f35052b11f4 100644 --- a/x-pack/plugins/security_solution/public/common/components/ml/score/__snapshots__/anomaly_score.test.tsx.snap +++ b/x-pack/plugins/security_solution/public/common/components/ml/score/__snapshots__/anomaly_score.test.tsx.snap @@ -3,6 +3,7 @@ exports[`anomaly_scores renders correctly against snapshot 1`] = ` @@ -10,7 +11,6 @@ exports[`anomaly_scores renders correctly against snapshot 1`] = ` endDate="3000-01-01T00:00:00.000Z" index={0} interval="day" - jobKey="job-1-16.193669439507826-process.name-du" jobName="job-1" key="job-1-16.193669439507826-process.name-du" narrowDateRange={[MockFunction]} @@ -86,7 +86,6 @@ exports[`anomaly_scores renders correctly against snapshot 1`] = ` endDate="3000-01-01T00:00:00.000Z" index={1} interval="day" - jobKey="job-2-16.193669439507826-process.name-ls" jobName="job-2" key="job-2-16.193669439507826-process.name-ls" narrowDateRange={[MockFunction]} diff --git a/x-pack/plugins/security_solution/public/common/components/ml/score/anomaly_score.test.tsx b/x-pack/plugins/security_solution/public/common/components/ml/score/anomaly_score.test.tsx index 4a639da633021..4bdbbdca8bdca 100644 --- a/x-pack/plugins/security_solution/public/common/components/ml/score/anomaly_score.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/ml/score/anomaly_score.test.tsx @@ -34,7 +34,6 @@ describe('anomaly_scores', () => { test('renders correctly against snapshot', () => { const wrapper = shallow( { const wrapper = mount( { const wrapper = mount( - + diff --git a/x-pack/plugins/security_solution/public/common/components/ml/score/anomaly_scores.tsx b/x-pack/plugins/security_solution/public/common/components/ml/score/anomaly_scores.tsx index 3cbdec141e221..919e347f53aca 100644 --- a/x-pack/plugins/security_solution/public/common/components/ml/score/anomaly_scores.tsx +++ b/x-pack/plugins/security_solution/public/common/components/ml/score/anomaly_scores.tsx @@ -41,13 +41,12 @@ export const AnomalyScoresComponent = ({ } else { return ( <> - + {getTopSeverityJobs(anomalies.anomalies, limit).map((score, index) => { const jobKey = createJobKey(score); return ( { const { result } = renderUseFirstLastSeen({ order: Direction.desc }); expect(result.current).toEqual([false, { errorMessage: `Error: ${msg}` }]); }); + + it('should search with given filter query', () => { + const filterQuery = { terms: { test: ['test123'] } }; + mockUseSearchStrategy.mockImplementation(() => ({ + loading: false, + result: {}, + search: mockSearch, + refetch: jest.fn(), + inspect: {}, + })); + + renderUseFirstLastSeen({ order: Direction.desc, filterQuery }); + + expect(mockSearch).toHaveBeenCalledWith( + expect.objectContaining({ + filterQuery, + }) + ); + }); }); diff --git a/x-pack/plugins/security_solution/public/common/containers/use_first_last_seen/use_first_last_seen.tsx b/x-pack/plugins/security_solution/public/common/containers/use_first_last_seen/use_first_last_seen.tsx index 81bbf4984c2cb..1eccfd125e2c9 100644 --- a/x-pack/plugins/security_solution/public/common/containers/use_first_last_seen/use_first_last_seen.tsx +++ b/x-pack/plugins/security_solution/public/common/containers/use_first_last_seen/use_first_last_seen.tsx @@ -11,6 +11,7 @@ import * as i18n from './translations'; import { useSearchStrategy } from '../use_search_strategy'; import { FirstLastSeenQuery } from '../../../../common/search_strategy'; import type { Direction } from '../../../../common/search_strategy'; +import type { ESQuery } from '../../../../common/typed_json'; export interface FirstLastSeenArgs { errorMessage: string | null; @@ -22,6 +23,7 @@ export interface UseFirstLastSeen { value: string; order: Direction.asc | Direction.desc; defaultIndex: string[]; + filterQuery?: ESQuery | string; } export const useFirstLastSeen = ({ @@ -29,6 +31,7 @@ export const useFirstLastSeen = ({ value, order, defaultIndex, + filterQuery, }: UseFirstLastSeen): [boolean, FirstLastSeenArgs] => { const { loading, result, search, error } = useSearchStrategy({ factoryQueryType: FirstLastSeenQuery, @@ -46,8 +49,9 @@ export const useFirstLastSeen = ({ field, value, order, + filterQuery, }); - }, [defaultIndex, field, value, order, search]); + }, [defaultIndex, field, value, order, search, filterQuery]); const setFirstLastSeenResponse: FirstLastSeenArgs = useMemo( () => ({ diff --git a/x-pack/plugins/security_solution/public/common/mock/storybook_providers.tsx b/x-pack/plugins/security_solution/public/common/mock/storybook_providers.tsx new file mode 100644 index 0000000000000..dc92b1892541d --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/mock/storybook_providers.tsx @@ -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 { euiLightVars } from '@kbn/ui-theme'; +import React from 'react'; +import { Provider as ReduxStoreProvider } from 'react-redux'; +import { BehaviorSubject, Subject } from 'rxjs'; +import { ThemeProvider } from 'styled-components'; +import type { CoreStart } from '@kbn/core/public'; +import { createKibanaReactContext } from '@kbn/kibana-react-plugin/public'; +import { I18nProvider } from '@kbn/i18n-react'; +import { CellActionsProvider } from '@kbn/cell-actions'; +import { createStore } from '../store'; +import { mockGlobalState } from './global_state'; +import { SUB_PLUGINS_REDUCER } from './utils'; +import { createSecuritySolutionStorageMock } from './mock_local_storage'; +import type { StartServices } from '../../types'; + +export const kibanaObservable = new BehaviorSubject({} as unknown as StartServices); + +const { storage } = createSecuritySolutionStorageMock(); + +const uiSettings = { + get: (setting: string) => { + switch (setting) { + case 'dateFormat': + return 'MMM D, YYYY @ HH:mm:ss.SSS'; + case 'dateFormat:scaled': + return [['', 'HH:mm:ss.SSS']]; + } + }, + get$: () => new Subject(), +}; + +const coreMock = { + application: { + getUrlForApp: () => {}, + }, + uiSettings, +} as unknown as CoreStart; + +const KibanaReactContext = createKibanaReactContext(coreMock); + +/** + * A utility for wrapping components in Storybook that provides access to the most common React contexts used by security components. + * It is a simplified version of TestProvidersComponent. + * To reuse TestProvidersComponent here, we need to remove all references to jest from mocks. + */ +export const StorybookProviders: React.FC = ({ children }) => { + const store = createStore(mockGlobalState, SUB_PLUGINS_REDUCER, kibanaObservable, storage); + return ( + + + Promise.resolve([])}> + + ({ eui: euiLightVars, darkMode: false })}> + {children} + + + + + + ); +}; diff --git a/x-pack/plugins/security_solution/public/explore/components/stat_items/use_kpi_matrix_status.ts b/x-pack/plugins/security_solution/public/explore/components/stat_items/use_kpi_matrix_status.ts index 218f8dbe77409..9ddeed04b0786 100644 --- a/x-pack/plugins/security_solution/public/explore/components/stat_items/use_kpi_matrix_status.ts +++ b/x-pack/plugins/security_solution/public/explore/components/stat_items/use_kpi_matrix_status.ts @@ -7,7 +7,7 @@ import type { HostsKpiStrategyResponse, NetworkKpiStrategyResponse, - UserskKpiStrategyResponse, + UsersKpiStrategyResponse, } from '../../../../common/search_strategy'; import type { UpdateDateRange } from '../../../common/components/charts/common'; import type { StatItems, StatItemsProps } from './types'; @@ -15,7 +15,7 @@ import { addValueToAreaChart, addValueToBarChart, addValueToFields } from './uti export const useKpiMatrixStatus = ( mappings: Readonly, - data: HostsKpiStrategyResponse | NetworkKpiStrategyResponse | UserskKpiStrategyResponse, + data: HostsKpiStrategyResponse | NetworkKpiStrategyResponse | UsersKpiStrategyResponse, id: string, from: string, to: string, diff --git a/x-pack/plugins/security_solution/public/explore/components/stat_items/utils.tsx b/x-pack/plugins/security_solution/public/explore/components/stat_items/utils.tsx index 62fbbdc393f67..c82ac34eee3b5 100644 --- a/x-pack/plugins/security_solution/public/explore/components/stat_items/utils.tsx +++ b/x-pack/plugins/security_solution/public/explore/components/stat_items/utils.tsx @@ -14,7 +14,7 @@ import { EuiFlexGroup, EuiFlexItem, EuiTitle } from '@elastic/eui'; import type { HostsKpiStrategyResponse, NetworkKpiStrategyResponse, - UserskKpiStrategyResponse, + UsersKpiStrategyResponse, } from '../../../../common/search_strategy'; import type { ChartSeriesData, ChartData } from '../../../common/components/charts/common'; @@ -95,12 +95,12 @@ export const barchartConfigs = (config?: { onElementClick?: ElementClickListener export const addValueToFields = ( fields: StatItem[], - data: HostsKpiStrategyResponse | NetworkKpiStrategyResponse | UserskKpiStrategyResponse + data: HostsKpiStrategyResponse | NetworkKpiStrategyResponse | UsersKpiStrategyResponse ): StatItem[] => fields.map((field) => ({ ...field, value: get(field.key, data) })); export const addValueToAreaChart = ( fields: StatItem[], - data: HostsKpiStrategyResponse | NetworkKpiStrategyResponse | UserskKpiStrategyResponse + data: HostsKpiStrategyResponse | NetworkKpiStrategyResponse | UsersKpiStrategyResponse ): ChartSeriesData[] => fields .filter((field) => get(`${field.key}Histogram`, data) != null) @@ -112,7 +112,7 @@ export const addValueToAreaChart = ( export const addValueToBarChart = ( fields: StatItem[], - data: HostsKpiStrategyResponse | NetworkKpiStrategyResponse | UserskKpiStrategyResponse + data: HostsKpiStrategyResponse | NetworkKpiStrategyResponse | UsersKpiStrategyResponse ): ChartSeriesData[] => { if (fields.length === 0) return []; return fields.reduce((acc: ChartSeriesData[], field: StatItem, idx: number) => { diff --git a/x-pack/plugins/security_solution/public/explore/hosts/components/kpi_hosts/common/index.tsx b/x-pack/plugins/security_solution/public/explore/hosts/components/kpi_hosts/common/index.tsx index 0c92362c1b026..7413563e0c950 100644 --- a/x-pack/plugins/security_solution/public/explore/hosts/components/kpi_hosts/common/index.tsx +++ b/x-pack/plugins/security_solution/public/explore/hosts/components/kpi_hosts/common/index.tsx @@ -18,7 +18,7 @@ import type { import type { StatItemsProps, StatItems } from '../../../../components/stat_items'; import { StatItemsComponent, useKpiMatrixStatus } from '../../../../components/stat_items'; import type { UpdateDateRange } from '../../../../../common/components/charts/common'; -import type { UserskKpiStrategyResponse } from '../../../../../../common/search_strategy/security_solution/users'; +import type { UsersKpiStrategyResponse } from '../../../../../../common/search_strategy/security_solution/users'; const kpiWidgetHeight = 247; @@ -30,7 +30,7 @@ FlexGroup.displayName = 'FlexGroup'; interface KpiBaseComponentProps { fieldsMapping: Readonly; - data: HostsKpiStrategyResponse | NetworkKpiStrategyResponse | UserskKpiStrategyResponse; + data: HostsKpiStrategyResponse | NetworkKpiStrategyResponse | UsersKpiStrategyResponse; loading?: boolean; id: string; from: string; diff --git a/x-pack/plugins/security_solution/public/explore/users/containers/users/details/index.test.tsx b/x-pack/plugins/security_solution/public/explore/users/containers/users/observed_details/index.test.tsx similarity index 88% rename from x-pack/plugins/security_solution/public/explore/users/containers/users/details/index.test.tsx rename to x-pack/plugins/security_solution/public/explore/users/containers/users/observed_details/index.test.tsx index 396cf027ebccb..f0e2536096421 100644 --- a/x-pack/plugins/security_solution/public/explore/users/containers/users/details/index.test.tsx +++ b/x-pack/plugins/security_solution/public/explore/users/containers/users/observed_details/index.test.tsx @@ -6,7 +6,7 @@ */ import { act, renderHook } from '@testing-library/react-hooks'; import { TestProviders } from '../../../../../common/mock'; -import { useUserDetails } from '.'; +import { useObservedUserDetails } from '.'; import { useSearchStrategy } from '../../../../../common/containers/use_search_strategy'; jest.mock('../../../../../common/containers/use_search_strategy', () => ({ @@ -38,7 +38,7 @@ describe('useUserDetails', () => { }); it('runs search', () => { - renderHook(() => useUserDetails(defaultProps), { + renderHook(() => useObservedUserDetails(defaultProps), { wrapper: TestProviders, }); @@ -50,7 +50,7 @@ describe('useUserDetails', () => { ...defaultProps, skip: true, }; - renderHook(() => useUserDetails(props), { + renderHook(() => useObservedUserDetails(props), { wrapper: TestProviders, }); @@ -60,7 +60,7 @@ describe('useUserDetails', () => { const props = { ...defaultProps, }; - const { rerender } = renderHook(() => useUserDetails(props), { + const { rerender } = renderHook(() => useObservedUserDetails(props), { wrapper: TestProviders, }); props.skip = true; diff --git a/x-pack/plugins/security_solution/public/explore/users/containers/users/details/index.tsx b/x-pack/plugins/security_solution/public/explore/users/containers/users/observed_details/index.tsx similarity index 71% rename from x-pack/plugins/security_solution/public/explore/users/containers/users/details/index.tsx rename to x-pack/plugins/security_solution/public/explore/users/containers/users/observed_details/index.tsx index 7ddd0486a48ba..12e6e583c9a41 100644 --- a/x-pack/plugins/security_solution/public/explore/users/containers/users/details/index.tsx +++ b/x-pack/plugins/security_solution/public/explore/users/containers/users/observed_details/index.tsx @@ -11,9 +11,11 @@ import * as i18n from './translations'; import type { InspectResponse } from '../../../../../types'; import { UsersQueries } from '../../../../../../common/search_strategy/security_solution/users'; import type { UserItem } from '../../../../../../common/search_strategy/security_solution/users/common'; +import { NOT_EVENT_KIND_ASSET_FILTER } from '../../../../../../common/search_strategy/security_solution/users/common'; import { useSearchStrategy } from '../../../../../common/containers/use_search_strategy'; +import { useIsExperimentalFeatureEnabled } from '../../../../../common/hooks/use_experimental_features'; -export const ID = 'usersDetailsQuery'; +export const OBSERVED_USER_QUERY_ID = 'observedUsersDetailsQuery'; export interface UserDetailsArgs { id: string; @@ -33,22 +35,23 @@ interface UseUserDetails { startDate: string; } -export const useUserDetails = ({ +export const useObservedUserDetails = ({ endDate, userName, indexNames, - id = ID, + id = OBSERVED_USER_QUERY_ID, skip = false, startDate, }: UseUserDetails): [boolean, UserDetailsArgs] => { + const isNewUserDetailsFlyoutEnabled = useIsExperimentalFeatureEnabled('newUserDetailsFlyout'); const { loading, result: response, search, refetch, inspect, - } = useSearchStrategy({ - factoryQueryType: UsersQueries.details, + } = useSearchStrategy({ + factoryQueryType: UsersQueries.observedDetails, initialResult: { userDetails: {}, }, @@ -62,7 +65,6 @@ export const useUserDetails = ({ userDetails: response.userDetails, id, inspect, - isInspected: false, refetch, startDate, }), @@ -72,15 +74,16 @@ export const useUserDetails = ({ const userDetailsRequest = useMemo( () => ({ defaultIndex: indexNames, - factoryQueryType: UsersQueries.details, + factoryQueryType: UsersQueries.observedDetails, userName, timerange: { interval: '12h', from: startDate, to: endDate, }, + filterQuery: isNewUserDetailsFlyoutEnabled ? NOT_EVENT_KIND_ASSET_FILTER : undefined, }), - [endDate, indexNames, startDate, userName] + [endDate, indexNames, startDate, userName, isNewUserDetailsFlyoutEnabled] ); useEffect(() => { diff --git a/x-pack/plugins/security_solution/public/explore/users/containers/users/details/translations.ts b/x-pack/plugins/security_solution/public/explore/users/containers/users/observed_details/translations.ts similarity index 100% rename from x-pack/plugins/security_solution/public/explore/users/containers/users/details/translations.ts rename to x-pack/plugins/security_solution/public/explore/users/containers/users/observed_details/translations.ts diff --git a/x-pack/plugins/security_solution/public/explore/users/pages/details/index.tsx b/x-pack/plugins/security_solution/public/explore/users/pages/details/index.tsx index d991a75880f61..10581694bc36f 100644 --- a/x-pack/plugins/security_solution/public/explore/users/pages/details/index.tsx +++ b/x-pack/plugins/security_solution/public/explore/users/pages/details/index.tsx @@ -57,7 +57,7 @@ import { LastEventIndexKey } from '../../../../../common/search_strategy'; import { AnomalyTableProvider } from '../../../../common/components/ml/anomaly/anomaly_table_provider'; import { UserOverview } from '../../../../overview/components/user_overview'; -import { useUserDetails } from '../../containers/users/details'; +import { useObservedUserDetails } from '../../containers/users/observed_details'; import { useQueryInspector } from '../../../../common/components/page/manage_query'; import { scoreIntervalToDateTime } from '../../../../common/components/ml/score/score_interval_to_datetime'; import { getCriteriaFromUsersType } from '../../../../common/components/ml/criteria/get_criteria_from_users_type'; @@ -134,7 +134,7 @@ const UsersDetailsComponent: React.FC = ({ dispatch(setUsersDetailsTablesActivePageToZero()); }, [dispatch, detailName]); - const [loading, { inspect, userDetails, refetch }] = useUserDetails({ + const [loading, { inspect, userDetails, refetch }] = useObservedUserDetails({ id: QUERY_ID, endDate: to, startDate: from, diff --git a/x-pack/plugins/security_solution/public/flyout/right/components/user_entity_overview.test.tsx b/x-pack/plugins/security_solution/public/flyout/right/components/user_entity_overview.test.tsx index b868d1161a65b..26895e144ceb8 100644 --- a/x-pack/plugins/security_solution/public/flyout/right/components/user_entity_overview.test.tsx +++ b/x-pack/plugins/security_solution/public/flyout/right/components/user_entity_overview.test.tsx @@ -9,12 +9,13 @@ import { render } from '@testing-library/react'; import { TestProviders } from '../../../common/mock'; import { UserEntityOverview } from './user_entity_overview'; import { useRiskScore } from '../../../explore/containers/risk_score'; -import { useUserDetails } from '../../../explore/users/containers/users/details'; + import { ENTITIES_USER_OVERVIEW_IP_TEST_ID, ENTITIES_USER_OVERVIEW_RISK_LEVEL_TEST_ID, TECHNICAL_PREVIEW_ICON_TEST_ID, } from './test_ids'; +import { useObservedUserDetails } from '../../../explore/users/containers/users/observed_details'; const userName = 'user'; const ip = '10.200.000.000'; @@ -38,8 +39,8 @@ jest.mock('../../../common/containers/sourcerer', () => { }; }); -const mockUseUserDetails = useUserDetails as jest.Mock; -jest.mock('../../../explore/users/containers/users/details'); +const mockUseUserDetails = useObservedUserDetails as jest.Mock; +jest.mock('../../../explore/users/containers/users/observed_details'); const mockUseRiskScore = useRiskScore as jest.Mock; jest.mock('../../../explore/containers/risk_score'); diff --git a/x-pack/plugins/security_solution/public/flyout/right/components/user_entity_overview.tsx b/x-pack/plugins/security_solution/public/flyout/right/components/user_entity_overview.tsx index bf793a1d058ac..af1a33580b475 100644 --- a/x-pack/plugins/security_solution/public/flyout/right/components/user_entity_overview.tsx +++ b/x-pack/plugins/security_solution/public/flyout/right/components/user_entity_overview.tsx @@ -24,7 +24,6 @@ import { getEmptyTagValue } from '../../../common/components/empty_value'; import { useSourcererDataView } from '../../../common/containers/sourcerer'; import { useGlobalTime } from '../../../common/containers/use_global_time'; import { useRiskScore } from '../../../explore/containers/risk_score'; -import { useUserDetails } from '../../../explore/users/containers/users/details'; import * as i18n from '../../../overview/components/user_overview/translations'; import { TECHNICAL_PREVIEW_TITLE, TECHNICAL_PREVIEW_MESSAGE } from './translations'; import { @@ -33,6 +32,7 @@ import { ENTITIES_USER_OVERVIEW_IP_TEST_ID, ENTITIES_USER_OVERVIEW_RISK_LEVEL_TEST_ID, } from './test_ids'; +import { useObservedUserDetails } from '../../../explore/users/containers/users/observed_details'; const StyledEuiBetaBadge = styled(EuiBetaBadge)` margin-left: ${({ theme }) => theme.eui.euiSizeXS}; @@ -66,7 +66,7 @@ export const UserEntityOverview: React.FC = ({ userName () => (userName ? buildUserNamesFilter([userName]) : undefined), [userName] ); - const [_, { userDetails: data }] = useUserDetails({ + const [_, { userDetails: data }] = useObservedUserDetails({ endDate: to, userName, indexNames: selectedPatterns, diff --git a/x-pack/plugins/security_solution/public/timelines/components/side_panel/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/side_panel/index.tsx index 329de290c19c8..19aa3fe2874ec 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/side_panel/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/side_panel/index.tsx @@ -23,6 +23,7 @@ import { EventDetailsPanel } from './event_details'; import { HostDetailsPanel } from './host_details'; import { NetworkDetailsPanel } from './network_details'; import { UserDetailsPanel } from './user_details'; +import { useIsExperimentalFeatureEnabled } from '../../../common/hooks/use_experimental_features'; interface DetailsPanelProps { browserFields: BrowserFields; @@ -52,6 +53,7 @@ export const DetailsPanel = React.memo( isReadOnly, }: DetailsPanelProps) => { const dispatch = useDispatch(); + const isNewUserDetailsFlyoutEnable = useIsExperimentalFeatureEnabled('newUserDetailsFlyout'); const getScope = useMemo(() => { if (isTimelineScope(scopeId)) { return timelineSelectors.getTimelineByIdSelector(); @@ -124,6 +126,9 @@ export const DetailsPanel = React.memo( if (currentTabDetail?.panelView === 'userDetail' && currentTabDetail?.params?.userName) { flyoutUniqueKey = currentTabDetail.params.userName; + if (isNewUserDetailsFlyoutEnable) { + panelSize = 'm'; + } visiblePanel = ( ); } diff --git a/x-pack/plugins/security_solution/public/timelines/components/side_panel/new_user_detail/__mocks__/index.ts b/x-pack/plugins/security_solution/public/timelines/components/side_panel/new_user_detail/__mocks__/index.ts new file mode 100644 index 0000000000000..59fafed45b468 --- /dev/null +++ b/x-pack/plugins/security_solution/public/timelines/components/side_panel/new_user_detail/__mocks__/index.ts @@ -0,0 +1,105 @@ +/* + * Copyright 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 { RiskSeverity } from '../../../../../../common/search_strategy'; +import { mockAnomalies } from '../../../../../common/components/ml/mock'; +import type { ObservedUserData } from '../types'; + +const userRiskScore = { + '@timestamp': '123456', + user: { + name: 'test', + risk: { + rule_risks: [], + calculated_score_norm: 70, + multipliers: [], + calculated_level: RiskSeverity.high, + }, + }, + alertsCount: 0, + oldestAlertTimestamp: '123456', +}; + +export const mockRiskScoreState = { + data: [userRiskScore], + inspect: { + dsl: [], + response: [], + }, + isInspected: false, + refetch: () => {}, + totalCount: 0, + isModuleEnabled: true, + isLicenseValid: true, + isDeprecated: false, + loading: false, +}; + +const anomaly = mockAnomalies.anomalies[0]; + +export const managedUserDetails = { + '@timestamp': '', + agent: {}, + host: {}, + event: {}, + user: { + id: '123456', + last_name: 'user', + first_name: 'test', + full_name: 'test user', + phone: ['123456', '654321'], + }, +}; + +export const observedUserDetails = { + user: { + id: ['1234', '321'], + domain: ['test domain', 'another test domain'], + }, + host: { + ip: ['10.0.0.1', '127.0.0.1'], + os: { + name: ['testOs'], + family: ['testFamily'], + }, + }, +}; + +export const mockManagedUser = { + details: managedUserDetails, + isLoading: false, + isIntegrationEnabled: true, + firstSeen: { + isLoading: false, + date: '2023-03-23T20:03:17.489Z', + }, + lastSeen: { + isLoading: false, + date: '2023-03-23T20:03:17.489Z', + }, +}; + +export const mockObservedUser: ObservedUserData = { + details: observedUserDetails, + isLoading: false, + firstSeen: { + isLoading: false, + date: '2023-02-23T20:03:17.489Z', + }, + lastSeen: { + isLoading: false, + date: '2023-02-23T20:03:17.489Z', + }, + anomalies: { + isLoading: false, + anomalies: { + anomalies: [anomaly], + interval: '', + }, + jobNameById: { [anomaly.jobId]: 'job_name' }, + }, +}; diff --git a/x-pack/plugins/security_solution/public/timelines/components/side_panel/new_user_detail/columns.tsx b/x-pack/plugins/security_solution/public/timelines/components/side_panel/new_user_detail/columns.tsx new file mode 100644 index 0000000000000..a876a380c8aea --- /dev/null +++ b/x-pack/plugins/security_solution/public/timelines/components/side_panel/new_user_detail/columns.tsx @@ -0,0 +1,133 @@ +/* + * Copyright 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 { css } from '@emotion/react'; +import React, { useCallback } from 'react'; +import { head } from 'lodash/fp'; +import { euiLightVars } from '@kbn/ui-theme'; +import type { EuiBasicTableColumn } from '@elastic/eui'; +import { useDispatch } from 'react-redux'; +import { DefaultFieldRenderer } from '../../field_renderers/field_renderers'; +import type { + ManagedUsersTableColumns, + ManagedUserTable, + ObservedUsersTableColumns, + ObservedUserTable, + UserAnomalies, +} from './types'; +import * as i18n from './translations'; +import { defaultToEmptyTag } from '../../../../common/components/empty_value'; +import { FormattedRelativePreferenceDate } from '../../../../common/components/formatted_date'; +import { AnomalyScores } from '../../../../common/components/ml/score/anomaly_scores'; +import { useGlobalTime } from '../../../../common/containers/use_global_time'; +import { scoreIntervalToDateTime } from '../../../../common/components/ml/score/score_interval_to_datetime'; +import { InputsModelId } from '../../../../common/store/inputs/constants'; +import { setAbsoluteRangeDatePicker } from '../../../../common/store/inputs/actions'; + +const fieldColumn: EuiBasicTableColumn = { + name: i18n.FIELD_COLUMN_TITLE, + field: 'label', + render: (label: string) => ( + + {label} + + ), +}; + +export const getManagedUserTableColumns = ( + contextID: string, + isDraggable: boolean +): ManagedUsersTableColumns => [ + fieldColumn, + { + name: i18n.VALUES_COLUMN_TITLE, + field: 'value', + render: (value: ManagedUserTable['value'], { field }) => { + return field && value ? ( + + ) : ( + defaultToEmptyTag(value) + ); + }, + }, +]; + +function isAnomalies( + field: string | undefined, + values: UserAnomalies | unknown +): values is UserAnomalies { + return field === 'anomalies'; +} + +export const getObservedUserTableColumns = ( + contextID: string, + isDraggable: boolean +): ObservedUsersTableColumns => [ + fieldColumn, + { + name: i18n.VALUES_COLUMN_TITLE, + field: 'values', + render: (values: ObservedUserTable['values'], { field }) => { + if (isAnomalies(field, values) && values) { + return ; + } + + if (field === '@timestamp') { + return ; + } + + return ( + + ); + }, + }, +]; + +const AnomaliesField = ({ anomalies }: { anomalies: UserAnomalies }) => { + const { to, from } = useGlobalTime(); + const dispatch = useDispatch(); + + const narrowDateRange = useCallback( + (score, interval) => { + const fromTo = scoreIntervalToDateTime(score, interval); + dispatch( + setAbsoluteRangeDatePicker({ + id: InputsModelId.global, + from: fromTo.from, + to: fromTo.to, + }) + ); + }, + [dispatch] + ); + + return ( + + ); +}; diff --git a/x-pack/plugins/security_solution/public/timelines/components/side_panel/new_user_detail/constants.ts b/x-pack/plugins/security_solution/public/timelines/components/side_panel/new_user_detail/constants.ts new file mode 100644 index 0000000000000..4b4f3c39628f6 --- /dev/null +++ b/x-pack/plugins/security_solution/public/timelines/components/side_panel/new_user_detail/constants.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 const MANAGED_USER_INDEX = ['logs-entityanalytics_azure.users-*']; +export const MANAGED_USER_PACKAGE_NAME = 'entityanalytics_azure'; +export const INSTALL_INTEGRATION_HREF = `/app/fleet/integrations/${MANAGED_USER_PACKAGE_NAME}/add-integration`; +export const ONE_WEEK_IN_HOURS = 24 * 7; +export const MANAGED_USER_QUERY_ID = 'managedUserDetailsQuery'; diff --git a/x-pack/plugins/security_solution/public/timelines/components/side_panel/new_user_detail/hooks.test.ts b/x-pack/plugins/security_solution/public/timelines/components/side_panel/new_user_detail/hooks.test.ts new file mode 100644 index 0000000000000..3c8815a3dcffb --- /dev/null +++ b/x-pack/plugins/security_solution/public/timelines/components/side_panel/new_user_detail/hooks.test.ts @@ -0,0 +1,61 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { renderHook } from '@testing-library/react-hooks'; +import type { InstalledIntegration } from '../../../../../common/detection_engine/fleet_integrations'; +import { TestProviders } from '../../../../common/mock'; +import { MANAGED_USER_PACKAGE_NAME } from './constants'; +import { useManagedUser } from './hooks'; + +const makeInstalledIntegration = ( + pkgName = 'testPkg', + isEnabled = false +): InstalledIntegration => ({ + package_name: pkgName, + package_title: '', + package_version: '', + integration_name: '', + integration_title: '', + is_enabled: isEnabled, +}); + +const mockUseInstalledIntegrations = jest.fn().mockReturnValue({ + data: [], +}); + +jest.mock( + '../../../../detections/components/rules/related_integrations/use_installed_integrations', + () => ({ + useInstalledIntegrations: () => mockUseInstalledIntegrations(), + }) +); + +describe('useManagedUser', () => { + it('returns isIntegrationEnabled:true when it finds an enabled integration with the given name', () => { + mockUseInstalledIntegrations.mockReturnValue({ + data: [makeInstalledIntegration(MANAGED_USER_PACKAGE_NAME, true)], + }); + + const { result } = renderHook(() => useManagedUser('test-userName'), { + wrapper: TestProviders, + }); + + expect(result.current.isIntegrationEnabled).toBeTruthy(); + }); + + it('returns isIntegrationEnabled:false when it does not find an enabled integration with the given name', () => { + mockUseInstalledIntegrations.mockReturnValue({ + data: [makeInstalledIntegration('fake-name', true)], + }); + + const { result } = renderHook(() => useManagedUser('test-userName'), { + wrapper: TestProviders, + }); + + expect(result.current.isIntegrationEnabled).toBeFalsy(); + }); +}); diff --git a/x-pack/plugins/security_solution/public/timelines/components/side_panel/new_user_detail/hooks.ts b/x-pack/plugins/security_solution/public/timelines/components/side_panel/new_user_detail/hooks.ts new file mode 100644 index 0000000000000..13d3b07676238 --- /dev/null +++ b/x-pack/plugins/security_solution/public/timelines/components/side_panel/new_user_detail/hooks.ts @@ -0,0 +1,236 @@ +/* + * Copyright 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 { useMemo, useEffect } from 'react'; +import * as i18n from './translations'; +import type { ManagedUserTable, ObservedUserData, ObservedUserTable } from './types'; +import type { AzureManagedUser } from '../../../../../common/search_strategy/security_solution/users/managed_details'; + +import { + Direction, + UsersQueries, + NOT_EVENT_KIND_ASSET_FILTER, +} from '../../../../../common/search_strategy'; +import { useSourcererDataView } from '../../../../common/containers/sourcerer'; +import { useGlobalTime } from '../../../../common/containers/use_global_time'; +import { useSearchStrategy } from '../../../../common/containers/use_search_strategy'; +import { useObservedUserDetails } from '../../../../explore/users/containers/users/observed_details'; +import { useFirstLastSeen } from '../../../../common/containers/use_first_last_seen'; +import { useInstalledIntegrations } from '../../../../detections/components/rules/related_integrations/use_installed_integrations'; +import { MANAGED_USER_INDEX, MANAGED_USER_PACKAGE_NAME, MANAGED_USER_QUERY_ID } from './constants'; +import { useQueryInspector } from '../../../../common/components/page/manage_query'; + +export const useObservedUserItems = (userData: ObservedUserData): ObservedUserTable[] => + useMemo( + () => + !userData.details + ? [] + : [ + { label: i18n.USER_ID, values: userData.details.user?.id, field: 'user.id' }, + { label: 'Domain', values: userData.details.user?.domain, field: 'user.domain' }, + { + label: i18n.MAX_ANOMALY_SCORE_BY_JOB, + field: 'anomalies', + values: userData.anomalies, + }, + { + label: i18n.FIRST_SEEN, + values: userData.firstSeen.date ? [userData.firstSeen.date] : undefined, + field: '@timestamp', + }, + { + label: i18n.LAST_SEEN, + values: userData.lastSeen.date ? [userData.lastSeen.date] : undefined, + field: '@timestamp', + }, + { + label: i18n.OPERATING_SYSTEM_TITLE, + values: userData.details.host?.os?.name, + field: 'host.os.name', + }, + { + label: i18n.FAMILY, + values: userData.details.host?.os?.family, + field: 'host.os.family', + }, + { label: i18n.IP_ADDRESSES, values: userData.details.host?.ip, field: 'host.ip' }, + ], + [userData.details, userData.anomalies, userData.firstSeen, userData.lastSeen] + ); + +export const useManagedUserItems = ( + managedUserDetails?: AzureManagedUser +): ManagedUserTable[] | null => + useMemo( + () => + !managedUserDetails + ? null + : [ + { + label: i18n.USER_ID, + value: managedUserDetails.user.id, + field: 'user.id', + }, + { + label: i18n.FULL_NAME, + value: managedUserDetails.user.full_name, + field: 'user.full_name', + }, + { + label: i18n.FIRST_NAME, + value: managedUserDetails.user.first_name, + }, + { + label: i18n.LAST_NAME, + value: managedUserDetails.user.last_name, + }, + { label: i18n.PHONE, value: managedUserDetails.user.phone?.join(', ') }, + ], + [managedUserDetails] + ); + +export const useManagedUser = (userName: string) => { + const { to, from, isInitializing, deleteQuery, setQuery } = useGlobalTime(); + const { + loading: loadingManagedUser, + result: { userDetails: managedUserDetails }, + search, + refetch, + inspect, + } = useSearchStrategy({ + factoryQueryType: UsersQueries.managedDetails, + initialResult: {}, + errorMessage: i18n.FAIL_MANAGED_USER, + }); + + useEffect(() => { + if (!isInitializing) { + search({ + defaultIndex: MANAGED_USER_INDEX, + factoryQueryType: UsersQueries.managedDetails, + userName, + }); + } + }, [from, search, to, userName, isInitializing]); + + const { data: installedIntegrations } = useInstalledIntegrations({ + packages: [MANAGED_USER_PACKAGE_NAME], + }); + + useQueryInspector({ + deleteQuery, + inspect, + refetch, + setQuery, + queryId: MANAGED_USER_QUERY_ID, + loading: loadingManagedUser, + }); + + const isIntegrationEnabled = useMemo( + () => + !!installedIntegrations?.some( + ({ package_name: packageName, is_enabled: isEnabled }) => + packageName === MANAGED_USER_PACKAGE_NAME && isEnabled + ), + [installedIntegrations] + ); + + const [loadingFirstSeen, { firstSeen }] = useFirstLastSeen({ + field: 'user.name', + value: userName, + defaultIndex: MANAGED_USER_INDEX, + order: Direction.asc, + }); + + const [loadingLastSeen, { lastSeen }] = useFirstLastSeen({ + field: 'user.name', + value: userName, + defaultIndex: MANAGED_USER_INDEX, + order: Direction.desc, + }); + + return useMemo( + () => ({ + details: managedUserDetails, + isLoading: loadingManagedUser, + isIntegrationEnabled, + firstSeen: { + date: firstSeen, + isLoading: loadingFirstSeen, + }, + lastSeen: { date: lastSeen, isLoading: loadingLastSeen }, + }), + [ + firstSeen, + isIntegrationEnabled, + lastSeen, + loadingFirstSeen, + loadingLastSeen, + loadingManagedUser, + managedUserDetails, + ] + ); +}; + +export const useObservedUser = (userName: string) => { + const { selectedPatterns } = useSourcererDataView(); + const { to, from, isInitializing, deleteQuery, setQuery } = useGlobalTime(); + + const [loadingObservedUser, { userDetails: observedUserDetails, inspect, refetch, id: queryId }] = + useObservedUserDetails({ + endDate: to, + startDate: from, + userName, + indexNames: selectedPatterns, + skip: isInitializing, + }); + + useQueryInspector({ + deleteQuery, + inspect, + refetch, + setQuery, + queryId, + loading: loadingObservedUser, + }); + + const [loadingFirstSeen, { firstSeen }] = useFirstLastSeen({ + field: 'user.name', + value: userName, + defaultIndex: selectedPatterns, + order: Direction.asc, + filterQuery: NOT_EVENT_KIND_ASSET_FILTER, + }); + + const [loadingLastSeen, { lastSeen }] = useFirstLastSeen({ + field: 'user.name', + value: userName, + defaultIndex: selectedPatterns, + order: Direction.desc, + filterQuery: NOT_EVENT_KIND_ASSET_FILTER, + }); + + return useMemo( + () => ({ + details: observedUserDetails, + isLoading: loadingObservedUser, + firstSeen: { + date: firstSeen, + isLoading: loadingFirstSeen, + }, + lastSeen: { date: lastSeen, isLoading: loadingLastSeen }, + }), + [ + firstSeen, + lastSeen, + loadingFirstSeen, + loadingLastSeen, + loadingObservedUser, + observedUserDetails, + ] + ); +}; diff --git a/x-pack/plugins/security_solution/public/timelines/components/side_panel/new_user_detail/managed_user.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/side_panel/new_user_detail/managed_user.test.tsx new file mode 100644 index 0000000000000..fe08629c85367 --- /dev/null +++ b/x-pack/plugins/security_solution/public/timelines/components/side_panel/new_user_detail/managed_user.test.tsx @@ -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 { fireEvent, render } from '@testing-library/react'; +import React from 'react'; +import { TestProviders } from '../../../../common/mock'; +import { ManagedUser } from './managed_user'; +import { mockManagedUser } from './__mocks__'; + +describe('ManagedUser', () => { + const mockProps = { + managedUser: mockManagedUser, + contextID: '', + isDraggable: false, + }; + + it('renders', () => { + const { getByTestId } = render( + + + + ); + + expect(getByTestId('managedUser-data')).toBeInTheDocument(); + }); + + it('updates the accordion button title when visibility toggles', () => { + const { getByTestId } = render( + + + + ); + const accordionButton = getByTestId('managedUser-accordion-button'); + + expect(accordionButton).toHaveTextContent('Show Azure AD data'); + fireEvent.click(accordionButton); + expect(accordionButton).toHaveTextContent('Hide Azure AD data'); + }); + + it('renders the formatted date', () => { + const { getByTestId } = render( + + + + ); + + expect(getByTestId('managedUser-data')).toHaveTextContent('Updated Mar 23, 2023'); + }); + + it('renders enable integration callout when the integration is disabled', () => { + const { getByTestId } = render( + + + + ); + + expect(getByTestId('managedUser-integration-disable-callout')).toBeInTheDocument(); + }); + + it('renders phone number separated by comma', () => { + const { getByTestId } = render( + + + + ); + + expect(getByTestId('managedUser-data')).toHaveTextContent('123456, 654321'); + }); +}); diff --git a/x-pack/plugins/security_solution/public/timelines/components/side_panel/new_user_detail/managed_user.tsx b/x-pack/plugins/security_solution/public/timelines/components/side_panel/new_user_detail/managed_user.tsx new file mode 100644 index 0000000000000..873a7a5bde78a --- /dev/null +++ b/x-pack/plugins/security_solution/public/timelines/components/side_panel/new_user_detail/managed_user.tsx @@ -0,0 +1,157 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { + EuiButton, + EuiSpacer, + EuiAccordion, + EuiTitle, + EuiPanel, + useEuiTheme, + EuiEmptyPrompt, + EuiCallOut, +} from '@elastic/eui'; + +import React, { useCallback, useMemo, useState } from 'react'; +import { css } from '@emotion/react'; +import { FormattedMessage } from '@kbn/i18n-react'; +import * as i18n from './translations'; + +import { BasicTable } from '../../../../common/components/ml/tables/basic_table'; +import { getManagedUserTableColumns } from './columns'; +import { useManagedUserItems } from './hooks'; + +import { FormattedRelativePreferenceDate } from '../../../../common/components/formatted_date'; +import type { ManagedUserData } from './types'; +import { INSTALL_INTEGRATION_HREF, MANAGED_USER_QUERY_ID, ONE_WEEK_IN_HOURS } from './constants'; +import { InspectButton, InspectButtonContainer } from '../../../../common/components/inspect'; + +export const ManagedUser = ({ + managedUser, + contextID, + isDraggable, +}: { + managedUser: ManagedUserData; + contextID: string; + isDraggable: boolean; +}) => { + const { euiTheme } = useEuiTheme(); + const managedItems = useManagedUserItems(managedUser.details); + const [isManagedDataToggleOpen, setManagedDataToggleOpen] = useState(false); + const onToggleManagedData = useCallback(() => { + setManagedDataToggleOpen((isOpen) => !isOpen); + }, [setManagedDataToggleOpen]); + const managedUserTableColumns = useMemo( + () => getManagedUserTableColumns(contextID, isDraggable), + [isDraggable, contextID] + ); + + if (!managedUser.isIntegrationEnabled) { + return ( + <> + +
{i18n.MANAGED_DATA_TITLE}
+
+ + + {i18n.NO_ACTIVE_INTEGRATION_TITLE}} + body={

{i18n.NO_ACTIVE_INTEGRATION_TEXT}

} + actions={ + + {i18n.ADD_EXTERNAL_INTEGRATION_BUTTON} + + } + /> +
+ + ); + } + + return ( + <> + +
{i18n.MANAGED_DATA_TITLE}
+
+ + + + + + + {managedUser.lastSeen.date && ( + + ), + }} + /> + )} + + } + css={css` + .euiAccordion__optionalAction { + margin-left: auto; + } + `} + > + + {managedItems || managedUser.isLoading ? ( + + ) : ( + <> + +

{i18n.NO_AZURE_DATA_TEXT}

+
+ + )} +
+
+
+ + ); +}; diff --git a/x-pack/plugins/security_solution/public/timelines/components/side_panel/new_user_detail/observed_user.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/side_panel/new_user_detail/observed_user.test.tsx new file mode 100644 index 0000000000000..d7493ed3a8921 --- /dev/null +++ b/x-pack/plugins/security_solution/public/timelines/components/side_panel/new_user_detail/observed_user.test.tsx @@ -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 { fireEvent, render } from '@testing-library/react'; +import React from 'react'; +import { TestProviders } from '../../../../common/mock'; +import { mockObservedUser } from './__mocks__'; +import { ObservedUser } from './observed_user'; + +describe('ObservedUser', () => { + const mockProps = { + observedUser: mockObservedUser, + contextID: '', + isDraggable: false, + }; + + it('renders', () => { + const { getByTestId } = render( + + + + ); + + expect(getByTestId('observedUser-data')).toBeInTheDocument(); + }); + + it('updates the accordion button title when visibility toggles', () => { + const { getByTestId } = render( + + + + ); + const accordionButton = getByTestId('observedUser-accordion-button'); + + expect(accordionButton).toHaveTextContent('Show observed data'); + fireEvent.click(accordionButton); + expect(accordionButton).toHaveTextContent('Hide observed data'); + }); + + it('renders the formatted date', () => { + const { getByTestId } = render( + + + + ); + + expect(getByTestId('observedUser-data')).toHaveTextContent('Updated Feb 23, 2023'); + }); + + it('renders anomaly score', () => { + const { getByTestId } = render( + + + + ); + + expect(getByTestId('anomaly-score')).toHaveTextContent('17'); + }); +}); diff --git a/x-pack/plugins/security_solution/public/timelines/components/side_panel/new_user_detail/observed_user.tsx b/x-pack/plugins/security_solution/public/timelines/components/side_panel/new_user_detail/observed_user.tsx new file mode 100644 index 0000000000000..c67975d50d238 --- /dev/null +++ b/x-pack/plugins/security_solution/public/timelines/components/side_panel/new_user_detail/observed_user.tsx @@ -0,0 +1,119 @@ +/* + * Copyright 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 { EuiAccordion, EuiSpacer, EuiTitle, useEuiTheme, EuiPanel } from '@elastic/eui'; + +import React, { useCallback, useMemo, useState } from 'react'; +import { css } from '@emotion/react'; +import { FormattedMessage } from '@kbn/i18n-react'; +import * as i18n from './translations'; +import type { ObservedUserData } from './types'; +import { useObservedUserItems } from './hooks'; +import { BasicTable } from '../../../../common/components/ml/tables/basic_table'; +import { FormattedRelativePreferenceDate } from '../../../../common/components/formatted_date'; +import { getObservedUserTableColumns } from './columns'; +import { ONE_WEEK_IN_HOURS } from './constants'; +import { InspectButton, InspectButtonContainer } from '../../../../common/components/inspect'; +import { OBSERVED_USER_QUERY_ID } from '../../../../explore/users/containers/users/observed_details'; + +export const ObservedUser = ({ + observedUser, + contextID, + isDraggable, +}: { + observedUser: ObservedUserData; + contextID: string; + isDraggable: boolean; +}) => { + const { euiTheme } = useEuiTheme(); + const observedItems = useObservedUserItems(observedUser); + const [isObservedDataToggleOpen, setObservedDataToggleOpen] = useState(false); + const onToggleObservedData = useCallback(() => { + setObservedDataToggleOpen((isOpen) => !isOpen); + }, [setObservedDataToggleOpen]); + const observedUserTableColumns = useMemo( + () => getObservedUserTableColumns(contextID, isDraggable), + [contextID, isDraggable] + ); + + return ( + <> + +
{i18n.OBSERVED_DATA_TITLE}
+
+ + + + + + + {observedUser.lastSeen.date && ( + + ), + }} + /> + )} + + } + css={css` + .euiAccordion__optionalAction { + margin-left: auto; + } + `} + > + + + + + + + ); +}; diff --git a/x-pack/plugins/security_solution/public/timelines/components/side_panel/new_user_detail/risk_score_field.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/side_panel/new_user_detail/risk_score_field.test.tsx new file mode 100644 index 0000000000000..9e759fd246178 --- /dev/null +++ b/x-pack/plugins/security_solution/public/timelines/components/side_panel/new_user_detail/risk_score_field.test.tsx @@ -0,0 +1,46 @@ +/* + * Copyright 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 { render } from '@testing-library/react'; +import React from 'react'; +import { TestProviders } from '../../../../common/mock'; +import { RiskScoreField } from './risk_score_field'; +import { mockRiskScoreState } from './__mocks__'; +import { getEmptyValue } from '../../../../common/components/empty_value'; + +describe('RiskScoreField', () => { + it('renders', () => { + const { getByTestId } = render( + + + + ); + + expect(getByTestId('user-details-risk-score')).toBeInTheDocument(); + expect(getByTestId('user-details-risk-score')).toHaveTextContent('70'); + }); + + it('does not render content when the license is invalid', () => { + const { queryByTestId } = render( + + + + ); + + expect(queryByTestId('user-details-risk-score')).not.toBeInTheDocument(); + }); + + it('renders empty tag when risk score is undefined', () => { + const { getByTestId } = render( + + + + ); + + expect(getByTestId('user-details-risk-score')).toHaveTextContent(getEmptyValue()); + }); +}); diff --git a/x-pack/plugins/security_solution/public/timelines/components/side_panel/new_user_detail/risk_score_field.tsx b/x-pack/plugins/security_solution/public/timelines/components/side_panel/new_user_detail/risk_score_field.tsx new file mode 100644 index 0000000000000..9dce74009a0fa --- /dev/null +++ b/x-pack/plugins/security_solution/public/timelines/components/side_panel/new_user_detail/risk_score_field.tsx @@ -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 { EuiFlexItem, EuiFlexGroup, useEuiFontSize, useEuiTheme } from '@elastic/eui'; + +import React from 'react'; +import { css } from '@emotion/react'; + +import * as i18n from './translations'; + +import type { RiskScoreEntity } from '../../../../../common/search_strategy'; +import { RiskSeverity } from '../../../../../common/search_strategy'; +import { getEmptyTagValue } from '../../../../common/components/empty_value'; +import { RiskScore } from '../../../../explore/components/risk_score/severity/common'; +import type { RiskScoreState } from '../../../../explore/containers/risk_score'; + +export const RiskScoreField = ({ + riskScoreState, +}: { + riskScoreState: RiskScoreState; +}) => { + const { euiTheme } = useEuiTheme(); + const { fontSize: xsFontSize } = useEuiFontSize('xs'); + const { data: userRisk, isLicenseValid: isRiskLicenseValid } = riskScoreState; + const userRiskData = userRisk && userRisk.length > 0 ? userRisk[0] : undefined; + + if (!isRiskLicenseValid) { + return null; + } + + return ( + + + + {i18n.RISK_SCORE} + {': '} + + + {userRiskData ? ( + <> + + {Math.round(userRiskData.user.risk.calculated_score_norm)} + + + + + + ) : ( + getEmptyTagValue() + )} + + ); +}; diff --git a/x-pack/plugins/security_solution/public/timelines/components/side_panel/new_user_detail/translations.ts b/x-pack/plugins/security_solution/public/timelines/components/side_panel/new_user_detail/translations.ts new file mode 100644 index 0000000000000..01563fcb94781 --- /dev/null +++ b/x-pack/plugins/security_solution/public/timelines/components/side_panel/new_user_detail/translations.ts @@ -0,0 +1,222 @@ +/* + * Copyright 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'; + +export const OBSERVED_BADGE = i18n.translate( + 'xpack.securitySolution.timeline.userDetails.observedBadge', + { + defaultMessage: 'OBSERVED', + } +); + +export const MANAGED_BADGE = i18n.translate( + 'xpack.securitySolution.timeline.userDetails.managedBadge', + { + defaultMessage: 'MANAGED', + } +); + +export const USER = i18n.translate('xpack.securitySolution.timeline.userDetails.userLabel', { + defaultMessage: 'User', +}); + +export const FAIL_MANAGED_USER = i18n.translate( + 'xpack.securitySolution.timeline.userDetails.failManagedUserDescription', + { + defaultMessage: 'Failed to run search on user managed data', + } +); + +export const MANAGED_DATA_TITLE = i18n.translate( + 'xpack.securitySolution.timeline.userDetails.managedDataTitle', + { + defaultMessage: 'Managed data', + } +); + +export const OBSERVED_DATA_TITLE = i18n.translate( + 'xpack.securitySolution.timeline.userDetails.observedDataTitle', + { + defaultMessage: 'Observed data', + } +); + +export const HIDE_OBSERVED_DATA_BUTTON = i18n.translate( + 'xpack.securitySolution.timeline.userDetails.hideObservedDataButton', + { + defaultMessage: 'Hide observed data', + } +); + +export const SHOW_OBSERVED_DATA_BUTTON = i18n.translate( + 'xpack.securitySolution.timeline.userDetails.showObservedDataButton', + { + defaultMessage: 'Show observed data', + } +); + +export const HIDE_AZURE_DATA_BUTTON = i18n.translate( + 'xpack.securitySolution.timeline.userDetails.hideManagedDataButton', + { + defaultMessage: 'Hide Azure AD data', + } +); + +export const SHOW_AZURE_DATA_BUTTON = i18n.translate( + 'xpack.securitySolution.timeline.userDetails.showManagedDataButton', + { + defaultMessage: 'Show Azure AD data', + } +); + +export const RISK_SCORE = i18n.translate( + 'xpack.securitySolution.timeline.userDetails.riskScoreLabel', + { + defaultMessage: 'Risk score', + } +); + +export const VALUES_COLUMN_TITLE = i18n.translate( + 'xpack.securitySolution.timeline.userDetails.valuesColumnTitle', + { + defaultMessage: 'Values', + } +); + +export const FIELD_COLUMN_TITLE = i18n.translate( + 'xpack.securitySolution.timeline.userDetails.fieldColumnTitle', + { + defaultMessage: 'Field', + } +); + +export const USER_ID = i18n.translate('xpack.securitySolution.timeline.userDetails.userIdLabel', { + defaultMessage: 'User ID', +}); + +export const MAX_ANOMALY_SCORE_BY_JOB = i18n.translate( + 'xpack.securitySolution.timeline.userDetails.maxAnomalyScoreByJobLabel', + { + defaultMessage: 'Max anomaly score by job', + } +); + +export const FIRST_SEEN = i18n.translate( + 'xpack.securitySolution.timeline.userDetails.firstSeenLabel', + { + defaultMessage: 'First seen', + } +); + +export const LAST_SEEN = i18n.translate( + 'xpack.securitySolution.timeline.userDetails.lastSeenLabel', + { + defaultMessage: 'Last seen', + } +); + +export const OPERATING_SYSTEM_TITLE = i18n.translate( + 'xpack.securitySolution.timeline.userDetails.hostOsNameLabel', + { + defaultMessage: 'Operating system', + } +); + +export const FAMILY = i18n.translate('xpack.securitySolution.timeline.userDetails.familyLabel', { + defaultMessage: 'Family', +}); + +export const IP_ADDRESSES = i18n.translate( + 'xpack.securitySolution.timeline.userDetails.ipAddressesLabel', + { + defaultMessage: 'IP addresses', + } +); + +export const FULL_NAME = i18n.translate( + 'xpack.securitySolution.timeline.userDetails.fullNameLabel', + { + defaultMessage: 'Full name', + } +); + +export const FIRST_NAME = i18n.translate( + 'xpack.securitySolution.timeline.userDetails.firstNameLabel', + { + defaultMessage: 'First name', + } +); + +export const LAST_NAME = i18n.translate( + 'xpack.securitySolution.timeline.userDetails.lastNameLabel', + { + defaultMessage: 'Last name', + } +); + +export const PHONE = i18n.translate('xpack.securitySolution.timeline.userDetails.phoneLabel', { + defaultMessage: 'Phone', +}); + +export const NO_ACTIVE_INTEGRATION_TITLE = i18n.translate( + 'xpack.securitySolution.timeline.userDetails.noActiveIntegrationTitle', + { + defaultMessage: 'You don’t have any active integrations', + } +); + +export const NO_ACTIVE_INTEGRATION_TEXT = i18n.translate( + 'xpack.securitySolution.timeline.userDetails.noActiveIntegrationText', + { + defaultMessage: + 'External integrations can provide additional metadata and help you manage users.', + } +); + +export const ADD_EXTERNAL_INTEGRATION_BUTTON = i18n.translate( + 'xpack.securitySolution.timeline.userDetails.addExternalIntegrationButton', + { + defaultMessage: 'Add external integrations', + } +); + +export const NO_AZURE_DATA_TITLE = i18n.translate( + 'xpack.securitySolution.timeline.userDetails.noAzureDataTitle', + { + defaultMessage: 'No metadata found for this user', + } +); + +export const NO_AZURE_DATA_TEXT = i18n.translate( + 'xpack.securitySolution.timeline.userDetails.noAzureDataText', + { + defaultMessage: + 'If you expected to see metadata for this user, make sure you have configured your integrations properly.', + } +); + +export const CLOSE_BUTTON = i18n.translate( + 'xpack.securitySolution.timeline.userDetails.closeButton', + { + defaultMessage: 'close', + } +); + +export const OBSERVED_USER_INSPECT_TITLE = i18n.translate( + 'xpack.securitySolution.timeline.userDetails.observedUserInspectTitle', + { + defaultMessage: 'Observed user', + } +); + +export const MANAGED_USER_INSPECT_TITLE = i18n.translate( + 'xpack.securitySolution.timeline.userDetails.managedUserInspectTitle', + { + defaultMessage: 'Managed user', + } +); diff --git a/x-pack/plugins/security_solution/public/timelines/components/side_panel/new_user_detail/types.ts b/x-pack/plugins/security_solution/public/timelines/components/side_panel/new_user_detail/types.ts new file mode 100644 index 0000000000000..2587d81d09dc5 --- /dev/null +++ b/x-pack/plugins/security_solution/public/timelines/components/side_panel/new_user_detail/types.ts @@ -0,0 +1,53 @@ +/* + * Copyright 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 { EuiBasicTableColumn } from '@elastic/eui'; +import type { UserItem } from '../../../../../common/search_strategy'; +import type { AzureManagedUser } from '../../../../../common/search_strategy/security_solution/users/managed_details'; +import type { AnomalyTableProviderChildrenProps } from '../../../../common/components/ml/anomaly/anomaly_table_provider'; + +export interface ObservedUserTable { + label: string; + values: string[] | null | undefined | UserAnomalies; + field: string; +} + +export interface ManagedUserTable { + label: string; + value: string | null | undefined; + field?: string; +} + +export type ObservedUsersTableColumns = Array>; +export type ManagedUsersTableColumns = Array>; + +export interface ObservedUserData { + isLoading: boolean; + details: UserItem; + firstSeen: FirstLastSeenData; + lastSeen: FirstLastSeenData; + anomalies: UserAnomalies; +} + +export interface ManagedUserData { + isLoading: boolean; + details: AzureManagedUser | undefined; + isIntegrationEnabled: boolean; + firstSeen: FirstLastSeenData; + lastSeen: FirstLastSeenData; +} + +export interface FirstLastSeenData { + date: string | null | undefined; + isLoading: boolean; +} + +export interface UserAnomalies { + isLoading: AnomalyTableProviderChildrenProps['isLoadingAnomaliesData']; + anomalies: AnomalyTableProviderChildrenProps['anomaliesData']; + jobNameById: AnomalyTableProviderChildrenProps['jobNameById']; +} diff --git a/x-pack/plugins/security_solution/public/timelines/components/side_panel/new_user_detail/user_details_content.stories.tsx b/x-pack/plugins/security_solution/public/timelines/components/side_panel/new_user_detail/user_details_content.stories.tsx new file mode 100644 index 0000000000000..db17ada4a3b10 --- /dev/null +++ b/x-pack/plugins/security_solution/public/timelines/components/side_panel/new_user_detail/user_details_content.stories.tsx @@ -0,0 +1,157 @@ +/* + * Copyright 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 { storiesOf, addDecorator } from '@storybook/react'; +import { EuiFlyout, EuiFlyoutBody } from '@elastic/eui'; +import { UserDetailsContentComponent } from './user_details_content'; +import { StorybookProviders } from '../../../../common/mock/storybook_providers'; +import { mockManagedUser, mockObservedUser, mockRiskScoreState } from './__mocks__'; + +addDecorator((storyFn) => ( + + {}}> + {storyFn()} + + +)); + +storiesOf('UserDetailsContent', module) + .add('default', () => ( + + )) + .add('integration disabled', () => ( + + )) + .add('no managed data', () => ( + + )) + .add('no observed data', () => ( + + )) + .add('loading', () => ( + + )); diff --git a/x-pack/plugins/security_solution/public/timelines/components/side_panel/new_user_detail/user_details_content.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/side_panel/new_user_detail/user_details_content.test.tsx new file mode 100644 index 0000000000000..6ac3f1a0975f7 --- /dev/null +++ b/x-pack/plugins/security_solution/public/timelines/components/side_panel/new_user_detail/user_details_content.test.tsx @@ -0,0 +1,130 @@ +/* + * Copyright 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 { render } from '@testing-library/react'; +import React from 'react'; +import { TestProviders } from '../../../../common/mock'; +import { mockManagedUser, mockObservedUser, mockRiskScoreState } from './__mocks__'; +import { UserDetailsContentComponent } from './user_details_content'; + +const mockProps = { + userName: 'test', + managedUser: mockManagedUser, + observedUser: mockObservedUser, + riskScoreState: mockRiskScoreState, + contextID: 'test-user-details', + isDraggable: false, +}; + +describe('UserDetailsContentComponent', () => { + it('renders', () => { + const { getByTestId } = render( + + + + ); + + expect(getByTestId('user-details-content-header')).toBeInTheDocument(); + }); + + it('renders observed user date when it is bigger than managed user date', () => { + const futureDay = '2989-03-07T20:00:00.000Z'; + const { getByTestId } = render( + + + + ); + + expect(getByTestId('user-details-content-lastSeen').textContent).toContain('Mar 7, 2989'); + }); + + it('renders managed user date when it is bigger than observed user date', () => { + const futureDay = '2989-03-07T20:00:00.000Z'; + const { getByTestId } = render( + + + + ); + + expect(getByTestId('user-details-content-lastSeen').textContent).toContain('Mar 7, 2989'); + }); + + it('renders observed and managed badges when lastSeen is defined', () => { + const { getByTestId } = render( + + + + ); + + expect(getByTestId('user-details-content-observed-badge')).toBeInTheDocument(); + expect(getByTestId('user-details-content-managed-badge')).toBeInTheDocument(); + }); + + it('does not render observed badge when lastSeen date is undefined', () => { + const { queryByTestId } = render( + + + + ); + + expect(queryByTestId('user-details-content-observed-badge')).not.toBeInTheDocument(); + }); + + it('does not render managed badge when lastSeen date is undefined', () => { + const { queryByTestId } = render( + + + + ); + + expect(queryByTestId('user-details-content-managed-badge')).not.toBeInTheDocument(); + }); +}); diff --git a/x-pack/plugins/security_solution/public/timelines/components/side_panel/new_user_detail/user_details_content.tsx b/x-pack/plugins/security_solution/public/timelines/components/side_panel/new_user_detail/user_details_content.tsx new file mode 100644 index 0000000000000..baab7d365c99d --- /dev/null +++ b/x-pack/plugins/security_solution/public/timelines/components/side_panel/new_user_detail/user_details_content.tsx @@ -0,0 +1,182 @@ +/* + * Copyright 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, + EuiHorizontalRule, + EuiIcon, + EuiBadge, + EuiText, + EuiFlexItem, + EuiFlexGroup, + useEuiFontSize, + useEuiTheme, + euiTextBreakWord, + EuiProgress, +} from '@elastic/eui'; + +import React, { useMemo } from 'react'; +import { css } from '@emotion/react'; +import { max } from 'lodash'; +import * as i18n from './translations'; + +import { RiskScoreEntity } from '../../../../../common/search_strategy'; +import { UserDetailsLink } from '../../../../common/components/links'; +import { useGlobalTime } from '../../../../common/containers/use_global_time'; +import type { RiskScoreState } from '../../../../explore/containers/risk_score'; +import { useRiskScore } from '../../../../explore/containers/risk_score'; + +import { useManagedUser, useObservedUser } from './hooks'; +import { AnomalyTableProvider } from '../../../../common/components/ml/anomaly/anomaly_table_provider'; +import { getCriteriaFromUsersType } from '../../../../common/components/ml/criteria/get_criteria_from_users_type'; +import { UsersType } from '../../../../explore/users/store/model'; +import { PreferenceFormattedDate } from '../../../../common/components/formatted_date'; +import type { ManagedUserData, ObservedUserData } from './types'; +import { RiskScoreField } from './risk_score_field'; +import { ObservedUser } from './observed_user'; +import { ManagedUser } from './managed_user'; + +export const QUERY_ID = 'usersDetailsQuery'; + +interface UserDetailsContentComponentProps { + userName: string; + observedUser: ObservedUserData; + managedUser: ManagedUserData; + riskScoreState: RiskScoreState; + contextID: string; + isDraggable: boolean; +} + +/** + * This is a visual component. It doesn't access any external Context or API. + * It designed for unit testing the UI and previewing changes on storybook. + */ +export const UserDetailsContentComponent = ({ + userName, + observedUser, + managedUser, + riskScoreState, + contextID, + isDraggable, +}: UserDetailsContentComponentProps) => { + const { euiTheme } = useEuiTheme(); + const { fontSize: xlFontSize } = useEuiFontSize('xl'); + + const lastSeenDate = useMemo( + () => + max([observedUser.lastSeen, managedUser.lastSeen].map((el) => el.date && new Date(el.date))), + [managedUser.lastSeen, observedUser.lastSeen] + ); + + return ( + <> + + + + + {i18n.USER} + + + {observedUser.lastSeen.date && ( + + {i18n.OBSERVED_BADGE} + + )} + + + {managedUser.lastSeen.date && ( + + {i18n.MANAGED_BADGE} + + )} + + + + + {observedUser.lastSeen.isLoading || managedUser.lastSeen.isLoading ? ( + + ) : ( + + )} + + + + + {userName} + + + + + {i18n.LAST_SEEN} + {': '} + {lastSeenDate && } + + + + + + + + + + ); +}; + +export const UserDetailsContent = ({ + userName, + contextID, + isDraggable = false, +}: { + userName: string; + contextID: string; + isDraggable?: boolean; +}) => { + const { to, from, isInitializing } = useGlobalTime(); + const riskScoreState = useRiskScore({ + riskEntity: RiskScoreEntity.user, + }); + const observedUser = useObservedUser(userName); + const managedUser = useManagedUser(userName); + + return ( + + {({ isLoadingAnomaliesData, anomaliesData, jobNameById }) => ( + + )} + + ); +}; diff --git a/x-pack/plugins/security_solution/public/timelines/components/side_panel/user_details/expandable_user.tsx b/x-pack/plugins/security_solution/public/timelines/components/side_panel/user_details/expandable_user.tsx index e8554f04dbcaf..f12ea6b0ada14 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/side_panel/user_details/expandable_user.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/side_panel/user_details/expandable_user.tsx @@ -13,7 +13,7 @@ import { useDispatch } from 'react-redux'; import { InputsModelId } from '../../../../common/store/inputs/constants'; import { UserDetailsLink } from '../../../../common/components/links'; import { UserOverview } from '../../../../overview/components/user_overview'; -import { useUserDetails } from '../../../../explore/users/containers/users/details'; +import { useObservedUserDetails } from '../../../../explore/users/containers/users/observed_details'; import { useGlobalTime } from '../../../../common/containers/use_global_time'; import { useSourcererDataView } from '../../../../common/containers/sourcerer'; import { setAbsoluteRangeDatePicker } from '../../../../common/store/inputs/actions'; @@ -61,7 +61,7 @@ export const ExpandableUserDetails = ({ const { selectedPatterns } = useSourcererDataView(); const dispatch = useDispatch(); - const [loading, { userDetails }] = useUserDetails({ + const [loading, { userDetails }] = useObservedUserDetails({ endDate: to, startDate: from, userName, diff --git a/x-pack/plugins/security_solution/public/timelines/components/side_panel/user_details/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/side_panel/user_details/index.tsx index 09380d6874b92..2041b1e00bef4 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/side_panel/user_details/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/side_panel/user_details/index.tsx @@ -6,9 +6,13 @@ */ import React from 'react'; +import { EuiFlyoutBody, EuiSpacer, EuiButtonIcon } from '@elastic/eui'; +import { css } from '@emotion/react'; import { UserDetailsFlyout } from './user_details_flyout'; import { UserDetailsSidePanel } from './user_details_side_panel'; import type { UserDetailsProps } from './types'; +import { UserDetailsContent } from '../new_user_detail/user_details_content'; +import * as i18n from './translations'; const UserDetailsPanelComponent = ({ contextID, @@ -16,7 +20,30 @@ const UserDetailsPanelComponent = ({ handleOnClose, isFlyoutView, isDraggable, + isNewUserDetailsFlyoutEnable, }: UserDetailsProps) => { + if (isNewUserDetailsFlyoutEnable) { + return isFlyoutView ? ( + + + + ) : ( +
+ + + + +
+ ); + } + return isFlyoutView ? ( ) : ( diff --git a/x-pack/plugins/security_solution/public/timelines/components/side_panel/user_details/translations.ts b/x-pack/plugins/security_solution/public/timelines/components/side_panel/user_details/translations.ts new file mode 100644 index 0000000000000..3ee0484416e3a --- /dev/null +++ b/x-pack/plugins/security_solution/public/timelines/components/side_panel/user_details/translations.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 { i18n } from '@kbn/i18n'; + +export const CLOSE_BUTTON = i18n.translate( + 'xpack.securitySolution.timeline.userDetails.closeButton', + { + defaultMessage: 'close', + } +); diff --git a/x-pack/plugins/security_solution/public/timelines/components/side_panel/user_details/types.ts b/x-pack/plugins/security_solution/public/timelines/components/side_panel/user_details/types.ts index d745b89377ac0..4c22322888cc1 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/side_panel/user_details/types.ts +++ b/x-pack/plugins/security_solution/public/timelines/components/side_panel/user_details/types.ts @@ -11,4 +11,5 @@ export interface UserDetailsProps { handleOnClose: () => void; isFlyoutView?: boolean; isDraggable?: boolean; + isNewUserDetailsFlyoutEnable?: boolean; } diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/last_first_seen/query.first_or_last_seen.dsl.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/last_first_seen/query.first_or_last_seen.dsl.ts index ba2842b78e92b..1270f3f49909d 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/last_first_seen/query.first_or_last_seen.dsl.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/last_first_seen/query.first_or_last_seen.dsl.ts @@ -6,14 +6,16 @@ */ import type { FirstLastSeenRequestOptions } from '../../../../../common/search_strategy/security_solution/first_last_seen'; +import { createQueryFilterClauses } from '../../../../utils/build_query'; export const buildFirstOrLastSeenQuery = ({ field, value, defaultIndex, order, + filterQuery, }: FirstLastSeenRequestOptions) => { - const filter = [{ term: { [field]: value } }]; + const filter = [...createQueryFilterClauses(filterQuery), { term: { [field]: value } }]; const dslQuery = { allow_no_indices: true, diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/all/index.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/all/index.ts index c936ad85f79e0..2bdc6c6956633 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/all/index.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/all/index.ts @@ -42,7 +42,6 @@ export const allUsers: SecuritySolutionFactory = { deps?: { esClient: IScopedClusterClient; spaceId?: string; - // endpointContext: EndpointAppContext; } ): Promise => { const { activePage, cursorStart, fakePossibleCount, querySize } = options.pagination; diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/index.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/index.ts index a4f33213f73ab..ea4038e53730d 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/index.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/index.ts @@ -11,12 +11,14 @@ import { UsersQueries } from '../../../../../common/search_strategy/security_sol import type { SecuritySolutionFactory } from '../types'; import { allUsers } from './all'; import { authentications } from './authentications'; -import { userDetails } from './details'; +import { managedUserDetails } from './managed_details'; import { usersKpiAuthentications } from './kpi/authentications'; import { totalUsersKpi } from './kpi/total_users'; +import { observedUserDetails } from './observed_details'; export const usersFactory: Record> = { - [UsersQueries.details]: userDetails, + [UsersQueries.observedDetails]: observedUserDetails, + [UsersQueries.managedDetails]: managedUserDetails, [UsersQueries.kpiTotalUsers]: totalUsersKpi, [UsersQueries.users]: allUsers, [UsersQueries.authentications]: authentications, diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/managed_details/__snapshots__/index.test.tsx.snap b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/managed_details/__snapshots__/index.test.tsx.snap new file mode 100644 index 0000000000000..73388c583b607 --- /dev/null +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/managed_details/__snapshots__/index.test.tsx.snap @@ -0,0 +1,173 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`userDetails search strategy parse should parse data correctly 1`] = ` +Object { + "inspect": Object { + "dsl": Array [ + "{ + \\"allow_no_indices\\": true, + \\"index\\": [ + \\"logs-*\\" + ], + \\"ignore_unavailable\\": true, + \\"track_total_hits\\": false, + \\"body\\": { + \\"query\\": { + \\"bool\\": { + \\"filter\\": [ + { + \\"term\\": { + \\"user.name\\": \\"test-user-name\\" + } + }, + { + \\"term\\": { + \\"event.kind\\": \\"asset\\" + } + } + ] + } + }, + \\"size\\": 1 + }, + \\"sort\\": [ + { + \\"@timestamp\\": \\"desc\\" + } + ] +}", + ], + }, + "isPartial": false, + "isRunning": false, + "loaded": 21, + "rawResponse": Object { + "_shards": Object { + "failed": 0, + "skipped": 1, + "successful": 2, + "total": 2, + }, + "hits": Object { + "hits": Array [ + Object { + "_id": "9AxbIocB-WLv2258YZtS", + "_index": ".test", + "_score": null, + "_source": Object { + "@timestamp": "2023-02-23T20:03:17.489Z", + "agent": Object { + "ephemeral_id": "914fd1fa-aa37-4ab4-b36d-972ab9b19cde", + "id": "9528bb69-1511-4631-a5af-1d7e93c02009", + "name": "docker-fleet-agent", + "type": "filebeat", + "version": "8.8.0", + }, + "event": Object { + "action": "user-discovered", + "agent_id_status": "verified", + "dataset": "entityanalytics_azure.users", + "ingested": "2023-02-23T20:03:18Z", + "kind": "asset", + "provider": "Azure AD", + "type": Array [ + "user", + "info", + ], + }, + "host": Object { + "architecture": "x86_64", + "hostname": "docker-fleet-agent", + "id": "cff3d165179d4aef9596ddbb263e3adb", + "ip": Array [ + "172.26.0.7", + ], + "mac": Array [ + "02-42-AC-1A-00-07", + ], + "name": "docker-fleet-agent", + "os": Object { + "family": "debian", + "kernel": "5.10.47-linuxkit", + "name": "Ubuntu", + "platform": "ubuntu", + "type": "linux", + "version": "20.04.5 LTS (Focal Fossa)", + }, + }, + "user": Object { + "email": "tes.user@elastic.co", + "first_name": "Taylor", + "full_name": "Test user", + "id": "39fac578-91fb-47f6-8f7a-fab05ce70d8b", + "last_name": "Test last name", + "phone": Array [ + "1235559999", + ], + }, + }, + "sort": Array [ + 1677182597489, + ], + }, + ], + "max_score": null, + }, + "timed_out": false, + "took": 124, + }, + "total": 21, + "userDetails": Object { + "@timestamp": "2023-02-23T20:03:17.489Z", + "agent": Object { + "ephemeral_id": "914fd1fa-aa37-4ab4-b36d-972ab9b19cde", + "id": "9528bb69-1511-4631-a5af-1d7e93c02009", + "name": "docker-fleet-agent", + "type": "filebeat", + "version": "8.8.0", + }, + "event": Object { + "action": "user-discovered", + "agent_id_status": "verified", + "dataset": "entityanalytics_azure.users", + "ingested": "2023-02-23T20:03:18Z", + "kind": "asset", + "provider": "Azure AD", + "type": Array [ + "user", + "info", + ], + }, + "host": Object { + "architecture": "x86_64", + "hostname": "docker-fleet-agent", + "id": "cff3d165179d4aef9596ddbb263e3adb", + "ip": Array [ + "172.26.0.7", + ], + "mac": Array [ + "02-42-AC-1A-00-07", + ], + "name": "docker-fleet-agent", + "os": Object { + "family": "debian", + "kernel": "5.10.47-linuxkit", + "name": "Ubuntu", + "platform": "ubuntu", + "type": "linux", + "version": "20.04.5 LTS (Focal Fossa)", + }, + }, + "user": Object { + "email": "tes.user@elastic.co", + "first_name": "Taylor", + "full_name": "Test user", + "id": "39fac578-91fb-47f6-8f7a-fab05ce70d8b", + "last_name": "Test last name", + "phone": Array [ + "1235559999", + ], + }, + }, +} +`; diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/managed_details/__snapshots__/query.managed_user_details.dsl.test.ts.snap b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/managed_details/__snapshots__/query.managed_user_details.dsl.test.ts.snap new file mode 100644 index 0000000000000..12f5431941fd5 --- /dev/null +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/managed_details/__snapshots__/query.managed_user_details.dsl.test.ts.snap @@ -0,0 +1,36 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`buildManagedUserDetailsQuery build query from options correctly 1`] = ` +Object { + "allow_no_indices": true, + "body": Object { + "query": Object { + "bool": Object { + "filter": Array [ + Object { + "term": Object { + "user.name": "test-user-name", + }, + }, + Object { + "term": Object { + "event.kind": "asset", + }, + }, + ], + }, + }, + "size": 1, + }, + "ignore_unavailable": true, + "index": Array [ + "logs-*", + ], + "sort": Array [ + Object { + "@timestamp": "desc", + }, + ], + "track_total_hits": false, +} +`; diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/managed_details/index.test.tsx b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/managed_details/index.test.tsx new file mode 100644 index 0000000000000..cbd601efa90fa --- /dev/null +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/managed_details/index.test.tsx @@ -0,0 +1,112 @@ +/* + * Copyright 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 buildQuery from './query.managed_user_details.dsl'; +import { managedUserDetails } from '.'; +import type { + AzureManagedUser, + ManagedUserDetailsRequestOptions, +} from '../../../../../../common/search_strategy/security_solution/users/managed_details'; +import type { IEsSearchResponse } from '@kbn/data-plugin/public'; + +export const mockOptions: ManagedUserDetailsRequestOptions = { + defaultIndex: ['logs-*'], + userName: 'test-user-name', +}; + +export const mockSearchStrategyResponse: IEsSearchResponse = { + isPartial: false, + isRunning: false, + rawResponse: { + took: 124, + timed_out: false, + _shards: { + total: 2, + successful: 2, + skipped: 1, + failed: 0, + }, + hits: { + max_score: null, + hits: [ + { + _index: '.test', + _id: '9AxbIocB-WLv2258YZtS', + _score: null, + _source: { + agent: { + name: 'docker-fleet-agent', + id: '9528bb69-1511-4631-a5af-1d7e93c02009', + type: 'filebeat', + ephemeral_id: '914fd1fa-aa37-4ab4-b36d-972ab9b19cde', + version: '8.8.0', + }, + '@timestamp': '2023-02-23T20:03:17.489Z', + host: { + hostname: 'docker-fleet-agent', + os: { + kernel: '5.10.47-linuxkit', + name: 'Ubuntu', + type: 'linux', + family: 'debian', + version: '20.04.5 LTS (Focal Fossa)', + platform: 'ubuntu', + }, + ip: ['172.26.0.7'], + name: 'docker-fleet-agent', + id: 'cff3d165179d4aef9596ddbb263e3adb', + mac: ['02-42-AC-1A-00-07'], + architecture: 'x86_64', + }, + event: { + agent_id_status: 'verified', + ingested: '2023-02-23T20:03:18Z', + provider: 'Azure AD', + kind: 'asset', + action: 'user-discovered', + type: ['user', 'info'], + dataset: 'entityanalytics_azure.users', + }, + user: { + full_name: 'Test user', + phone: ['1235559999'], + last_name: 'Test last name', + id: '39fac578-91fb-47f6-8f7a-fab05ce70d8b', + first_name: 'Taylor', + email: 'tes.user@elastic.co', + }, + }, + sort: [1677182597489], + }, + ], + }, + }, + total: 21, + loaded: 21, +}; + +describe('userDetails search strategy', () => { + const buildManagedUserDetailsQuery = jest.spyOn(buildQuery, 'buildManagedUserDetailsQuery'); + + afterEach(() => { + buildManagedUserDetailsQuery.mockClear(); + }); + + describe('buildDsl', () => { + test('should build dsl query', () => { + managedUserDetails.buildDsl(mockOptions); + expect(buildManagedUserDetailsQuery).toHaveBeenCalledWith(mockOptions); + }); + }); + + describe('parse', () => { + test('should parse data correctly', async () => { + const result = await managedUserDetails.parse(mockOptions, mockSearchStrategyResponse); + expect(result).toMatchSnapshot(); + }); + }); +}); diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/managed_details/index.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/managed_details/index.ts new file mode 100644 index 0000000000000..def73159a4793 --- /dev/null +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/managed_details/index.ts @@ -0,0 +1,40 @@ +/* + * Copyright 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 { IEsSearchResponse } from '@kbn/data-plugin/common'; + +import { inspectStringifyObject } from '../../../../../utils/build_query'; +import type { SecuritySolutionFactory } from '../../types'; +import { buildManagedUserDetailsQuery } from './query.managed_user_details.dsl'; + +import type { UsersQueries } from '../../../../../../common/search_strategy/security_solution/users'; +import type { + AzureManagedUser, + ManagedUserDetailsRequestOptions, + ManagedUserDetailsStrategyResponse, +} from '../../../../../../common/search_strategy/security_solution/users/managed_details'; + +export const managedUserDetails: SecuritySolutionFactory = { + buildDsl: (options: ManagedUserDetailsRequestOptions) => buildManagedUserDetailsQuery(options), + parse: async ( + options: ManagedUserDetailsRequestOptions, + response: IEsSearchResponse + ): Promise => { + const inspect = { + dsl: [inspectStringifyObject(buildManagedUserDetailsQuery(options))], + }; + + const hits = response.rawResponse.hits.hits; + const userDetails = hits.length > 0 ? hits[0]._source : undefined; + + return { + ...response, + inspect, + userDetails, + }; + }, +}; diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/managed_details/query.managed_user_details.dsl.test.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/managed_details/query.managed_user_details.dsl.test.ts new file mode 100644 index 0000000000000..9f29bc98f287f --- /dev/null +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/managed_details/query.managed_user_details.dsl.test.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 type { ManagedUserDetailsRequestOptions } from '../../../../../../common/search_strategy/security_solution/users/managed_details'; +import { buildManagedUserDetailsQuery } from './query.managed_user_details.dsl'; + +export const mockOptions: ManagedUserDetailsRequestOptions = { + defaultIndex: ['logs-*'], + userName: 'test-user-name', +}; + +describe('buildManagedUserDetailsQuery', () => { + test('build query from options correctly', () => { + expect(buildManagedUserDetailsQuery(mockOptions)).toMatchSnapshot(); + }); +}); diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/managed_details/query.managed_user_details.dsl.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/managed_details/query.managed_user_details.dsl.ts new file mode 100644 index 0000000000000..8af2bbee0aa0a --- /dev/null +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/managed_details/query.managed_user_details.dsl.ts @@ -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 type { ISearchRequestParams } from '@kbn/data-plugin/common'; +import { EVENT_KIND_ASSET_FILTER } from '../../../../../../common/search_strategy'; +import type { ManagedUserDetailsRequestOptions } from '../../../../../../common/search_strategy/security_solution/users/managed_details'; + +export const buildManagedUserDetailsQuery = ({ + userName, + defaultIndex, +}: ManagedUserDetailsRequestOptions): ISearchRequestParams => { + const filter = [{ term: { 'user.name': userName } }, EVENT_KIND_ASSET_FILTER]; + + const dslQuery = { + allow_no_indices: true, + index: defaultIndex, + ignore_unavailable: true, + track_total_hits: false, + body: { + query: { bool: { filter } }, + size: 1, + }, + sort: [{ '@timestamp': 'desc' }], + }; + + return dslQuery; +}; diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/details/__mocks__/index.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/observed_details/__mocks__/index.ts similarity index 91% rename from x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/details/__mocks__/index.ts rename to x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/observed_details/__mocks__/index.ts index 5ec7aeea9e117..9cf6a6089e21e 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/details/__mocks__/index.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/observed_details/__mocks__/index.ts @@ -8,11 +8,11 @@ import type { IEsSearchResponse } from '@kbn/data-plugin/common'; import { UsersQueries } from '../../../../../../../common/search_strategy/security_solution/users'; -import type { UserDetailsRequestOptions } from '../../../../../../../common/search_strategy/security_solution/users/details'; +import type { ObservedUserDetailsRequestOptions } from '../../../../../../../common/search_strategy/security_solution/users/observed_details'; -export const mockOptions: UserDetailsRequestOptions = { +export const mockOptions: ObservedUserDetailsRequestOptions = { defaultIndex: ['test_indices*'], - factoryQueryType: UsersQueries.details, + factoryQueryType: UsersQueries.observedDetails, filterQuery: '{"bool":{"must":[],"filter":[{"match_all":{}},{"match_phrase":{"user.name":{"query":"test_user"}}}],"should":[],"must_not":[]}}', timerange: { @@ -22,7 +22,7 @@ export const mockOptions: UserDetailsRequestOptions = { }, params: {}, userName: 'bastion00.siem.estc.dev', -} as UserDetailsRequestOptions; +} as ObservedUserDetailsRequestOptions; export const mockSearchStrategyResponse: IEsSearchResponse = { rawResponse: { @@ -125,14 +125,6 @@ export const mockSearchStrategyResponse: IEsSearchResponse = { }, ], }, - first_seen: { - value: 1644837532000, - value_as_string: '2022-02-14T11:18:52.000Z', - }, - last_seen: { - value: 1644837532000, - value_as_string: '2022-02-14T11:18:52.000Z', - }, user_domain: { doc_count_error_upper_bound: 0, sum_other_doc_count: 0, diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/details/__snapshots__/index.test.tsx.snap b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/observed_details/__snapshots__/index.test.tsx.snap similarity index 94% rename from x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/details/__snapshots__/index.test.tsx.snap rename to x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/observed_details/__snapshots__/index.test.tsx.snap index 2dcdb43bef47c..915b71e05cc3e 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/details/__snapshots__/index.test.tsx.snap +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/observed_details/__snapshots__/index.test.tsx.snap @@ -116,6 +116,25 @@ Object { \\"query\\": { \\"bool\\": { \\"filter\\": [ + { + \\"bool\\": { + \\"must\\": [], + \\"filter\\": [ + { + \\"match_all\\": {} + }, + { + \\"match_phrase\\": { + \\"user.name\\": { + \\"query\\": \\"test_user\\" + } + } + } + ], + \\"should\\": [], + \\"must_not\\": [] + } + }, { \\"term\\": { \\"user.name\\": \\"bastion00.siem.estc.dev\\" @@ -149,10 +168,6 @@ Object { "total": 2, }, "aggregations": Object { - "first_seen": Object { - "value": 1644837532000, - "value_as_string": "2022-02-14T11:18:52.000Z", - }, "host_ip": Object { "buckets": Array [ Object { @@ -267,10 +282,6 @@ Object { "doc_count_error_upper_bound": 0, "sum_other_doc_count": 0, }, - "last_seen": Object { - "value": 1644837532000, - "value_as_string": "2022-02-14T11:18:52.000Z", - }, "user_domain": Object { "buckets": Array [ Object { @@ -323,7 +334,6 @@ Object { }, "total": 2, "userDetails": Object { - "firstSeen": "2022-02-14T11:18:52.000Z", "host": Object { "ip": Array [ "11.245.5.152", @@ -346,7 +356,6 @@ Object { ], }, }, - "lastSeen": "2022-02-14T11:18:52.000Z", "user": Object { "domain": Array [ "NT AUTHORITY", diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/details/__snapshots__/query.user_details.dsl.test.ts.snap b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/observed_details/__snapshots__/query.observed_user_details.dsl.test.ts.snap similarity index 85% rename from x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/details/__snapshots__/query.user_details.dsl.test.ts.snap rename to x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/observed_details/__snapshots__/query.observed_user_details.dsl.test.ts.snap index 2cbfa200b3d6b..0815b6851c936 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/details/__snapshots__/query.user_details.dsl.test.ts.snap +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/observed_details/__snapshots__/query.observed_user_details.dsl.test.ts.snap @@ -108,6 +108,25 @@ Object { "query": Object { "bool": Object { "filter": Array [ + Object { + "bool": Object { + "filter": Array [ + Object { + "match_all": Object {}, + }, + Object { + "match_phrase": Object { + "user.name": Object { + "query": "test_user", + }, + }, + }, + ], + "must": Array [], + "must_not": Array [], + "should": Array [], + }, + }, Object { "term": Object { "user.name": "bastion00.siem.estc.dev", diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/details/helper.test.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/observed_details/helper.test.ts similarity index 85% rename from x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/details/helper.test.ts rename to x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/observed_details/helper.test.ts index b8d6b442483b3..b36e8bc9b1cc8 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/details/helper.test.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/observed_details/helper.test.ts @@ -24,13 +24,9 @@ describe('helpers', () => { }, ], }, - first_seen: { value_as_string: '123456789' }, - last_seen: { value_as_string: '987654321' }, }; expect(formatUserItem(aggregations)).toEqual({ - firstSeen: '123456789', - lastSeen: '987654321', user: { id: [userId] }, }); }); diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/details/helpers.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/observed_details/helpers.ts similarity index 86% rename from x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/details/helpers.ts rename to x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/observed_details/helpers.ts index f6c9f1d194215..14ba6c3496290 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/details/helpers.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/observed_details/helpers.ts @@ -25,11 +25,6 @@ export const USER_FIELDS = [ export const fieldNameToAggField = (fieldName: string) => fieldName.replace(/\./g, '_'); export const formatUserItem = (aggregations: UserAggEsItem): UserItem => { - const firstLastSeen = { - firstSeen: get('first_seen.value_as_string', aggregations), - lastSeen: get('last_seen.value_as_string', aggregations), - }; - return USER_FIELDS.reduce((flattenedFields, fieldName) => { const aggField = fieldNameToAggField(fieldName); @@ -40,5 +35,5 @@ export const formatUserItem = (aggregations: UserAggEsItem): UserItem => { return set(fieldName, fieldValue, flattenedFields); } return flattenedFields; - }, firstLastSeen); + }, {}); }; diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/details/index.test.tsx b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/observed_details/index.test.tsx similarity index 59% rename from x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/details/index.test.tsx rename to x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/observed_details/index.test.tsx index bd23c37ff24f9..9666615297f0d 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/details/index.test.tsx +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/observed_details/index.test.tsx @@ -5,27 +5,27 @@ * 2.0. */ -import * as buildQuery from './query.user_details.dsl'; -import { userDetails } from '.'; +import * as buildQuery from './query.observed_user_details.dsl'; +import { observedUserDetails } from '.'; import { mockOptions, mockSearchStrategyResponse } from './__mocks__'; describe('userDetails search strategy', () => { - const buildHostDetailsQuery = jest.spyOn(buildQuery, 'buildUserDetailsQuery'); + const buildUserDetailsQuery = jest.spyOn(buildQuery, 'buildObservedUserDetailsQuery'); afterEach(() => { - buildHostDetailsQuery.mockClear(); + buildUserDetailsQuery.mockClear(); }); describe('buildDsl', () => { test('should build dsl query', () => { - userDetails.buildDsl(mockOptions); - expect(buildHostDetailsQuery).toHaveBeenCalledWith(mockOptions); + observedUserDetails.buildDsl(mockOptions); + expect(buildUserDetailsQuery).toHaveBeenCalledWith(mockOptions); }); }); describe('parse', () => { test('should parse data correctly', async () => { - const result = await userDetails.parse(mockOptions, mockSearchStrategyResponse); + const result = await observedUserDetails.parse(mockOptions, mockSearchStrategyResponse); expect(result).toMatchSnapshot(); }); }); diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/details/index.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/observed_details/index.ts similarity index 64% rename from x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/details/index.ts rename to x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/observed_details/index.ts index c57c5a21f6c77..7fd64694fc198 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/details/index.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/observed_details/index.ts @@ -9,25 +9,25 @@ import type { IEsSearchResponse } from '@kbn/data-plugin/common'; import { inspectStringifyObject } from '../../../../../utils/build_query'; import type { SecuritySolutionFactory } from '../../types'; -import { buildUserDetailsQuery } from './query.user_details.dsl'; +import { buildObservedUserDetailsQuery } from './query.observed_user_details.dsl'; import type { UsersQueries } from '../../../../../../common/search_strategy/security_solution/users'; import type { - UserDetailsRequestOptions, - UserDetailsStrategyResponse, -} from '../../../../../../common/search_strategy/security_solution/users/details'; + ObservedUserDetailsRequestOptions, + ObservedUserDetailsStrategyResponse, +} from '../../../../../../common/search_strategy/security_solution/users/observed_details'; import { formatUserItem } from './helpers'; -export const userDetails: SecuritySolutionFactory = { - buildDsl: (options: UserDetailsRequestOptions) => buildUserDetailsQuery(options), +export const observedUserDetails: SecuritySolutionFactory = { + buildDsl: (options: ObservedUserDetailsRequestOptions) => buildObservedUserDetailsQuery(options), parse: async ( - options: UserDetailsRequestOptions, + options: ObservedUserDetailsRequestOptions, response: IEsSearchResponse - ): Promise => { + ): Promise => { const aggregations = response.rawResponse.aggregations; const inspect = { - dsl: [inspectStringifyObject(buildUserDetailsQuery(options))], + dsl: [inspectStringifyObject(buildObservedUserDetailsQuery(options))], }; if (aggregations == null) { diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/details/query.user_details.dsl.test.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/observed_details/query.observed_user_details.dsl.test.ts similarity index 71% rename from x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/details/query.user_details.dsl.test.ts rename to x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/observed_details/query.observed_user_details.dsl.test.ts index 394890ad163e1..82943d6257a32 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/details/query.user_details.dsl.test.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/observed_details/query.observed_user_details.dsl.test.ts @@ -5,11 +5,11 @@ * 2.0. */ -import { buildUserDetailsQuery } from './query.user_details.dsl'; +import { buildObservedUserDetailsQuery } from './query.observed_user_details.dsl'; import { mockOptions } from './__mocks__'; describe('buildUserDetailsQuery', () => { test('build query from options correctly', () => { - expect(buildUserDetailsQuery(mockOptions)).toMatchSnapshot(); + expect(buildObservedUserDetailsQuery(mockOptions)).toMatchSnapshot(); }); }); diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/details/query.user_details.dsl.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/observed_details/query.observed_user_details.dsl.ts similarity index 65% rename from x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/details/query.user_details.dsl.ts rename to x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/observed_details/query.observed_user_details.dsl.ts index 93c3624b8597d..efbf49231486e 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/details/query.user_details.dsl.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/observed_details/query.observed_user_details.dsl.ts @@ -5,17 +5,21 @@ * 2.0. */ +import type { QueryDslQueryContainer } from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import type { ISearchRequestParams } from '@kbn/data-plugin/common'; -import type { UserDetailsRequestOptions } from '../../../../../../common/search_strategy/security_solution/users/details'; +import type { ObservedUserDetailsRequestOptions } from '../../../../../../common/search_strategy/security_solution/users/observed_details'; +import { createQueryFilterClauses } from '../../../../../utils/build_query'; import { buildFieldsTermAggregation } from '../../hosts/details/helpers'; import { USER_FIELDS } from './helpers'; -export const buildUserDetailsQuery = ({ +export const buildObservedUserDetailsQuery = ({ userName, defaultIndex, timerange: { from, to }, -}: UserDetailsRequestOptions): ISearchRequestParams => { - const filter = [ + filterQuery, +}: ObservedUserDetailsRequestOptions): ISearchRequestParams => { + const filter: QueryDslQueryContainer[] = [ + ...createQueryFilterClauses(filterQuery), { term: { 'user.name': userName } }, { range: { diff --git a/x-pack/plugins/security_solution/tsconfig.json b/x-pack/plugins/security_solution/tsconfig.json index 4d179e84f6dcb..bfc45e92c36a5 100644 --- a/x-pack/plugins/security_solution/tsconfig.json +++ b/x-pack/plugins/security_solution/tsconfig.json @@ -152,6 +152,7 @@ "@kbn/core-analytics-server", "@kbn/analytics-client", "@kbn/security-solution-side-nav", - "@kbn/core-lifecycle-browser" + "@kbn/core-lifecycle-browser", + "@kbn/ecs", ] } From ad2d5adf4347e9323ed318db790f53efd61a0d28 Mon Sep 17 00:00:00 2001 From: Xavier Mouligneau Date: Tue, 18 Apr 2023 11:02:25 -0400 Subject: [PATCH 03/78] [RAM] ResponseOps saved object updates (#152857) ## Summary This PR is just the first phase for response ops to go through their saved object attributes. The idea is to comment out all the attributes that we all agree that we do not need to filter/search/sort/aggregate on. After, in a second phase/PR, we will create a new file who will represent all of attributes in our saved object as a source of truth. Then, we will generate our SO mappings from this source of truth to register our saved object. Phase 3, we will try to generate also our type from our source of truth. --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> --- .../group2/check_registered_types.test.ts | 12 +- .../actions/server/saved_objects/index.ts | 9 +- .../server/saved_objects/mappings.json | 74 ---------- .../actions/server/saved_objects/mappings.ts | 93 +++++++++++++ .../alerting/server/saved_objects/mappings.ts | 128 +++++++++--------- .../saved_objects/rules_settings_mappings.ts | 58 ++++---- .../server/saved_objects/index.ts | 6 +- .../server/saved_objects/mappings.json | 55 -------- .../server/saved_objects/mappings.ts | 66 +++++++++ .../task_manager/task_management.ts | 6 +- 10 files changed, 271 insertions(+), 236 deletions(-) delete mode 100644 x-pack/plugins/actions/server/saved_objects/mappings.json create mode 100644 x-pack/plugins/actions/server/saved_objects/mappings.ts delete mode 100644 x-pack/plugins/task_manager/server/saved_objects/mappings.json create mode 100644 x-pack/plugins/task_manager/server/saved_objects/mappings.ts diff --git a/src/core/server/integration_tests/saved_objects/migrations/group2/check_registered_types.test.ts b/src/core/server/integration_tests/saved_objects/migrations/group2/check_registered_types.test.ts index 09977e1c272ab..4ef2e37cad8e6 100644 --- a/src/core/server/integration_tests/saved_objects/migrations/group2/check_registered_types.test.ts +++ b/src/core/server/integration_tests/saved_objects/migrations/group2/check_registered_types.test.ts @@ -55,9 +55,9 @@ describe('checking migration metadata changes on all registered SO types', () => expect(hashMap).toMatchInlineSnapshot(` Object { - "action": "6cfc277ed3211639e37546ac625f4a68f2494215", - "action_task_params": "5f419caba96dd8c77d0f94013e71d43890e3d5d6", - "alert": "7bec97d7775a025ecf36a33baf17386b9e7b4c3c", + "action": "12c6b25ef1fffb36d8de893318f8a2bc5d6a46a6", + "action_task_params": "c725c37de66135934150465f9b1e76fe349e8a23", + "alert": "0cd1f1921014004a9ff5c0a9333ca9bde14bf748", "api_key_pending_invalidation": "16e7bcf8e78764102d7f525542d5b616809a21ee", "apm-indices": "d19dd7fb51f2d2cbc1f8769481721e0953f9a6d2", "apm-server-schema": "1d42f17eff9ec6c16d3a9324d9539e2d123d0a9a", @@ -77,7 +77,7 @@ describe('checking migration metadata changes on all registered SO types', () => "cases-user-actions": "8ad74294b71edffa58fad7a40eea2388209477c9", "config": "97e16b8f5dc10c404fd3b201ef36bc6c3c63dc80", "config-global": "d9791e8f73edee884630e1cb6e4954ae513ce75e", - "connector_token": "fb05ff5afdcb6e2f20c9c6513ff7a1ab12b66f36", + "connector_token": "aff1aa0ebc0a6b44b570057972af25178b0bca42", "core-usage-stats": "b3c04da317c957741ebcdedfea4524049fdc79ff", "csp-rule-template": "099c229bf97578d9ca72b3a672d397559b84ee0b", "dashboard": "71e3f8dfcffeb5fbd410dec81ce46f5691763c43", @@ -124,7 +124,7 @@ describe('checking migration metadata changes on all registered SO types', () => "osquery-pack-asset": "18e08979d46ee7e5538f54c080aec4d8c58516ca", "osquery-saved-query": "f5e4e303f65c7607248ea8b2672f1ee30e4fb15e", "query": "cfc049e1f0574fb4fdb2d653d7c10bdc970a2610", - "rules-settings": "9854495c3b54b16a6625fb250c35e5504da72266", + "rules-settings": "eb8d40b7d60aeffe66831f7d5d83de28d85776d8", "sample-data-telemetry": "c38daf1a49ed24f2a4fb091e6e1e833fccf19935", "search": "ed3a9b1681b57d69560909d51933fdf17576ea68", "search-session": "fae0dfc63274d6a3b90ca583802c48cab8760637", @@ -142,7 +142,7 @@ describe('checking migration metadata changes on all registered SO types', () => "synthetics-param": "9776c9b571d35f0d0397e8915e035ea1dc026db7", "synthetics-privates-locations": "7d032fc788905e32152029ae7ab3d6038c48ae44", "tag": "87f21f07df9cc37001b15a26e413c18f50d1fbfe", - "task": "ff760534a44c4cfabcf4baf8cfe8283f717cab02", + "task": "533ee80c50c47f0505846bfac73fc10962c5bc45", "telemetry": "561b329aaed3c15b91aaf2075645be3097247612", "ui-metric": "410a8ad28e0f44b161c960ff0ce950c712b17c52", "upgrade-assistant-ml-upgrade-operation": "d8816e5ce32649e7a3a43e2c406c632319ff84bb", diff --git a/x-pack/plugins/actions/server/saved_objects/index.ts b/x-pack/plugins/actions/server/saved_objects/index.ts index 27850b6945a21..a110320586750 100644 --- a/x-pack/plugins/actions/server/saved_objects/index.ts +++ b/x-pack/plugins/actions/server/saved_objects/index.ts @@ -9,11 +9,10 @@ import type { SavedObject, SavedObjectsExportTransformContext, SavedObjectsServiceSetup, - SavedObjectsTypeMappingDefinition, } from '@kbn/core/server'; import { EncryptedSavedObjectsPluginSetup } from '@kbn/encrypted-saved-objects-plugin/server'; import { getOldestIdleActionTask } from '@kbn/task-manager-plugin/server'; -import mappings from './mappings.json'; +import { actionMappings, actionTaskParamsMappings, connectorTokenMappings } from './mappings'; import { getActionsMigrations } from './actions_migrations'; import { getActionTaskParamsMigrations } from './action_task_params_migrations'; import { PreConfiguredAction, RawAction } from '../types'; @@ -38,7 +37,7 @@ export function setupSavedObjects( hidden: true, namespaceType: 'multiple-isolated', convertToMultiNamespaceTypeVersion: '8.0.0', - mappings: mappings.action as SavedObjectsTypeMappingDefinition, + mappings: actionMappings, migrations: getActionsMigrations(encryptedSavedObjects), management: { displayName: 'connector', @@ -76,7 +75,7 @@ export function setupSavedObjects( hidden: true, namespaceType: 'multiple-isolated', convertToMultiNamespaceTypeVersion: '8.0.0', - mappings: mappings.action_task_params as SavedObjectsTypeMappingDefinition, + mappings: actionTaskParamsMappings, migrations: getActionTaskParamsMigrations(encryptedSavedObjects, preconfiguredActions), excludeOnUpgrade: async ({ readonlyEsClient }) => { const oldestIdleActionTask = await getOldestIdleActionTask( @@ -102,7 +101,7 @@ export function setupSavedObjects( name: CONNECTOR_TOKEN_SAVED_OBJECT_TYPE, hidden: true, namespaceType: 'agnostic', - mappings: mappings.connector_token as SavedObjectsTypeMappingDefinition, + mappings: connectorTokenMappings, management: { importableAndExportable: false, }, diff --git a/x-pack/plugins/actions/server/saved_objects/mappings.json b/x-pack/plugins/actions/server/saved_objects/mappings.json deleted file mode 100644 index be0a691f852b2..0000000000000 --- a/x-pack/plugins/actions/server/saved_objects/mappings.json +++ /dev/null @@ -1,74 +0,0 @@ -{ - "action": { - "properties": { - "name": { - "type": "text", - "fields": { - "keyword": { - "type": "keyword" - } - } - }, - "actionTypeId": { - "type": "keyword" - }, - "isMissingSecrets": { - "type": "boolean" - }, - "config": { - "enabled": false, - "type": "object" - }, - "secrets": { - "type": "binary" - } - } - }, - "action_task_params": { - "dynamic": false, - "properties": { - "actionId": { - "type": "keyword" - }, - "consumer": { - "type": "keyword" - }, - "params": { - "enabled": false, - "type": "object" - }, - "apiKey": { - "type": "binary" - }, - "executionId": { - "type": "keyword" - }, - "relatedSavedObjects": { - "enabled": false, - "type": "object" - } - } - }, - "connector_token": { - "properties": { - "connectorId": { - "type": "keyword" - }, - "tokenType": { - "type": "keyword" - }, - "token": { - "type": "binary" - }, - "expiresAt": { - "type": "date" - }, - "createdAt": { - "type": "date" - }, - "updatedAt": { - "type": "date" - } - } - } -} diff --git a/x-pack/plugins/actions/server/saved_objects/mappings.ts b/x-pack/plugins/actions/server/saved_objects/mappings.ts new file mode 100644 index 0000000000000..70e3e7447a6a0 --- /dev/null +++ b/x-pack/plugins/actions/server/saved_objects/mappings.ts @@ -0,0 +1,93 @@ +/* + * Copyright 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 { SavedObjectsTypeMappingDefinition } from '@kbn/core/server'; + +export const actionMappings: SavedObjectsTypeMappingDefinition = { + dynamic: false, + properties: { + name: { + type: 'text', + fields: { + keyword: { + type: 'keyword', + }, + }, + }, + actionTypeId: { + type: 'keyword', + }, + // NO NEED TO BE INDEXED + // isMissingSecrets: { + // type: 'boolean', + // }, + // NO NEED TO BE INDEXED + // config: { + // enabled: false, + // type: 'object', + // }, + // NEED TO CHECK WITH KIBANA SECURITY + // secrets: { + // type: 'binary', + // }, + }, +}; + +export const actionTaskParamsMappings: SavedObjectsTypeMappingDefinition = { + dynamic: false, + properties: { + // NO NEED TO BE INDEXED + // actionId: { + // type: 'keyword', + // }, + // consumer: { + // type: 'keyword', + // }, + // params: { + // enabled: false, + // type: 'object', + // }, + // apiKey: { + // type: 'binary', + // }, + // executionId: { + // type: 'keyword', + // }, + // relatedSavedObjects: { + // enabled: false, + // type: 'object', + // }, + // source: { + // type: 'keyword' + // } + }, +}; + +export const connectorTokenMappings: SavedObjectsTypeMappingDefinition = { + dynamic: false, + properties: { + connectorId: { + type: 'keyword', + }, + tokenType: { + type: 'keyword', + }, + // NO NEED TO BE INDEXED + // token: { + // type: 'binary', + // }, + // expiresAt: { + // type: 'date', + // }, + // createdAt: { + // type: 'date', + // }, + // updatedAt: { + // type: 'date', + // }, + }, +}; diff --git a/x-pack/plugins/alerting/server/saved_objects/mappings.ts b/x-pack/plugins/alerting/server/saved_objects/mappings.ts index 9ee204bb53896..2cec5bdd449e6 100644 --- a/x-pack/plugins/alerting/server/saved_objects/mappings.ts +++ b/x-pack/plugins/alerting/server/saved_objects/mappings.ts @@ -6,7 +6,6 @@ */ import { SavedObjectsTypeMappingDefinition } from '@kbn/core/server'; -import { rRuleMappingsField } from './rrule_mappings_field'; export const alertMappings: SavedObjectsTypeMappingDefinition = { dynamic: false, @@ -55,26 +54,27 @@ export const alertMappings: SavedObjectsTypeMappingDefinition = { actionTypeId: { type: 'keyword', }, - params: { - dynamic: false, - properties: {}, - }, - frequency: { - properties: { - summary: { - index: false, - type: 'boolean', - }, - notifyWhen: { - index: false, - type: 'keyword', - }, - throttle: { - index: false, - type: 'keyword', - }, - }, - }, + // NO NEED TO BE INDEXED + // params: { + // dynamic: false, + // properties: {}, + // }, + // frequency: { + // properties: { + // summary: { + // index: false, + // type: 'boolean', + // }, + // notifyWhen: { + // index: false, + // type: 'keyword', + // }, + // throttle: { + // index: false, + // type: 'keyword', + // }, + // }, + // }, }, }, params: { @@ -106,12 +106,14 @@ export const alertMappings: SavedObjectsTypeMappingDefinition = { updatedAt: { type: 'date', }, - apiKey: { - type: 'binary', - }, - apiKeyOwner: { - type: 'keyword', - }, + // NEED TO CHECK WITH KIBANA SECURITY + // apiKey: { + // type: 'binary', + // }, + // NO NEED TO BE INDEXED + // apiKeyOwner: { + // type: 'keyword', + // }, throttle: { type: 'keyword', }, @@ -124,33 +126,34 @@ export const alertMappings: SavedObjectsTypeMappingDefinition = { mutedInstanceIds: { type: 'keyword', }, - meta: { - properties: { - versionApiKeyLastmodified: { - type: 'keyword', - }, - }, - }, + // NO NEED TO BE INDEXED + // meta: { + // properties: { + // versionApiKeyLastmodified: { + // type: 'keyword', + // }, + // }, + // }, monitoring: { properties: { run: { properties: { - history: { - properties: { - duration: { - type: 'long', - }, - success: { - type: 'boolean', - }, - timestamp: { - type: 'date', - }, - outcome: { - type: 'keyword', - }, - }, - }, + // history: { + // properties: { + // duration: { + // type: 'long', + // }, + // success: { + // type: 'boolean', + // }, + // timestamp: { + // type: 'date', + // }, + // outcome: { + // type: 'keyword', + // }, + // }, + // }, calculated_metrics: { properties: { p50: { @@ -200,8 +203,8 @@ export const alertMappings: SavedObjectsTypeMappingDefinition = { }, }, }, + // There is need to query for a rule by a specific revision revision: { - index: true, // Explicitly setting to `true` as there is need to query for a rule by a specific revision type: 'long', }, snoozeSchedule: { @@ -217,12 +220,14 @@ export const alertMappings: SavedObjectsTypeMappingDefinition = { type: 'date', format: 'strict_date_time', }, - rRule: rRuleMappingsField, + // NO NEED TO BE INDEXED + // rRule: rRuleMappingsField, }, }, - nextRun: { - type: 'date', - }, + // NO NEED TO BE INDEXED + // nextRun: { + // type: 'date', + // }, // Deprecated, if you need to add new property please do it in `last_run` executionStatus: { properties: { @@ -268,12 +273,13 @@ export const alertMappings: SavedObjectsTypeMappingDefinition = { outcomeOrder: { type: 'float', }, - warning: { - type: 'text', - }, - outcomeMsg: { - type: 'text', - }, + // NO NEED TO BE INDEXED + // warning: { + // type: 'text', + // }, + // outcomeMsg: { + // type: 'text', + // }, alertsCount: { properties: { active: { diff --git a/x-pack/plugins/alerting/server/saved_objects/rules_settings_mappings.ts b/x-pack/plugins/alerting/server/saved_objects/rules_settings_mappings.ts index d20567edc2832..3f363a39e35fa 100644 --- a/x-pack/plugins/alerting/server/saved_objects/rules_settings_mappings.ts +++ b/x-pack/plugins/alerting/server/saved_objects/rules_settings_mappings.ts @@ -8,37 +8,39 @@ import { SavedObjectsTypeMappingDefinition } from '@kbn/core/server'; export const rulesSettingsMappings: SavedObjectsTypeMappingDefinition = { + dynamic: false, properties: { flapping: { properties: { - enabled: { - type: 'boolean', - index: false, - }, - lookBackWindow: { - type: 'long', - index: false, - }, - statusChangeThreshold: { - type: 'long', - index: false, - }, - createdBy: { - type: 'keyword', - index: false, - }, - updatedBy: { - type: 'keyword', - index: false, - }, - createdAt: { - type: 'date', - index: false, - }, - updatedAt: { - type: 'date', - index: false, - }, + // NO NEED TO BE INDEXED + // enabled: { + // type: 'boolean', + // index: false, + // }, + // lookBackWindow: { + // type: 'long', + // index: false, + // }, + // statusChangeThreshold: { + // type: 'long', + // index: false, + // }, + // createdBy: { + // type: 'keyword', + // index: false, + // }, + // updatedBy: { + // type: 'keyword', + // index: false, + // }, + // createdAt: { + // type: 'date', + // index: false, + // }, + // updatedAt: { + // type: 'date', + // index: false, + // }, }, }, }, diff --git a/x-pack/plugins/task_manager/server/saved_objects/index.ts b/x-pack/plugins/task_manager/server/saved_objects/index.ts index 2418346234a29..53d09d4baf131 100644 --- a/x-pack/plugins/task_manager/server/saved_objects/index.ts +++ b/x-pack/plugins/task_manager/server/saved_objects/index.ts @@ -5,9 +5,9 @@ * 2.0. */ -import type { SavedObjectsServiceSetup, SavedObjectsTypeMappingDefinition } from '@kbn/core/server'; +import type { SavedObjectsServiceSetup } from '@kbn/core/server'; import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; -import mappings from './mappings.json'; +import { taskMappings } from './mappings'; import { getMigrations } from './migrations'; import { TaskManagerConfig } from '../config'; import { getOldestIdleActionTask } from '../queries/oldest_idle_action_task'; @@ -22,7 +22,7 @@ export function setupSavedObjects( namespaceType: 'agnostic', hidden: true, convertToAliasScript: `ctx._id = ctx._source.type + ':' + ctx._id; ctx._source.remove("kibana")`, - mappings: mappings.task as SavedObjectsTypeMappingDefinition, + mappings: taskMappings, migrations: getMigrations(), indexPattern: TASK_MANAGER_INDEX, excludeOnUpgrade: async ({ readonlyEsClient }) => { diff --git a/x-pack/plugins/task_manager/server/saved_objects/mappings.json b/x-pack/plugins/task_manager/server/saved_objects/mappings.json deleted file mode 100644 index 00129ac1bcdd4..0000000000000 --- a/x-pack/plugins/task_manager/server/saved_objects/mappings.json +++ /dev/null @@ -1,55 +0,0 @@ -{ - "task": { - "properties": { - "taskType": { - "type": "keyword" - }, - "scheduledAt": { - "type": "date" - }, - "runAt": { - "type": "date" - }, - "startedAt": { - "type": "date" - }, - "retryAt": { - "type": "date" - }, - "enabled": { - "type": "boolean" - }, - "schedule": { - "properties": { - "interval": { - "type": "keyword" - } - } - }, - "attempts": { - "type": "integer" - }, - "status": { - "type": "keyword" - }, - "traceparent": { - "type": "text" - }, - "params": { - "type": "text" - }, - "state": { - "type": "text" - }, - "user": { - "type": "keyword" - }, - "scope": { - "type": "keyword" - }, - "ownerId": { - "type": "keyword" - } - } - } -} diff --git a/x-pack/plugins/task_manager/server/saved_objects/mappings.ts b/x-pack/plugins/task_manager/server/saved_objects/mappings.ts new file mode 100644 index 0000000000000..df38c78309e39 --- /dev/null +++ b/x-pack/plugins/task_manager/server/saved_objects/mappings.ts @@ -0,0 +1,66 @@ +/* + * Copyright 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 { SavedObjectsTypeMappingDefinition } from '@kbn/core/server'; + +export const taskMappings: SavedObjectsTypeMappingDefinition = { + dynamic: false, + properties: { + taskType: { + type: 'keyword', + }, + scheduledAt: { + type: 'date', + }, + runAt: { + type: 'date', + }, + // NO NEED TO BE INDEXED + // startedAt: { + // type: 'date', + // }, + retryAt: { + type: 'date', + }, + enabled: { + type: 'boolean', + }, + schedule: { + properties: { + interval: { + type: 'keyword', + }, + }, + }, + attempts: { + type: 'integer', + }, + status: { + type: 'keyword', + }, + // NO NEED TO BE INDEXED + // traceparent: { + // type: 'text', + // }, + // params: { + // type: 'text', + // }, + // state: { + // type: 'text', + // }, + // NO NEED TO BE INDEXED + // user: { + // type: 'keyword', + // }, + scope: { + type: 'keyword', + }, + ownerId: { + type: 'keyword', + }, + }, +}; diff --git a/x-pack/test/plugin_api_integration/test_suites/task_manager/task_management.ts b/x-pack/test/plugin_api_integration/test_suites/task_manager/task_management.ts index 987a498d420d2..f897e0fa05035 100644 --- a/x-pack/test/plugin_api_integration/test_suites/task_manager/task_management.ts +++ b/x-pack/test/plugin_api_integration/test_suites/task_manager/task_management.ts @@ -9,13 +9,11 @@ import moment from 'moment'; import { random } from 'lodash'; import expect from '@kbn/expect'; import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; -import TaskManagerMapping from '@kbn/task-manager-plugin/server/saved_objects/mappings.json'; +import { taskMappings as TaskManagerMapping } from '@kbn/task-manager-plugin/server/saved_objects/mappings'; import { ConcreteTaskInstance, BulkUpdateTaskResult } from '@kbn/task-manager-plugin/server'; import { FtrProviderContext } from '../../ftr_provider_context'; -const { - task: { properties: taskManagerIndexMapping }, -} = TaskManagerMapping; +const { properties: taskManagerIndexMapping } = TaskManagerMapping; export interface RawDoc { _id: string; From 7172905d632e8d7c695725043a7bc91887309e31 Mon Sep 17 00:00:00 2001 From: Ioana Tagirta Date: Tue, 18 Apr 2023 17:08:08 +0200 Subject: [PATCH 04/78] Exclude metadata fields from schema page (#155147) Excludes metadata fields from schema page. Given a mapping with 2 fields: ``` { "books": { "mappings": { "properties": { "author": { "type": "text", "fields": { "keyword": { "type": "keyword", "ignore_above": 256 } } }, "title": { "type": "text", "fields": { "keyword": { "type": "keyword", "ignore_above": 256 } } } } } } } ``` before: Screenshot 2023-04-18 at 14 25 42 after: Screenshot 2023-04-18 at 14 13 25 --- .../server/lib/engines/field_capabilities.test.ts | 1 + .../enterprise_search/server/lib/engines/field_capabilities.ts | 1 + 2 files changed, 2 insertions(+) diff --git a/x-pack/plugins/enterprise_search/server/lib/engines/field_capabilities.test.ts b/x-pack/plugins/enterprise_search/server/lib/engines/field_capabilities.test.ts index d01c5bd9f0d79..f69d08b250af9 100644 --- a/x-pack/plugins/enterprise_search/server/lib/engines/field_capabilities.test.ts +++ b/x-pack/plugins/enterprise_search/server/lib/engines/field_capabilities.test.ts @@ -72,6 +72,7 @@ describe('engines field_capabilities', () => { fields: '*', include_unmapped: true, index: ['index-001'], + filters: '-metadata', }); }); }); diff --git a/x-pack/plugins/enterprise_search/server/lib/engines/field_capabilities.ts b/x-pack/plugins/enterprise_search/server/lib/engines/field_capabilities.ts index fd1fb28aa23ca..2494004919714 100644 --- a/x-pack/plugins/enterprise_search/server/lib/engines/field_capabilities.ts +++ b/x-pack/plugins/enterprise_search/server/lib/engines/field_capabilities.ts @@ -23,6 +23,7 @@ export const fetchEngineFieldCapabilities = async ( fields: '*', include_unmapped: true, index: indices, + filters: '-metadata', }); const fields = parseFieldsCapabilities(fieldCapabilities); return { From a7c988026346e0dbbc4ffed5815a0bd491f7c78b Mon Sep 17 00:00:00 2001 From: Navarone Feekery <13634519+navarone-feekery@users.noreply.github.com> Date: Tue, 18 Apr 2023 17:29:05 +0200 Subject: [PATCH 05/78] [Enterprise Search] Add connector configurable field dependencies (#155039) This PR hides a configurable field if its dependencies (document field `depends_on`) are not satisfied. Fields that have one or many dependencies also have some styling changes. --- .../common/connectors/native_connectors.ts | 14 ++++++++ .../common/types/connectors.ts | 12 +++++-- .../__mocks__/search_indices.mock.ts | 2 ++ .../__mocks__/view_index.mock.ts | 2 ++ .../connector_configuration_form.tsx | 31 +++++++++++++++-- .../connector_configuration_logic.test.ts | 23 +++++++++++++ .../connector_configuration_logic.ts | 34 ++++++++++++++----- 7 files changed, 104 insertions(+), 14 deletions(-) diff --git a/x-pack/plugins/enterprise_search/common/connectors/native_connectors.ts b/x-pack/plugins/enterprise_search/common/connectors/native_connectors.ts index 10c336540d9fa..aab72dce47f76 100644 --- a/x-pack/plugins/enterprise_search/common/connectors/native_connectors.ts +++ b/x-pack/plugins/enterprise_search/common/connectors/native_connectors.ts @@ -13,6 +13,7 @@ export const NATIVE_CONNECTOR_DEFINITIONS: Record; + export interface ConnectorConfigProperties { + depends_on: Dependency[]; display: string; label: string; - options: SelectOptions[]; + options: SelectOption[]; order?: number | null; required: boolean; sensitive: boolean; diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/__mocks__/search_indices.mock.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/__mocks__/search_indices.mock.ts index 6de267dca4b2f..0ae7543eadd45 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/__mocks__/search_indices.mock.ts +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/__mocks__/search_indices.mock.ts @@ -34,6 +34,7 @@ export const indices: ElasticsearchIndexWithIngestion[] = [ api_key_id: null, configuration: { foo: { + depends_on: [], display: 'textbox', key: 'foo', label: 'bar', @@ -141,6 +142,7 @@ export const indices: ElasticsearchIndexWithIngestion[] = [ api_key_id: null, configuration: { foo: { + depends_on: [], display: 'textbox', key: 'foo', label: 'bar', diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/__mocks__/view_index.mock.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/__mocks__/view_index.mock.ts index 3a799497993ba..517bad3746bc6 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/__mocks__/view_index.mock.ts +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/__mocks__/view_index.mock.ts @@ -44,6 +44,7 @@ export const connectorIndex: ConnectorViewIndex = { api_key_id: null, configuration: { foo: { + depends_on: [], display: 'textbox', key: 'foo', label: 'bar', @@ -155,6 +156,7 @@ export const crawlerIndex: CrawlerViewIndex = { api_key_id: null, configuration: { foo: { + depends_on: [], display: 'textbox', key: 'foo', label: 'bar', diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/connector_configuration_form.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/connector_configuration_form.tsx index 2ba55dc45c9a3..991e58a672016 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/connector_configuration_form.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/connector_configuration_form.tsx @@ -16,16 +16,22 @@ import { EuiFlexItem, EuiButton, EuiButtonEmpty, + EuiPanel, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { Status } from '../../../../../../common/types/api'; +import { DependencyLookup } from '../../../../../../common/types/connectors'; import { ConnectorConfigurationApiLogic } from '../../../api/connector/update_connector_configuration_api_logic'; import { ConnectorConfigurationField } from './connector_configuration_field'; -import { ConnectorConfigurationLogic } from './connector_configuration_logic'; +import { + ConfigEntry, + ConnectorConfigurationLogic, + dependenciesSatisfied, +} from './connector_configuration_logic'; export const ConnectorConfigurationForm = () => { const { status } = useValues(ConnectorConfigurationApiLogic); @@ -33,6 +39,14 @@ export const ConnectorConfigurationForm = () => { const { localConfigView } = useValues(ConnectorConfigurationLogic); const { saveConfig, setIsEditing } = useActions(ConnectorConfigurationLogic); + const dependencyLookup: DependencyLookup = localConfigView.reduce( + (prev: Record, configEntry: ConfigEntry) => ({ + ...prev, + [configEntry.key]: configEntry.value, + }), + {} + ); + return ( { @@ -42,9 +56,20 @@ export const ConnectorConfigurationForm = () => { component="form" > {localConfigView.map((configEntry) => { - const { key, label } = configEntry; + const { depends_on: dependencies, key, label } = configEntry; + const hasDependencies = dependencies.length > 0; - return ( + return hasDependencies ? ( + dependenciesSatisfied(dependencies, dependencyLookup) ? ( + + + + + + ) : ( + <> + ) + ) : ( diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/connector_configuration_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/connector_configuration_logic.test.ts index 13cbf2bc0a983..ebb173a3c42ca 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/connector_configuration_logic.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/connector_configuration_logic.test.ts @@ -52,6 +52,7 @@ describe('ConnectorConfigurationLogic', () => { ConnectorConfigurationApiLogic.actions.apiSuccess({ configuration: { foo: { + depends_on: [], display: 'textbox', label: 'newBar', options: [], @@ -67,6 +68,7 @@ describe('ConnectorConfigurationLogic', () => { ...DEFAULT_VALUES, configState: { foo: { + depends_on: [], display: 'textbox', label: 'newBar', options: [], @@ -78,6 +80,7 @@ describe('ConnectorConfigurationLogic', () => { }, configView: [ { + depends_on: [], display: 'textbox', key: 'foo', label: 'newBar', @@ -93,6 +96,7 @@ describe('ConnectorConfigurationLogic', () => { it('should set config on setConfigState', () => { ConnectorConfigurationLogic.actions.setConfigState({ foo: { + depends_on: [], display: 'textbox', label: 'thirdBar', options: [], @@ -106,6 +110,7 @@ describe('ConnectorConfigurationLogic', () => { ...DEFAULT_VALUES, configState: { foo: { + depends_on: [], display: 'textbox', label: 'thirdBar', options: [], @@ -117,6 +122,7 @@ describe('ConnectorConfigurationLogic', () => { }, configView: [ { + depends_on: [], display: 'textbox', key: 'foo', label: 'thirdBar', @@ -133,6 +139,7 @@ describe('ConnectorConfigurationLogic', () => { it('should set local config entry and sort keys', () => { ConnectorConfigurationLogic.actions.setConfigState({ bar: { + depends_on: [], display: 'textbox', label: 'foo', options: [], @@ -142,6 +149,7 @@ describe('ConnectorConfigurationLogic', () => { value: 'foofoo', }, password: { + depends_on: [], display: 'textbox', label: 'thirdBar', options: [], @@ -153,6 +161,7 @@ describe('ConnectorConfigurationLogic', () => { }); ConnectorConfigurationLogic.actions.setLocalConfigState({ bar: { + depends_on: [], display: 'textbox', label: 'foo', options: [], @@ -162,6 +171,7 @@ describe('ConnectorConfigurationLogic', () => { value: 'foofoo', }, password: { + depends_on: [], display: 'textbox', label: 'thirdBar', options: [], @@ -172,6 +182,7 @@ describe('ConnectorConfigurationLogic', () => { }, }); ConnectorConfigurationLogic.actions.setLocalConfigEntry({ + depends_on: [], display: 'textbox', key: 'bar', label: 'foo', @@ -185,6 +196,7 @@ describe('ConnectorConfigurationLogic', () => { ...DEFAULT_VALUES, configState: { bar: { + depends_on: [], display: 'textbox', label: 'foo', options: [], @@ -194,6 +206,7 @@ describe('ConnectorConfigurationLogic', () => { value: 'foofoo', }, password: { + depends_on: [], display: 'textbox', label: 'thirdBar', options: [], @@ -205,6 +218,7 @@ describe('ConnectorConfigurationLogic', () => { }, configView: [ { + depends_on: [], display: 'textbox', key: 'bar', label: 'foo', @@ -215,6 +229,7 @@ describe('ConnectorConfigurationLogic', () => { value: 'foofoo', }, { + depends_on: [], display: 'textbox', key: 'password', label: 'thirdBar', @@ -227,6 +242,7 @@ describe('ConnectorConfigurationLogic', () => { ], localConfigState: { bar: { + depends_on: [], display: 'textbox', label: 'foo', options: [], @@ -236,6 +252,7 @@ describe('ConnectorConfigurationLogic', () => { value: 'fafa', }, password: { + depends_on: [], display: 'textbox', label: 'thirdBar', options: [], @@ -247,6 +264,7 @@ describe('ConnectorConfigurationLogic', () => { }, localConfigView: [ { + depends_on: [], display: 'textbox', key: 'bar', label: 'foo', @@ -257,6 +275,7 @@ describe('ConnectorConfigurationLogic', () => { value: 'fafa', }, { + depends_on: [], display: 'textbox', key: 'password', label: 'thirdBar', @@ -278,6 +297,7 @@ describe('ConnectorConfigurationLogic', () => { configState: connectorIndex.connector.configuration, configView: [ { + depends_on: [], display: 'textbox', key: 'foo', label: 'bar', @@ -311,6 +331,7 @@ describe('ConnectorConfigurationLogic', () => { configState: connectorIndex.connector.configuration, configView: [ { + depends_on: [], display: 'textbox', key: 'foo', label: 'bar', @@ -329,6 +350,7 @@ describe('ConnectorConfigurationLogic', () => { localConfigState: connectorIndex.connector.configuration, localConfigView: [ { + depends_on: [], display: 'textbox', key: 'foo', label: 'bar', @@ -349,6 +371,7 @@ describe('ConnectorConfigurationLogic', () => { ConnectorConfigurationLogic.actions.fetchIndexApiSuccess(connectorIndex); ConnectorConfigurationLogic.actions.setLocalConfigState({ foo: { + depends_on: [], display: 'textbox', label: 'bar', options: [], diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/connector_configuration_logic.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/connector_configuration_logic.ts index d0416ca843c67..fee9496b3c069 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/connector_configuration_logic.ts +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/connector_configuration_logic.ts @@ -7,7 +7,13 @@ import { kea, MakeLogicType } from 'kea'; -import { ConnectorConfiguration, ConnectorStatus } from '../../../../../../common/types/connectors'; +import { + ConnectorConfiguration, + ConnectorStatus, + Dependency, + DependencyLookup, + SelectOption, +} from '../../../../../../common/types/connectors'; import { isNotNullish } from '../../../../../../common/utils/is_not_nullish'; import { @@ -48,16 +54,12 @@ interface ConnectorConfigurationValues { shouldStartInEditMode: boolean; } -interface SelectOptions { - label: string; - value: string; -} - export interface ConfigEntry { + depends_on: Dependency[]; display: string; key: string; label: string; - options: SelectOptions[]; + options: SelectOption[]; order?: number; required: boolean; sensitive: boolean; @@ -107,6 +109,19 @@ export function ensureBooleanType(value: string | number | boolean | null): bool return Boolean(value); } +export function dependenciesSatisfied( + dependencies: Dependency[], + dependencyLookup: DependencyLookup +): boolean { + for (const dependency of dependencies) { + if (dependency.value !== dependencyLookup[dependency.field]) { + return false; + } + } + + return true; +} + export const ConnectorConfigurationLogic = kea< MakeLogicType >({ @@ -214,10 +229,11 @@ export const ConnectorConfigurationLogic = kea< { setLocalConfigEntry: ( configState, - { key, display, label, options, order, required, sensitive, value } + // eslint-disable-next-line @typescript-eslint/naming-convention + { key, depends_on, display, label, options, order, required, sensitive, value } ) => ({ ...configState, - [key]: { display, label, options, order, required, sensitive, value }, + [key]: { depends_on, display, label, options, order, required, sensitive, value }, }), setLocalConfigState: (_, { configState }) => configState, }, From 136876091aae97db5031e712aeafaa293b27e4ae Mon Sep 17 00:00:00 2001 From: Kyle Pollich Date: Tue, 18 Apr 2023 12:17:06 -0400 Subject: [PATCH 06/78] [Fleet] Fix policy editor error when `validationResults.inputs` is `null` (#155079) Improve various typings and remove non-null assertions to ensure the policy editor doesn't break when rendering a policy template without `vars` or `streams` in its inputs. Closes https://github.com/elastic/kibana/issues/155064 ![image](https://user-images.githubusercontent.com/6766512/232572327-df4ee721-722a-4d10-8eba-5ff048f5536b.png) --------- Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../services/validate_package_policy.test.ts | 8 +++--- .../services/validate_package_policy.ts | 4 +-- .../steps/step_configure_package.test.tsx | 27 +++++++++++++++---- .../steps/step_configure_package.tsx | 6 ++--- .../steps/step_define_package_policy.tsx | 6 ++--- .../components/page_steps/add_integration.tsx | 4 +-- .../single_page_layout/index.tsx | 4 +-- .../edit_package_policy_page/index.tsx | 4 +-- .../all_assets/0.2.0/manifest.yml | 4 +-- .../0.1.0/docs/README.md | 3 +++ .../0.1.0/img/logo_overrides_64_color.svg | 7 +++++ .../0.1.0/manifest.yml | 24 +++++++++++++++++ .../apis/package_policy/create.ts | 25 +++++++++++++++++ 13 files changed, 101 insertions(+), 25 deletions(-) create mode 100644 x-pack/test/fleet_api_integration/apis/fixtures/test_packages/single_input_no_streams/0.1.0/docs/README.md create mode 100644 x-pack/test/fleet_api_integration/apis/fixtures/test_packages/single_input_no_streams/0.1.0/img/logo_overrides_64_color.svg create mode 100644 x-pack/test/fleet_api_integration/apis/fixtures/test_packages/single_input_no_streams/0.1.0/manifest.yml diff --git a/x-pack/plugins/fleet/common/services/validate_package_policy.test.ts b/x-pack/plugins/fleet/common/services/validate_package_policy.test.ts index 13410561e497e..35c1e8184c931 100644 --- a/x-pack/plugins/fleet/common/services/validate_package_policy.test.ts +++ b/x-pack/plugins/fleet/common/services/validate_package_policy.test.ts @@ -512,7 +512,7 @@ describe('Fleet - validatePackagePolicy()', () => { name: null, description: null, namespace: null, - inputs: null, + inputs: {}, vars: {}, }); expect( @@ -528,7 +528,7 @@ describe('Fleet - validatePackagePolicy()', () => { name: null, description: null, namespace: null, - inputs: null, + inputs: {}, vars: {}, }); }); @@ -547,7 +547,7 @@ describe('Fleet - validatePackagePolicy()', () => { name: null, description: null, namespace: null, - inputs: null, + inputs: {}, vars: {}, }); expect( @@ -563,7 +563,7 @@ describe('Fleet - validatePackagePolicy()', () => { name: null, description: null, namespace: null, - inputs: null, + inputs: {}, vars: {}, }); }); diff --git a/x-pack/plugins/fleet/common/services/validate_package_policy.ts b/x-pack/plugins/fleet/common/services/validate_package_policy.ts index 0af746ad2453e..49a59f1e64eb8 100644 --- a/x-pack/plugins/fleet/common/services/validate_package_policy.ts +++ b/x-pack/plugins/fleet/common/services/validate_package_policy.ts @@ -101,7 +101,7 @@ export const validatePackagePolicy = ( (policyTemplate.inputs && policyTemplate.inputs.length > 0) ) ) { - validationResults.inputs = null; + validationResults.inputs = {}; return validationResults; } @@ -199,7 +199,7 @@ export const validatePackagePolicy = ( }); if (Object.entries(validationResults.inputs!).length === 0) { - validationResults.inputs = null; + validationResults.inputs = {}; } return validationResults; diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/steps/step_configure_package.test.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/steps/step_configure_package.test.tsx index 776ee1957eaf9..6cff24d6ba091 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/steps/step_configure_package.test.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/steps/step_configure_package.test.tsx @@ -7,11 +7,14 @@ import React from 'react'; import { act, fireEvent, waitFor } from '@testing-library/react'; +import { safeLoad } from 'js-yaml'; import type { TestRenderer } from '../../../../../../../mock'; import { createFleetTestRendererMock } from '../../../../../../../mock'; import type { NewPackagePolicy, PackageInfo } from '../../../../../types'; +import { validatePackagePolicy } from '../../services'; + import { StepConfigurePackagePolicy } from './step_configure_package'; describe('StepConfigurePackage', () => { @@ -24,12 +27,12 @@ describe('StepConfigurePackage', () => { }; }); - const validationResults = { name: null, description: null, namespace: null, inputs: {} }; - let testRenderer: TestRenderer; let renderResult: ReturnType; - const render = () => - (renderResult = testRenderer.render( + const render = () => { + const validationResults = validatePackagePolicy(packagePolicy, packageInfo, safeLoad); + + renderResult = testRenderer.render( { validationResults={validationResults} submitAttempted={false} /> - )); + ); + }; beforeEach(() => { packageInfo = { @@ -150,4 +154,17 @@ describe('StepConfigurePackage', () => { }); expect(mockUpdatePackagePolicy.mock.calls[0][0].inputs[0].enabled).toEqual(false); }); + + it('should render without data streams or vars', async () => { + packageInfo.data_streams = []; + packagePolicy.inputs[0].streams = []; + + render(); + + await waitFor(async () => { + expect( + await renderResult.findByText('Collect logs from Nginx instances') + ).toBeInTheDocument(); + }); + }); }); diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/steps/step_configure_package.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/steps/step_configure_package.tsx index edcea931b5535..5fc13bd223800 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/steps/step_configure_package.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/steps/step_configure_package.tsx @@ -34,7 +34,7 @@ export const StepConfigurePackagePolicy: React.FunctionComponent<{ showOnlyIntegration?: string; packagePolicy: NewPackagePolicy; updatePackagePolicy: (fields: Partial) => void; - validationResults: PackagePolicyValidationResults; + validationResults: PackagePolicyValidationResults | undefined; submitAttempted: boolean; noTopRule?: boolean; isEditPage?: boolean; @@ -104,11 +104,11 @@ export const StepConfigurePackagePolicy: React.FunctionComponent<{ }); }} inputValidationResults={ - validationResults!.inputs![ + validationResults?.inputs?.[ hasIntegrations ? `${policyTemplate.name}-${packagePolicyInput.type}` : packagePolicyInput.type - ] + ] ?? {} } forceShowErrors={submitAttempted} isEditPage={isEditPage} diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/steps/step_define_package_policy.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/steps/step_define_package_policy.tsx index e9efc93d1d9b3..a9c3f7a70a58c 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/steps/step_define_package_policy.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/steps/step_define_package_policy.tsx @@ -52,7 +52,7 @@ export const StepDefinePackagePolicy: React.FunctionComponent<{ packageInfo: PackageInfo; packagePolicy: NewPackagePolicy; updatePackagePolicy: (fields: Partial) => void; - validationResults: PackagePolicyValidationResults; + validationResults: PackagePolicyValidationResults | undefined; submitAttempted: boolean; isEditPage?: boolean; noAdvancedToggle?: boolean; @@ -201,7 +201,7 @@ export const StepDefinePackagePolicy: React.FunctionComponent<{ }, }); }} - errors={validationResults.vars![varName]} + errors={validationResults?.vars?.[varName] ?? []} forceShowErrors={submitAttempted} />
@@ -350,7 +350,7 @@ export const StepDefinePackagePolicy: React.FunctionComponent<{ }, }); }} - errors={validationResults.vars![varName]} + errors={validationResults?.vars?.[varName] ?? []} forceShowErrors={submitAttempted} />
diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/multi_page_layout/components/page_steps/add_integration.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/multi_page_layout/components/page_steps/add_integration.tsx index f84d3fbfc2988..417b516c57932 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/multi_page_layout/components/page_steps/add_integration.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/multi_page_layout/components/page_steps/add_integration.tsx @@ -238,7 +238,7 @@ export const AddIntegrationPageStep: React.FC = (props showOnlyIntegration={integrationInfo?.name} packagePolicy={packagePolicy} updatePackagePolicy={updatePackagePolicy} - validationResults={validationResults!} + validationResults={validationResults} submitAttempted={formState === 'INVALID'} noTopRule={true} /> @@ -248,7 +248,7 @@ export const AddIntegrationPageStep: React.FC = (props packageInfo={packageInfo} packagePolicy={packagePolicy} updatePackagePolicy={updatePackagePolicy} - validationResults={validationResults!} + validationResults={validationResults} submitAttempted={formState === 'INVALID'} noAdvancedToggle={true} /> diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/index.tsx index a13d0ea31a33d..9940f17d11492 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/index.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/index.tsx @@ -307,7 +307,7 @@ export const CreatePackagePolicySinglePage: CreatePackagePolicyParams = ({ packageInfo={packageInfo} packagePolicy={packagePolicy} updatePackagePolicy={updatePackagePolicy} - validationResults={validationResults!} + validationResults={validationResults} submitAttempted={formState === 'INVALID'} /> @@ -318,7 +318,7 @@ export const CreatePackagePolicySinglePage: CreatePackagePolicyParams = ({ showOnlyIntegration={integrationInfo?.name} packagePolicy={packagePolicy} updatePackagePolicy={updatePackagePolicy} - validationResults={validationResults!} + validationResults={validationResults} submitAttempted={formState === 'INVALID'} /> )} diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/edit_package_policy_page/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/edit_package_policy_page/index.tsx index 8b87f64d7b6c0..433bb278a581d 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/edit_package_policy_page/index.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/edit_package_policy_page/index.tsx @@ -293,7 +293,7 @@ export const EditPackagePolicyForm = memo<{ packageInfo={packageInfo} packagePolicy={packagePolicy} updatePackagePolicy={updatePackagePolicy} - validationResults={validationResults!} + validationResults={validationResults} submitAttempted={formState === 'INVALID'} isEditPage={true} /> @@ -305,7 +305,7 @@ export const EditPackagePolicyForm = memo<{ packageInfo={packageInfo} packagePolicy={packagePolicy} updatePackagePolicy={updatePackagePolicy} - validationResults={validationResults!} + validationResults={validationResults} submitAttempted={formState === 'INVALID'} isEditPage={true} /> diff --git a/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/all_assets/0.2.0/manifest.yml b/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/all_assets/0.2.0/manifest.yml index 33c7c020b4c49..4359fc17f0c28 100644 --- a/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/all_assets/0.2.0/manifest.yml +++ b/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/all_assets/0.2.0/manifest.yml @@ -1,7 +1,7 @@ format_version: 1.0.0 -name: all_assets +name: all_assets title: All Assets Updated -description: tests that all assets are updated +description: tests that all assets are updated version: 0.2.0 categories: [] release: beta diff --git a/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/single_input_no_streams/0.1.0/docs/README.md b/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/single_input_no_streams/0.1.0/docs/README.md new file mode 100644 index 0000000000000..76889e6aa836c --- /dev/null +++ b/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/single_input_no_streams/0.1.0/docs/README.md @@ -0,0 +1,3 @@ +# Test package + +Test package with a single input and no defined data streams diff --git a/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/single_input_no_streams/0.1.0/img/logo_overrides_64_color.svg b/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/single_input_no_streams/0.1.0/img/logo_overrides_64_color.svg new file mode 100644 index 0000000000000..b03007a76ffcc --- /dev/null +++ b/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/single_input_no_streams/0.1.0/img/logo_overrides_64_color.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/single_input_no_streams/0.1.0/manifest.yml b/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/single_input_no_streams/0.1.0/manifest.yml new file mode 100644 index 0000000000000..0f7c477c97d02 --- /dev/null +++ b/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/single_input_no_streams/0.1.0/manifest.yml @@ -0,0 +1,24 @@ +format_version: 1.1.0 +name: single_input_no_streams +title: Single input, no stream +description: Test package with a single input and no streams +version: 0.1.0 +categories: [] +conditions: + kibana.version: ^8.8.0 + elastic.subscription: basic +policy_templates: + - name: Single Input + title: Single Input + description: Single Input + inputs: + - title: Single Input + description: Single input + type: single_input +type: integration +owner: + github: elastic/fleet +icons: + - src: '/img/logo_overrides_64_color.svg' + size: '16x16' + type: 'image/svg+xml' diff --git a/x-pack/test/fleet_api_integration/apis/package_policy/create.ts b/x-pack/test/fleet_api_integration/apis/package_policy/create.ts index 82777e41eb5e1..ca8ac94656402 100644 --- a/x-pack/test/fleet_api_integration/apis/package_policy/create.ts +++ b/x-pack/test/fleet_api_integration/apis/package_policy/create.ts @@ -446,6 +446,31 @@ export default function (providerContext: FtrProviderContext) { expect(policy.name).to.equal(nameWithWhitespace.trim()); }); + it('should return a 200 when a package has no variables or data streams', async function () { + await supertest + .post('/api/fleet/package_policies') + .set('kbn-xsrf', 'xxxx') + .send({ + name: 'no-variables-or-data-streams', + description: '', + namespace: 'default', + policy_id: agentPolicyId, + enabled: true, + inputs: [ + { + enabled: true, + streams: [], + type: 'single_input', + }, + ], + package: { + name: 'single_input_no_streams', + version: '0.1.0', + }, + }) + .expect(200); + }); + describe('input only packages', () => { it('should default dataset if not provided for input only pkg', async function () { await supertest From 42effff78d63257c40f4c919e461d5fdba30c0bf Mon Sep 17 00:00:00 2001 From: Christos Nasikas Date: Tue, 18 Apr 2023 19:20:59 +0300 Subject: [PATCH 07/78] [Cases] Remove case id from alerts when deleting a case (#154829) ## Summary This PR removes the case id from all alerts attached to a case when deleting a case. It also removes any alert authorization when removing alerts from a case. ### Checklist Delete any items that are not applicable to this PR. - [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 ### For maintainers - [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) --- .../cases/server/client/alerts/types.ts | 2 +- .../server/client/attachments/delete.test.ts | 13 -- .../cases/server/client/attachments/delete.ts | 1 - .../cases/server/client/cases/delete.test.ts | 32 ++- .../cases/server/client/cases/delete.ts | 3 +- .../server/services/alerts/index.test.ts | 42 ++++ .../cases/server/services/alerts/index.ts | 32 ++- x-pack/plugins/cases/server/services/mocks.ts | 4 +- .../alert_data_client/alerts_client.mock.ts | 1 + .../server/alert_data_client/alerts_client.ts | 80 +++++-- .../tests/remove_alerts_from_case.test.ts | 109 --------- .../tests/remove_cases_from_alerts.test.ts | 179 +++++++++++++++ .../common/lib/alerts.ts | 134 +++++++++-- .../tests/common/cases/delete_cases.ts | 209 ++++++++++++++++++ .../tests/common/comments/delete_comment.ts | 12 +- .../tests/common/comments/delete_comments.ts | 9 +- 16 files changed, 682 insertions(+), 180 deletions(-) delete mode 100644 x-pack/plugins/rule_registry/server/alert_data_client/tests/remove_alerts_from_case.test.ts create mode 100644 x-pack/plugins/rule_registry/server/alert_data_client/tests/remove_cases_from_alerts.test.ts diff --git a/x-pack/plugins/cases/server/client/alerts/types.ts b/x-pack/plugins/cases/server/client/alerts/types.ts index a1317106e6fe7..4ceba5e500d9b 100644 --- a/x-pack/plugins/cases/server/client/alerts/types.ts +++ b/x-pack/plugins/cases/server/client/alerts/types.ts @@ -43,7 +43,7 @@ export interface UpdateAlertCasesRequest { caseIds: string[]; } -export interface RemoveAlertsFromCaseRequest { +export interface RemoveCaseIdFromAlertsRequest { alerts: AlertInfo[]; caseId: string; } diff --git a/x-pack/plugins/cases/server/client/attachments/delete.test.ts b/x-pack/plugins/cases/server/client/attachments/delete.test.ts index d484515f4eeaf..475c7e5006b15 100644 --- a/x-pack/plugins/cases/server/client/attachments/delete.test.ts +++ b/x-pack/plugins/cases/server/client/attachments/delete.test.ts @@ -25,10 +25,6 @@ describe('delete', () => { it('delete alerts correctly', async () => { await deleteComment({ caseID: 'mock-id-4', attachmentID: 'mock-comment-4' }, clientArgs); - expect(clientArgs.services.alertsService.ensureAlertsAuthorized).toHaveBeenCalledWith({ - alerts: [{ id: 'test-id', index: 'test-index' }], - }); - expect(clientArgs.services.alertsService.removeCaseIdFromAlerts).toHaveBeenCalledWith({ alerts: [{ id: 'test-id', index: 'test-index' }], caseId: 'mock-id-4', @@ -39,7 +35,6 @@ describe('delete', () => { clientArgs.services.attachmentService.getter.get.mockResolvedValue(commentSO); await deleteComment({ caseID: 'mock-id-1', attachmentID: 'mock-comment-1' }, clientArgs); - expect(clientArgs.services.alertsService.ensureAlertsAuthorized).not.toHaveBeenCalledWith(); expect(clientArgs.services.alertsService.removeCaseIdFromAlerts).not.toHaveBeenCalledWith(); }); }); @@ -66,14 +61,6 @@ describe('delete', () => { it('delete alerts correctly', async () => { await deleteAll({ caseID: 'mock-id-4' }, clientArgs); - expect(clientArgs.services.alertsService.ensureAlertsAuthorized).toHaveBeenCalledWith({ - alerts: [ - { id: 'test-id', index: 'test-index' }, - { id: 'test-id-2', index: 'test-index-2' }, - { id: 'test-id-3', index: 'test-index-3' }, - ], - }); - expect(clientArgs.services.alertsService.removeCaseIdFromAlerts).toHaveBeenCalledWith({ alerts: [ { id: 'test-id', index: 'test-index' }, diff --git a/x-pack/plugins/cases/server/client/attachments/delete.ts b/x-pack/plugins/cases/server/client/attachments/delete.ts index 07d3091e8d348..718ff4eb7e56c 100644 --- a/x-pack/plugins/cases/server/client/attachments/delete.ts +++ b/x-pack/plugins/cases/server/client/attachments/delete.ts @@ -151,6 +151,5 @@ const handleAlerts = async ({ alertsService, attachments, caseId }: HandleAlerts } const alerts = getAlertInfoFromComments(alertAttachments); - await alertsService.ensureAlertsAuthorized({ alerts }); await alertsService.removeCaseIdFromAlerts({ alerts, caseId }); }; diff --git a/x-pack/plugins/cases/server/client/cases/delete.test.ts b/x-pack/plugins/cases/server/client/cases/delete.test.ts index 043b08733dd3f..ebe64da0c2baf 100644 --- a/x-pack/plugins/cases/server/client/cases/delete.test.ts +++ b/x-pack/plugins/cases/server/client/cases/delete.test.ts @@ -10,11 +10,14 @@ import { createFileServiceMock } from '@kbn/files-plugin/server/mocks'; import type { FileJSON } from '@kbn/shared-ux-file-types'; import type { CaseFileMetadataForDeletion } from '../../../common/files'; import { constructFileKindIdByOwner } from '../../../common/files'; -import { getFileEntities } from './delete'; +import { getFileEntities, deleteCases } from './delete'; +import { createCasesClientMockArgs } from '../mocks'; +import { mockCases } from '../../mocks'; const getCaseIds = (numIds: number) => { return Array.from(Array(numIds).keys()).map((key) => key.toString()); }; + describe('delete', () => { describe('getFileEntities', () => { const numCaseIds = 1000; @@ -66,6 +69,33 @@ describe('delete', () => { expect(entities).toEqual(expectedEntities); }); }); + + describe('deleteCases', () => { + const clientArgs = createCasesClientMockArgs(); + clientArgs.fileService.find.mockResolvedValue({ files: [], total: 0 }); + + clientArgs.services.caseService.getCases.mockResolvedValue({ + saved_objects: [mockCases[0], mockCases[1]], + }); + + clientArgs.services.attachmentService.getter.getAttachmentIdsForCases.mockResolvedValue([]); + clientArgs.services.userActionService.getUserActionIdsForCases.mockResolvedValue([]); + + beforeEach(() => { + jest.clearAllMocks(); + }); + + describe('alerts', () => { + const caseIds = ['mock-id-1', 'mock-id-2']; + + it('removes the case ids from all alerts', async () => { + await deleteCases(caseIds, clientArgs); + expect(clientArgs.services.alertsService.removeCaseIdsFromAllAlerts).toHaveBeenCalledWith({ + caseIds, + }); + }); + }); + }); }); const createMockFileJSON = (): FileJSON => { diff --git a/x-pack/plugins/cases/server/client/cases/delete.ts b/x-pack/plugins/cases/server/client/cases/delete.ts index a81e0db4dcf66..a9479e2ab171a 100644 --- a/x-pack/plugins/cases/server/client/cases/delete.ts +++ b/x-pack/plugins/cases/server/client/cases/delete.ts @@ -28,7 +28,7 @@ import { createFileEntities, deleteFiles } from '../files'; */ export async function deleteCases(ids: string[], clientArgs: CasesClientArgs): Promise { const { - services: { caseService, attachmentService, userActionService }, + services: { caseService, attachmentService, userActionService, alertsService }, logger, authorization, fileService, @@ -75,6 +75,7 @@ export async function deleteCases(ids: string[], clientArgs: CasesClientArgs): P entities: bulkDeleteEntities, options: { refresh: 'wait_for' }, }), + alertsService.removeCaseIdsFromAllAlerts({ caseIds: ids }), ]); await userActionService.creator.bulkAuditLogCaseDeletion( diff --git a/x-pack/plugins/cases/server/services/alerts/index.test.ts b/x-pack/plugins/cases/server/services/alerts/index.test.ts index 5cc5e6de9650d..970494ce2140f 100644 --- a/x-pack/plugins/cases/server/services/alerts/index.test.ts +++ b/x-pack/plugins/cases/server/services/alerts/index.test.ts @@ -423,5 +423,47 @@ describe('updateAlertsStatus', () => { expect(alertsClient.removeCaseIdFromAlerts).not.toHaveBeenCalled(); }); + + it('should not throw an error and log it', async () => { + alertsClient.removeCaseIdFromAlerts.mockRejectedValueOnce('An error'); + + await expect( + alertService.removeCaseIdFromAlerts({ alerts, caseId }) + ).resolves.not.toThrowError(); + + expect(logger.error).toHaveBeenCalledWith( + 'Failed removing case test-case from alerts: An error' + ); + }); + }); + + describe('removeCaseIdsFromAllAlerts', () => { + const caseIds = ['test-case-1', 'test-case-2']; + + it('remove all case ids from alerts', async () => { + await alertService.removeCaseIdsFromAllAlerts({ caseIds }); + + expect(alertsClient.removeCaseIdsFromAllAlerts).toBeCalledWith({ caseIds }); + }); + + it('does not call the alerts client with no case ids', async () => { + await alertService.removeCaseIdsFromAllAlerts({ + caseIds: [], + }); + + expect(alertsClient.removeCaseIdsFromAllAlerts).not.toHaveBeenCalled(); + }); + + it('should not throw an error and log it', async () => { + alertsClient.removeCaseIdsFromAllAlerts.mockRejectedValueOnce('An error'); + + await expect( + alertService.removeCaseIdsFromAllAlerts({ caseIds }) + ).resolves.not.toThrowError(); + + expect(logger.error).toHaveBeenCalledWith( + 'Failed removing cases test-case-1,test-case-2 for all alerts: An error' + ); + }); }); }); diff --git a/x-pack/plugins/cases/server/services/alerts/index.ts b/x-pack/plugins/cases/server/services/alerts/index.ts index 9ce651dce105d..d078508969b1e 100644 --- a/x-pack/plugins/cases/server/services/alerts/index.ts +++ b/x-pack/plugins/cases/server/services/alerts/index.ts @@ -19,7 +19,7 @@ import { MAX_ALERTS_PER_CASE, MAX_CONCURRENT_SEARCHES } from '../../../common/co import { createCaseError } from '../../common/error'; import type { AlertInfo } from '../../common/types'; import type { - RemoveAlertsFromCaseRequest, + RemoveCaseIdFromAlertsRequest, UpdateAlertCasesRequest, UpdateAlertStatusRequest, } from '../../client/alerts/types'; @@ -237,7 +237,7 @@ export class AlertService { public async removeCaseIdFromAlerts({ alerts, caseId, - }: RemoveAlertsFromCaseRequest): Promise { + }: RemoveCaseIdFromAlertsRequest): Promise { try { const nonEmptyAlerts = this.getNonEmptyAlerts(alerts); @@ -250,11 +250,31 @@ export class AlertService { caseId, }); } catch (error) { - throw createCaseError({ - message: `Failed to remove case ${caseId} from alerts: ${error}`, - error, - logger: this.logger, + /** + * We intentionally do not throw an error. + * Users should be able to remove alerts from a case even + * in the event of an error produced by the alerts client + */ + this.logger.error(`Failed removing case ${caseId} from alerts: ${error}`); + } + } + + public async removeCaseIdsFromAllAlerts({ caseIds }: { caseIds: string[] }): Promise { + try { + if (caseIds.length <= 0) { + return; + } + + await this.alertsClient.removeCaseIdsFromAllAlerts({ + caseIds, }); + } catch (error) { + /** + * We intentionally do not throw an error. + * Users should be able to remove alerts from cases even + * in the event of an error produced by the alerts client + */ + this.logger.error(`Failed removing cases ${caseIds} for all alerts: ${error}`); } } diff --git a/x-pack/plugins/cases/server/services/mocks.ts b/x-pack/plugins/cases/server/services/mocks.ts index 57a81e9afc94e..402d682300b7a 100644 --- a/x-pack/plugins/cases/server/services/mocks.ts +++ b/x-pack/plugins/cases/server/services/mocks.ts @@ -45,7 +45,7 @@ export type LicensingServiceMock = jest.Mocked; export type NotificationServiceMock = jest.Mocked; export const createCaseServiceMock = (): CaseServiceMock => { - const service = { + const service: PublicMethodsOf = { deleteCase: jest.fn(), findCases: jest.fn(), getAllCaseComments: jest.fn(), @@ -61,6 +61,7 @@ export const createCaseServiceMock = (): CaseServiceMock => { findCasesGroupedByID: jest.fn(), getCaseStatusStats: jest.fn(), executeAggregations: jest.fn(), + bulkDeleteCaseEntities: jest.fn(), }; // the cast here is required because jest.Mocked tries to include private members and would throw an error @@ -140,6 +141,7 @@ export const createAlertServiceMock = (): AlertServiceMock => { bulkUpdateCases: jest.fn(), ensureAlertsAuthorized: jest.fn(), removeCaseIdFromAlerts: jest.fn(), + removeCaseIdsFromAllAlerts: jest.fn(), }; // the cast here is required because jest.Mocked tries to include private members and would throw an error diff --git a/x-pack/plugins/rule_registry/server/alert_data_client/alerts_client.mock.ts b/x-pack/plugins/rule_registry/server/alert_data_client/alerts_client.mock.ts index a0c903ca7b4e1..e0acdfc0e40a2 100644 --- a/x-pack/plugins/rule_registry/server/alert_data_client/alerts_client.mock.ts +++ b/x-pack/plugins/rule_registry/server/alert_data_client/alerts_client.mock.ts @@ -24,6 +24,7 @@ const createAlertsClientMock = () => { getAlertSummary: jest.fn(), ensureAllAlertsAuthorizedRead: jest.fn(), removeCaseIdFromAlerts: jest.fn(), + removeCaseIdsFromAllAlerts: jest.fn(), }; return mocked; }; diff --git a/x-pack/plugins/rule_registry/server/alert_data_client/alerts_client.ts b/x-pack/plugins/rule_registry/server/alert_data_client/alerts_client.ts index 08b4dd5738791..9e66facd9d160 100644 --- a/x-pack/plugins/rule_registry/server/alert_data_client/alerts_client.ts +++ b/x-pack/plugins/rule_registry/server/alert_data_client/alerts_client.ts @@ -105,7 +105,7 @@ export interface BulkUpdateCasesOptions { caseIds: string[]; } -export interface RemoveAlertsFromCaseOptions { +export interface RemoveCaseIdFromAlertsOptions { alerts: MgetAndAuditAlert[]; caseId: string; } @@ -851,33 +851,33 @@ export class AlertsClient { }); } - public async removeCaseIdFromAlerts({ caseId, alerts }: RemoveAlertsFromCaseOptions) { + public async removeCaseIdFromAlerts({ caseId, alerts }: RemoveCaseIdFromAlertsOptions) { + /** + * We intentionally do not perform any authorization + * on the alerts. Users should be able to remove + * cases from alerts when deleting a case or an + * attachment + */ try { if (alerts.length === 0) { return; } - const mgetRes = await this.ensureAllAlertsAuthorized({ - alerts, - operation: ReadOperations.Get, - }); - const painlessScript = `if (ctx._source['${ALERT_CASE_IDS}'] != null) { - for (int i=0; i < ctx._source['${ALERT_CASE_IDS}'].length; i++) { - if (ctx._source['${ALERT_CASE_IDS}'][i].equals('${caseId}')) { - ctx._source['${ALERT_CASE_IDS}'].remove(i); - } + if (ctx._source['${ALERT_CASE_IDS}'].contains('${caseId}')) { + int index = ctx._source['${ALERT_CASE_IDS}'].indexOf('${caseId}'); + ctx._source['${ALERT_CASE_IDS}'].remove(index); } }`; const bulkUpdateRequest = []; - for (const doc of mgetRes.docs) { + for (const alert of alerts) { bulkUpdateRequest.push( { update: { - _index: doc._index, - _id: doc._id, + _index: alert.index, + _id: alert.id, }, }, { @@ -891,11 +891,61 @@ export class AlertsClient { body: bulkUpdateRequest, }); } catch (error) { - this.logger.error(`Error to remove case ${caseId} from alerts: ${error}`); + this.logger.error(`Error removing case ${caseId} from alerts: ${error}`); throw error; } } + public async removeCaseIdsFromAllAlerts({ caseIds }: { caseIds: string[] }) { + /** + * We intentionally do not perform any authorization + * on the alerts. Users should be able to remove + * cases from alerts when deleting a case or an + * attachment + */ + try { + if (caseIds.length === 0) { + return; + } + + const index = `${this.ruleDataService.getResourcePrefix()}-*`; + const query = `${ALERT_CASE_IDS}: (${caseIds.join(' or ')})`; + const esQuery = buildEsQuery(undefined, { query, language: 'kuery' }, []); + + const SCRIPT_PARAMS_ID = 'caseIds'; + + const painlessScript = `if (ctx._source['${ALERT_CASE_IDS}'] != null && ctx._source['${ALERT_CASE_IDS}'].length > 0 && params['${SCRIPT_PARAMS_ID}'] != null && params['${SCRIPT_PARAMS_ID}'].length > 0) { + List storedCaseIds = ctx._source['${ALERT_CASE_IDS}']; + List caseIdsToRemove = params['${SCRIPT_PARAMS_ID}']; + + for (int i=0; i < caseIdsToRemove.length; i++) { + if (storedCaseIds.contains(caseIdsToRemove[i])) { + int index = storedCaseIds.indexOf(caseIdsToRemove[i]); + storedCaseIds.remove(index); + } + } + }`; + + await this.esClient.updateByQuery({ + index, + conflicts: 'proceed', + refresh: true, + body: { + script: { + source: painlessScript, + lang: 'painless', + params: { caseIds }, + } as InlineScript, + query: esQuery, + }, + ignore_unavailable: true, + }); + } catch (err) { + this.logger.error(`Failed removing ${caseIds} from all alerts: ${err}`); + throw err; + } + } + public async find({ aggs, featureIds, diff --git a/x-pack/plugins/rule_registry/server/alert_data_client/tests/remove_alerts_from_case.test.ts b/x-pack/plugins/rule_registry/server/alert_data_client/tests/remove_alerts_from_case.test.ts deleted file mode 100644 index c938dd8d592fb..0000000000000 --- a/x-pack/plugins/rule_registry/server/alert_data_client/tests/remove_alerts_from_case.test.ts +++ /dev/null @@ -1,109 +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 { loggingSystemMock } from '@kbn/core/server/mocks'; -import { elasticsearchClientMock } from '@kbn/core-elasticsearch-client-server-mocks'; -import { alertingAuthorizationMock } from '@kbn/alerting-plugin/server/authorization/alerting_authorization.mock'; -import { auditLoggerMock } from '@kbn/security-plugin/server/audit/mocks'; -import { ALERT_CASE_IDS, ALERT_RULE_CONSUMER, ALERT_RULE_TYPE_ID } from '@kbn/rule-data-utils'; -import { AlertsClient, ConstructorOptions } from '../alerts_client'; -import { ruleDataServiceMock } from '../../rule_data_plugin_service/rule_data_plugin_service.mock'; - -describe('removeCaseIdFromAlerts', () => { - const alertingAuthMock = alertingAuthorizationMock.create(); - const esClientMock = elasticsearchClientMock.createElasticsearchClient(); - const auditLogger = auditLoggerMock.create(); - const caseId = 'test-case'; - const alerts = [ - { - id: 'alert-id', - index: 'alert-index', - }, - ]; - - const alertsClientParams: jest.Mocked = { - logger: loggingSystemMock.create().get(), - authorization: alertingAuthMock, - esClient: esClientMock, - auditLogger, - ruleDataService: ruleDataServiceMock.create(), - }; - - beforeEach(() => { - jest.clearAllMocks(); - - esClientMock.mget.mockResponseOnce({ - docs: [ - { - found: true, - _id: 'alert-id', - _index: 'alert-index', - _source: { - [ALERT_RULE_TYPE_ID]: 'apm.error_rate', - [ALERT_RULE_CONSUMER]: 'apm', - [ALERT_CASE_IDS]: [caseId], - }, - }, - ], - }); - }); - - it('removes alerts from a case', async () => { - const alertsClient = new AlertsClient(alertsClientParams); - await alertsClient.removeCaseIdFromAlerts({ caseId, alerts }); - - expect(esClientMock.mget).toHaveBeenCalledWith({ - docs: [{ _id: 'alert-id', _index: 'alert-index' }], - }); - - expect(esClientMock.bulk.mock.calls[0][0]).toMatchInlineSnapshot(` - Object { - "body": Array [ - Object { - "update": Object { - "_id": "alert-id", - "_index": "alert-index", - }, - }, - Object { - "script": Object { - "lang": "painless", - "source": "if (ctx._source['kibana.alert.case_ids'] != null) { - for (int i=0; i < ctx._source['kibana.alert.case_ids'].length; i++) { - if (ctx._source['kibana.alert.case_ids'][i].equals('test-case')) { - ctx._source['kibana.alert.case_ids'].remove(i); - } - } - }", - }, - }, - ], - "refresh": "wait_for", - } - `); - }); - - it('calls ensureAllAlertsAuthorized correctly', async () => { - const alertsClient = new AlertsClient(alertsClientParams); - await alertsClient.removeCaseIdFromAlerts({ caseId, alerts }); - - expect(alertingAuthMock.ensureAuthorized).toHaveBeenCalledWith({ - consumer: 'apm', - entity: 'alert', - operation: 'get', - ruleTypeId: 'apm.error_rate', - }); - }); - - it('does not do any calls if there are no alerts', async () => { - const alertsClient = new AlertsClient(alertsClientParams); - await alertsClient.removeCaseIdFromAlerts({ caseId, alerts: [] }); - - expect(alertingAuthMock.ensureAuthorized).not.toHaveBeenCalled(); - expect(esClientMock.bulk).not.toHaveBeenCalled(); - }); -}); diff --git a/x-pack/plugins/rule_registry/server/alert_data_client/tests/remove_cases_from_alerts.test.ts b/x-pack/plugins/rule_registry/server/alert_data_client/tests/remove_cases_from_alerts.test.ts new file mode 100644 index 0000000000000..8b6f075edfab4 --- /dev/null +++ b/x-pack/plugins/rule_registry/server/alert_data_client/tests/remove_cases_from_alerts.test.ts @@ -0,0 +1,179 @@ +/* + * Copyright 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 { elasticsearchClientMock } from '@kbn/core-elasticsearch-client-server-mocks'; +import { alertingAuthorizationMock } from '@kbn/alerting-plugin/server/authorization/alerting_authorization.mock'; +import { auditLoggerMock } from '@kbn/security-plugin/server/audit/mocks'; +import { AlertsClient, ConstructorOptions } from '../alerts_client'; +import { ruleDataServiceMock } from '../../rule_data_plugin_service/rule_data_plugin_service.mock'; + +describe('remove cases from alerts', () => { + describe('removeCaseIdFromAlerts', () => { + const alertingAuthMock = alertingAuthorizationMock.create(); + const esClientMock = elasticsearchClientMock.createElasticsearchClient(); + const auditLogger = auditLoggerMock.create(); + const caseId = 'test-case'; + const alerts = [ + { + id: 'alert-id', + index: 'alert-index', + }, + ]; + + const alertsClientParams: jest.Mocked = { + logger: loggingSystemMock.create().get(), + authorization: alertingAuthMock, + esClient: esClientMock, + auditLogger, + ruleDataService: ruleDataServiceMock.create(), + }; + + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('removes alerts from a case', async () => { + const alertsClient = new AlertsClient(alertsClientParams); + await alertsClient.removeCaseIdFromAlerts({ caseId, alerts }); + + expect(esClientMock.bulk.mock.calls[0][0]).toMatchInlineSnapshot(` + Object { + "body": Array [ + Object { + "update": Object { + "_id": "alert-id", + "_index": "alert-index", + }, + }, + Object { + "script": Object { + "lang": "painless", + "source": "if (ctx._source['kibana.alert.case_ids'] != null) { + if (ctx._source['kibana.alert.case_ids'].contains('test-case')) { + int index = ctx._source['kibana.alert.case_ids'].indexOf('test-case'); + ctx._source['kibana.alert.case_ids'].remove(index); + } + }", + }, + }, + ], + "refresh": "wait_for", + } + `); + }); + + it('does not do any calls if there are no alerts', async () => { + const alertsClient = new AlertsClient(alertsClientParams); + await alertsClient.removeCaseIdFromAlerts({ caseId, alerts: [] }); + + expect(esClientMock.bulk).not.toHaveBeenCalled(); + }); + }); + + describe('removeCaseIdsFromAllAlerts', () => { + const alertingAuthMock = alertingAuthorizationMock.create(); + const esClientMock = elasticsearchClientMock.createElasticsearchClient(); + const auditLogger = auditLoggerMock.create(); + const caseIds = ['test-case-1', 'test-case-2']; + + const alertsClientParams: jest.Mocked = { + logger: loggingSystemMock.create().get(), + authorization: alertingAuthMock, + esClient: esClientMock, + auditLogger, + ruleDataService: ruleDataServiceMock.create(), + }; + + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('removes alerts from a case', async () => { + const alertsClient = new AlertsClient(alertsClientParams); + await alertsClient.removeCaseIdsFromAllAlerts({ caseIds }); + + expect(esClientMock.updateByQuery.mock.calls[0][0]).toMatchInlineSnapshot(` + Object { + "body": Object { + "query": Object { + "bool": Object { + "filter": Array [ + Object { + "bool": Object { + "minimum_should_match": 1, + "should": Array [ + Object { + "bool": Object { + "minimum_should_match": 1, + "should": Array [ + Object { + "match": Object { + "kibana.alert.case_ids": "test-case-1", + }, + }, + ], + }, + }, + Object { + "bool": Object { + "minimum_should_match": 1, + "should": Array [ + Object { + "match": Object { + "kibana.alert.case_ids": "test-case-2", + }, + }, + ], + }, + }, + ], + }, + }, + ], + "must": Array [], + "must_not": Array [], + "should": Array [], + }, + }, + "script": Object { + "lang": "painless", + "params": Object { + "caseIds": Array [ + "test-case-1", + "test-case-2", + ], + }, + "source": "if (ctx._source['kibana.alert.case_ids'] != null && ctx._source['kibana.alert.case_ids'].length > 0 && params['caseIds'] != null && params['caseIds'].length > 0) { + List storedCaseIds = ctx._source['kibana.alert.case_ids']; + List caseIdsToRemove = params['caseIds']; + + for (int i=0; i < caseIdsToRemove.length; i++) { + if (storedCaseIds.contains(caseIdsToRemove[i])) { + int index = storedCaseIds.indexOf(caseIdsToRemove[i]); + storedCaseIds.remove(index); + } + } + }", + }, + }, + "conflicts": "proceed", + "ignore_unavailable": true, + "index": "undefined-*", + "refresh": true, + } + `); + }); + + it('does not do any calls if there are no cases', async () => { + const alertsClient = new AlertsClient(alertsClientParams); + await alertsClient.removeCaseIdsFromAllAlerts({ caseIds: [] }); + + expect(esClientMock.updateByQuery).not.toHaveBeenCalled(); + }); + }); +}); 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 08da41280f9be..418c9f3c81bda 100644 --- a/x-pack/test/cases_api_integration/common/lib/alerts.ts +++ b/x-pack/test/cases_api_integration/common/lib/alerts.ts @@ -12,7 +12,7 @@ import { ToolingLog } from '@kbn/tooling-log'; import { DETECTION_ENGINE_QUERY_SIGNALS_URL } from '@kbn/security-solution-plugin/common/constants'; import { DetectionAlert } from '@kbn/security-solution-plugin/common/detection_engine/schemas/alerts'; import { RiskEnrichmentFields } from '@kbn/security-solution-plugin/server/lib/detection_engine/rule_types/utils/enrichments/types'; -import { CommentType } from '@kbn/cases-plugin/common'; +import { CaseResponse, CommentType } from '@kbn/cases-plugin/common'; import { ALERT_CASE_IDS } from '@kbn/rule-data-utils'; import { getRuleForSignalTesting, @@ -25,7 +25,7 @@ import { import { superUser } from './authentication/users'; import { User } from './authentication/types'; import { getSpaceUrlPrefix } from './api/helpers'; -import { createCase } from './api/case'; +import { createCase, deleteCases } from './api/case'; import { createComment, deleteAllComments } from './api'; import { postCaseReq } from './mock'; @@ -102,6 +102,100 @@ export const createCaseAttachAlertAndDeleteAlert = async ({ alerts: Alerts; getAlerts: (alerts: Alerts) => Promise>>; }) => { + const updatedCases = await createCaseAndAttachAlert({ + supertest, + totalCases, + owner, + alerts, + getAlerts, + }); + + const caseToDelete = updatedCases[indexOfCaseToDelete]; + + await deleteAllComments({ + supertest, + caseId: caseToDelete.id, + expectedHttpCode, + auth: deleteCommentAuth, + }); + + const alertAfterDeletion = await getAlerts(alerts); + const caseIdsWithoutRemovedCase = getCaseIdsWithoutRemovedCases({ + expectedHttpCode, + updatedCases, + caseIdsToDelete: [caseToDelete.id], + }); + + for (const alert of alertAfterDeletion) { + expect(alert[ALERT_CASE_IDS]).eql(caseIdsWithoutRemovedCase); + } +}; + +export const createCaseAttachAlertAndDeleteCase = async ({ + supertest, + totalCases, + indicesOfCaseToDelete, + owner, + expectedHttpCode = 204, + deleteCaseAuth = { user: superUser, space: 'space1' }, + alerts, + getAlerts, +}: { + supertest: SuperTest.SuperTest; + totalCases: number; + indicesOfCaseToDelete: number[]; + owner: string; + expectedHttpCode?: number; + deleteCaseAuth?: { user: User; space: string | null }; + alerts: Alerts; + getAlerts: (alerts: Alerts) => Promise>>; +}) => { + const updatedCases = await createCaseAndAttachAlert({ + supertest, + totalCases, + owner, + alerts, + getAlerts, + }); + + const casesToDelete = updatedCases.filter((_, filterIndex) => + indicesOfCaseToDelete.some((indexToDelete) => indexToDelete === filterIndex) + ); + + const caseIdsToDelete = casesToDelete.map((theCase) => theCase.id); + + await deleteCases({ + supertest, + caseIDs: caseIdsToDelete, + expectedHttpCode, + auth: deleteCaseAuth, + }); + + const alertAfterDeletion = await getAlerts(alerts); + const caseIdsWithoutRemovedCase = getCaseIdsWithoutRemovedCases({ + expectedHttpCode, + updatedCases, + caseIdsToDelete, + }); + + for (const alert of alertAfterDeletion) { + expect(alert[ALERT_CASE_IDS]).eql(caseIdsWithoutRemovedCase); + } +}; + +export const createCaseAndAttachAlert = async ({ + supertest, + totalCases, + owner, + alerts, + getAlerts, +}: { + supertest: SuperTest.SuperTest; + totalCases: number; + owner: string; + alerts: Alerts; + getAlerts: (alerts: Alerts) => Promise>>; +}): Promise => { const cases = await Promise.all( [...Array(totalCases).keys()].map((index) => createCase( @@ -147,25 +241,21 @@ export const createCaseAttachAlertAndDeleteAlert = async ({ expect(alert[ALERT_CASE_IDS]).eql(caseIds); } - const caseToDelete = updatedCases[indexOfCaseToDelete]; - - await deleteAllComments({ - supertest, - caseId: caseToDelete.id, - expectedHttpCode, - auth: deleteCommentAuth, - }); - - const alertAfterDeletion = await getAlerts(alerts); - - const caseIdsWithoutRemovedCase = - expectedHttpCode === 204 - ? updatedCases - .filter((theCase) => theCase.id !== caseToDelete.id) - .map((theCase) => theCase.id) - : updatedCases.map((theCase) => theCase.id); + return updatedCases; +}; - for (const alert of alertAfterDeletion) { - expect(alert[ALERT_CASE_IDS]).eql(caseIdsWithoutRemovedCase); - } +export const getCaseIdsWithoutRemovedCases = ({ + updatedCases, + caseIdsToDelete, + expectedHttpCode, +}: { + expectedHttpCode: number; + updatedCases: Array<{ id: string }>; + caseIdsToDelete: string[]; +}) => { + return expectedHttpCode === 204 + ? updatedCases + .filter((theCase) => !caseIdsToDelete.some((id) => theCase.id === id)) + .map((theCase) => theCase.id) + : updatedCases.map((theCase) => theCase.id); }; 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 981a368019e8f..b1df6d69afe3a 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 @@ -8,6 +8,13 @@ import expect from '@kbn/expect'; import type SuperTest from 'supertest'; import { MAX_DOCS_PER_PAGE } from '@kbn/cases-plugin/common/constants'; +import { + Alerts, + createCaseAttachAlertAndDeleteCase, + createSecuritySolutionAlerts, + getAlertById, + getSecuritySolutionAlerts, +} from '../../../../common/lib/alerts'; import { FtrProviderContext } from '../../../../common/ftr_provider_context'; import { @@ -39,6 +46,10 @@ import { noKibanaPrivileges, obsOnly, superUser, + obsOnlyReadAlerts, + obsSec, + secSolutionOnlyReadNoIndexAlerts, + secOnlyReadAlerts, } from '../../../../common/lib/authentication/users'; import { secAllUser, @@ -51,12 +62,19 @@ import { SECURITY_SOLUTION_FILE_KIND, } from '../../../../common/lib/constants'; import { User } from '../../../../common/lib/authentication/types'; +import { + createSignalsIndex, + deleteAllRules, + deleteSignalsIndex, +} from '../../../../../detection_engine_api_integration/utils'; // eslint-disable-next-line import/no-default-export export default ({ getService }: FtrProviderContext): void => { const supertestWithoutAuth = getService('supertestWithoutAuth'); const supertest = getService('supertest'); const es = getService('es'); + const esArchiver = getService('esArchiver'); + const log = getService('log'); describe('delete_cases', () => { afterEach(async () => { @@ -209,6 +227,197 @@ export default ({ getService }: FtrProviderContext): void => { }); }); + describe('alerts', () => { + describe('security_solution', () => { + let alerts: Alerts = []; + + const getAlerts = async (_alerts: Alerts) => { + await es.indices.refresh({ index: _alerts.map((alert) => alert._index) }); + const updatedAlerts = await getSecuritySolutionAlerts( + supertest, + alerts.map((alert) => alert._id) + ); + + return updatedAlerts.hits.hits.map((alert) => ({ ...alert._source })); + }; + + beforeEach(async () => { + await esArchiver.load('x-pack/test/functional/es_archives/auditbeat/hosts'); + await createSignalsIndex(supertest, log); + const signals = await createSecuritySolutionAlerts(supertest, log); + alerts = [signals.hits.hits[0], signals.hits.hits[1]]; + }); + + afterEach(async () => { + await deleteSignalsIndex(supertest, log); + await deleteAllRules(supertest, log); + await esArchiver.unload('x-pack/test/functional/es_archives/auditbeat/hosts'); + }); + + it('removes a case from the alert schema when deleting a case', async () => { + await createCaseAttachAlertAndDeleteCase({ + supertest: supertestWithoutAuth, + totalCases: 1, + indicesOfCaseToDelete: [0], + owner: 'securitySolutionFixture', + alerts, + getAlerts, + }); + }); + + it('removes multiple cases from the alert schema when deleting all cases', async () => { + await createCaseAttachAlertAndDeleteCase({ + supertest: supertestWithoutAuth, + totalCases: 2, + indicesOfCaseToDelete: [0, 1], + owner: 'securitySolutionFixture', + alerts, + getAlerts, + }); + }); + + it('removes multiple cases from the alert schema when deleting multiple cases', async () => { + await createCaseAttachAlertAndDeleteCase({ + supertest: supertestWithoutAuth, + totalCases: 4, + indicesOfCaseToDelete: [0, 2], + owner: 'securitySolutionFixture', + alerts, + getAlerts, + }); + }); + + it('should delete case ID from the alert schema when the user has read access only', async () => { + await createCaseAttachAlertAndDeleteCase({ + supertest: supertestWithoutAuth, + totalCases: 1, + indicesOfCaseToDelete: [0], + expectedHttpCode: 204, + owner: 'securitySolutionFixture', + alerts, + getAlerts, + deleteCaseAuth: { user: secOnlyReadAlerts, space: 'space1' }, + }); + }); + + it('should delete case ID from the alert schema when the user does NOT have access to the alert', async () => { + await createCaseAttachAlertAndDeleteCase({ + supertest: supertestWithoutAuth, + totalCases: 1, + indicesOfCaseToDelete: [0], + expectedHttpCode: 204, + owner: 'securitySolutionFixture', + alerts, + getAlerts, + deleteCaseAuth: { user: obsSec, space: 'space1' }, + }); + }); + + it('should delete the case ID from the alert schema when the user has read access to the kibana feature but no read access to the ES index', async () => { + await createCaseAttachAlertAndDeleteCase({ + supertest: supertestWithoutAuth, + totalCases: 1, + indicesOfCaseToDelete: [0], + owner: 'securitySolutionFixture', + alerts, + getAlerts, + expectedHttpCode: 204, + deleteCaseAuth: { user: secSolutionOnlyReadNoIndexAlerts, space: 'space1' }, + }); + }); + }); + + describe('observability', () => { + const alerts = [ + { _id: 'NoxgpHkBqbdrfX07MqXV', _index: '.alerts-observability.apm.alerts' }, + { _id: 'space1alert', _index: '.alerts-observability.apm.alerts' }, + ]; + + const getAlerts = async (_alerts: Alerts) => { + await es.indices.refresh({ index: '.alerts-observability.apm.alerts' }); + const updatedAlerts = await Promise.all( + _alerts.map((alert) => + getAlertById({ + supertest: supertestWithoutAuth, + id: alert._id, + index: alert._index, + auth: { user: superUser, space: 'space1' }, + }) + ) + ); + + return updatedAlerts as Array>; + }; + + beforeEach(async () => { + await esArchiver.load('x-pack/test/functional/es_archives/rule_registry/alerts'); + }); + + afterEach(async () => { + await esArchiver.unload('x-pack/test/functional/es_archives/rule_registry/alerts'); + }); + + it('removes a case from the alert schema when deleting a case', async () => { + await createCaseAttachAlertAndDeleteCase({ + supertest: supertestWithoutAuth, + totalCases: 1, + indicesOfCaseToDelete: [0], + owner: 'observabilityFixture', + alerts, + getAlerts, + }); + }); + + it('removes multiple cases from the alert schema when deleting all cases', async () => { + await createCaseAttachAlertAndDeleteCase({ + supertest: supertestWithoutAuth, + totalCases: 2, + indicesOfCaseToDelete: [0, 1], + owner: 'observabilityFixture', + alerts, + getAlerts, + }); + }); + + it('removes multiple cases from the alert schema when deleting multiple cases', async () => { + await createCaseAttachAlertAndDeleteCase({ + supertest: supertestWithoutAuth, + totalCases: 4, + indicesOfCaseToDelete: [0, 2], + owner: 'observabilityFixture', + alerts, + getAlerts, + }); + }); + + it('should delete case ID from the alert schema when the user has read access only', async () => { + await createCaseAttachAlertAndDeleteCase({ + supertest: supertestWithoutAuth, + totalCases: 1, + indicesOfCaseToDelete: [0], + expectedHttpCode: 204, + owner: 'observabilityFixture', + alerts, + getAlerts, + deleteCaseAuth: { user: obsOnlyReadAlerts, space: 'space1' }, + }); + }); + + it('should delete case ID from the alert schema when the user does NOT have access to the alert', async () => { + await createCaseAttachAlertAndDeleteCase({ + supertest: supertestWithoutAuth, + totalCases: 1, + indicesOfCaseToDelete: [0], + expectedHttpCode: 204, + owner: 'observabilityFixture', + alerts, + getAlerts, + deleteCaseAuth: { user: obsSec, space: 'space1' }, + }); + }); + }); + }); + describe('rbac', () => { describe('files', () => { // we need api_int_users and roles because they have authorization for the actual plugins (not the fixtures). This 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 317bd2797245b..e9ca52d903b5b 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 @@ -7,6 +7,7 @@ import expect from '@kbn/expect'; import { + Alerts, createCaseAttachAlertAndDeleteAlert, createSecuritySolutionAlerts, getAlertById, @@ -109,8 +110,6 @@ export default ({ getService }: FtrProviderContext): void => { }); describe('alerts', () => { - type Alerts = Array<{ _id: string; _index: string }>; - describe('security_solution', () => { let alerts: Alerts = []; @@ -172,7 +171,7 @@ export default ({ getService }: FtrProviderContext): void => { }); }); - it('should NOT delete case ID from the alert schema when the user does NOT have access to the alert', async () => { + it('should delete case ID from the alert schema when the user does NOT have access to the alert', async () => { await createCaseAttachAlertAndDeleteAlert({ supertest: supertestWithoutAuth, totalCases: 1, @@ -180,7 +179,7 @@ export default ({ getService }: FtrProviderContext): void => { owner: 'securitySolutionFixture', alerts, getAlerts, - expectedHttpCode: 403, + expectedHttpCode: 204, deleteCommentAuth: { user: obsSec, space: 'space1' }, }); }); @@ -206,6 +205,7 @@ export default ({ getService }: FtrProviderContext): void => { ]; const getAlerts = async (_alerts: Alerts) => { + await es.indices.refresh({ index: '.alerts-observability.apm.alerts' }); const updatedAlerts = await Promise.all( _alerts.map((alert) => getAlertById({ @@ -263,12 +263,12 @@ export default ({ getService }: FtrProviderContext): void => { }); }); - it('should NOT delete case ID from the alert schema when the user does NOT have access to the alert', async () => { + it('should delete case ID from the alert schema when the user does NOT have access to the alert', async () => { await createCaseAttachAlertAndDeleteAlert({ supertest: supertestWithoutAuth, totalCases: 1, indexOfCaseToDelete: 0, - expectedHttpCode: 403, + expectedHttpCode: 204, owner: 'observabilityFixture', alerts, getAlerts, 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 7ac1cc4f9de77..3557e50464666 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 @@ -214,7 +214,7 @@ export default ({ getService }: FtrProviderContext): void => { }); }); - it('should NOT delete case ID from the alert schema when the user does NOT have access to the alert', async () => { + it('should delete case ID from the alert schema when the user does NOT have access to the alert', async () => { await createCaseAttachAlertAndDeleteAlert({ supertest: supertestWithoutAuth, totalCases: 1, @@ -222,7 +222,7 @@ export default ({ getService }: FtrProviderContext): void => { owner: 'securitySolutionFixture', alerts, getAlerts, - expectedHttpCode: 403, + expectedHttpCode: 204, deleteCommentAuth: { user: obsSec, space: 'space1' }, }); }); @@ -248,6 +248,7 @@ export default ({ getService }: FtrProviderContext): void => { ]; const getAlerts = async (_alerts: Alerts) => { + await es.indices.refresh({ index: '.alerts-observability.apm.alerts' }); const updatedAlerts = await Promise.all( _alerts.map((alert) => getAlertById({ @@ -346,12 +347,12 @@ export default ({ getService }: FtrProviderContext): void => { }); }); - it('should NOT delete case ID from the alert schema when the user does NOT have access to the alert', async () => { + it('should delete case ID from the alert schema when the user does NOT have access to the alert', async () => { await createCaseAttachAlertAndDeleteAlert({ supertest: supertestWithoutAuth, totalCases: 1, indexOfCaseToDelete: 0, - expectedHttpCode: 403, + expectedHttpCode: 204, owner: 'observabilityFixture', alerts, getAlerts, From f843932593b1be973cd1a3daff0697fca090fad0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alejandro=20Fern=C3=A1ndez=20Haro?= Date: Tue, 18 Apr 2023 18:37:23 +0200 Subject: [PATCH 08/78] [Telemetry] Saved Objects fields cleanup (#155165) --- .../group2/check_registered_types.test.ts | 2 +- .../register_telemetry_saved_object.ts | 28 ++----------------- 2 files changed, 3 insertions(+), 27 deletions(-) diff --git a/src/core/server/integration_tests/saved_objects/migrations/group2/check_registered_types.test.ts b/src/core/server/integration_tests/saved_objects/migrations/group2/check_registered_types.test.ts index 4ef2e37cad8e6..a6428387f65f3 100644 --- a/src/core/server/integration_tests/saved_objects/migrations/group2/check_registered_types.test.ts +++ b/src/core/server/integration_tests/saved_objects/migrations/group2/check_registered_types.test.ts @@ -143,7 +143,7 @@ describe('checking migration metadata changes on all registered SO types', () => "synthetics-privates-locations": "7d032fc788905e32152029ae7ab3d6038c48ae44", "tag": "87f21f07df9cc37001b15a26e413c18f50d1fbfe", "task": "533ee80c50c47f0505846bfac73fc10962c5bc45", - "telemetry": "561b329aaed3c15b91aaf2075645be3097247612", + "telemetry": "3b3b89cf411a2a2e60487cef6ccdbc5df691aeb9", "ui-metric": "410a8ad28e0f44b161c960ff0ce950c712b17c52", "upgrade-assistant-ml-upgrade-operation": "d8816e5ce32649e7a3a43e2c406c632319ff84bb", "upgrade-assistant-reindex-operation": "09ac8ed9c9acf7e8ece8eafe47d7019ea1472144", diff --git a/src/plugins/telemetry/server/saved_objects/register_telemetry_saved_object.ts b/src/plugins/telemetry/server/saved_objects/register_telemetry_saved_object.ts index 20b2ab6093514..17cae888c58dd 100644 --- a/src/plugins/telemetry/server/saved_objects/register_telemetry_saved_object.ts +++ b/src/plugins/telemetry/server/saved_objects/register_telemetry_saved_object.ts @@ -17,32 +17,8 @@ export function registerTelemetrySavedObject( hidden: true, namespaceType: 'agnostic', mappings: { - properties: { - enabled: { - type: 'boolean', - }, - sendUsageFrom: { - type: 'keyword', - }, - lastReported: { - type: 'date', - }, - lastVersionChecked: { - type: 'keyword', - }, - userHasSeenNotice: { - type: 'boolean', - }, - reportFailureCount: { - type: 'integer', - }, - reportFailureVersion: { - type: 'keyword', - }, - allowChangingOptInStatus: { - type: 'boolean', - }, - }, + dynamic: false, + properties: {}, }, }); } From 5af2fd42f1879a6eb63755eac99f32d8dc14e683 Mon Sep 17 00:00:00 2001 From: Justin Kambic Date: Tue, 18 Apr 2023 12:58:53 -0400 Subject: [PATCH 09/78] [Synthetics] Apply faster backoff for non-`test-now` screenshot results (#152874) ## Summary Resolves #152754. It was noted in #152754 that we're getting some negative side effects from the exponential backoff we added to the getter for journey screenshots. When a screenshot is legitimately not available for an older journey (as opposed to not yet fully indexed for a fresh journey), the backoff will cause the screenshot never to render. ## Testing Follow a flow similar to what's illustrated in the GIF below. 1. Create a monitor with some screenshots that show up. 2. Delete the screenshot data stream. A good way is to use the Index Management > Data Streams view in Kibana. 3. Refresh your monitor's detail page when new photos are coming in. It should give up on finding the images very fast, and as they aren't there, you should see the missing image placeholder. 4. Start creating a new monitor. Make it have multiple steps. 5. Ensure that you aren't seeing any "infinite" loading states across the screenshot UIs. ![20230308110103](https://user-images.githubusercontent.com/18429259/223766486-eaca4295-9e4f-4490-8717-c1b915382ec7.gif) --- .../browser_steps_list.tsx | 1 + .../use_retrieve_step_image.ts | 76 +++++++++++-------- .../common/screenshot/empty_thumbnail.tsx | 19 +++-- .../screenshot/journey_screenshot_dialog.tsx | 4 +- ...journey_step_screenshot_container.test.tsx | 18 +---- .../journey_step_screenshot_container.tsx | 5 +- .../state/browser_journey/api.test.ts | 8 +- .../synthetics/state/browser_journey/api.ts | 24 +++++- 8 files changed, 95 insertions(+), 60 deletions(-) diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/common/monitor_test_result/browser_steps_list.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/common/monitor_test_result/browser_steps_list.tsx index 508b9d29c04a9..ccec4c1e8f60d 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/common/monitor_test_result/browser_steps_list.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/common/monitor_test_result/browser_steps_list.tsx @@ -155,6 +155,7 @@ export const BrowserStepsList = ({ allStepsLoaded={!loading} retryFetchOnRevisit={true} size={screenshotImageSize} + testNowMode={testNowMode} timestamp={timestamp} /> ), diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/common/monitor_test_result/use_retrieve_step_image.ts b/x-pack/plugins/synthetics/public/apps/synthetics/components/common/monitor_test_result/use_retrieve_step_image.ts index 589b45c2c1c49..9f22bd9eb77f6 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/common/monitor_test_result/use_retrieve_step_image.ts +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/common/monitor_test_result/use_retrieve_step_image.ts @@ -6,15 +6,13 @@ */ import { useEffect, useMemo, useState } from 'react'; -import { useFetcher } from '@kbn/observability-plugin/public'; -import moment from 'moment'; import { ScreenshotImageBlob, ScreenshotRefImageData, isScreenshotRef, } from '../../../../../../common/runtime_types'; import { useComposeImageFromRef } from '../../../hooks/use_composite_image'; -import { getJourneyScreenshot } from '../../../state'; +import { BackoffOptions, getJourneyScreenshot } from '../../../state'; type ImageResponse = ScreenshotImageBlob | ScreenshotRefImageData | null; interface ImageDataResult { @@ -34,6 +32,7 @@ export const useRetrieveStepImage = ({ stepStatus, hasIntersected, imgPath, + testNowMode, retryFetchOnRevisit, }: { timestamp?: string; @@ -42,6 +41,8 @@ export const useRetrieveStepImage = ({ stepStatus?: string; hasIntersected: boolean; + testNowMode?: boolean; + /** * Whether to retry screenshot image fetch on revisit (when intersection change triggers). * Will only re-fetch if an image fetch wasn't successful in previous attempts. @@ -53,39 +54,54 @@ export const useRetrieveStepImage = ({ const [imgState, setImgState] = useState({}); const skippedStep = stepStatus === 'skipped'; - const dataResult = useGetStepScreenshotUrls(checkGroup, imgPath, imgState); - const isImageUrlAvailable = dataResult?.[imgPath]?.url ?? false; - - useFetcher(() => { - const is5MinutesOld = timestamp - ? moment(timestamp).isBefore(moment().subtract(5, 'minutes')) - : false; - const retrieveAttemptedBefore = (imgState[imgPath]?.attempts ?? 0) > 0; - const shouldRetry = retryFetchOnRevisit || !retrieveAttemptedBefore; + const imageResult = useGetStepScreenshotUrls(checkGroup, imgPath, imgState); + const isImageUrlAvailable = imageResult?.[imgPath]?.url ?? false; + + const shouldFetch = useMemo(() => { + const shouldRetry = retryFetchOnRevisit || !(imgState[imgPath]?.attempts ?? 0 > 0); + return !skippedStep && hasIntersected && !isImageUrlAvailable && shouldRetry && checkGroup; + }, [ + checkGroup, + hasIntersected, + imgPath, + imgState, + isImageUrlAvailable, + retryFetchOnRevisit, + skippedStep, + ]); - if (!skippedStep && hasIntersected && !isImageUrlAvailable && shouldRetry && checkGroup) { - setImgState((prevState) => { - return getUpdatedState({ prevState, imgPath, increment: true, loading: true }); - }); - return getJourneyScreenshot(imgPath, !is5MinutesOld) - .then((data) => { - setImgState((prevState) => { - return getUpdatedState({ prevState, imgPath, increment: false, data, loading: false }); - }); - - return data; - }) - .catch(() => { + useEffect(() => { + async function run() { + if (shouldFetch) { + setImgState((prevState) => { + return getUpdatedState({ prevState, imgPath, increment: true, loading: true }); + }); + const backoffOptions: Partial | undefined = !testNowMode + ? { shouldBackoff: false } + : undefined; + try { + getJourneyScreenshot(imgPath, backoffOptions).then((data) => + setImgState((prevState) => + getUpdatedState({ + prevState, + imgPath, + increment: false, + data, + loading: false, + }) + ) + ); + } catch (e: unknown) { setImgState((prevState) => { return getUpdatedState({ prevState, imgPath, increment: false, loading: false }); }); - }); - } else { - return new Promise((resolve) => resolve(null)); + } + } } - }, [skippedStep, hasIntersected, imgPath, retryFetchOnRevisit, timestamp]); + run(); + }, [imgPath, shouldFetch, testNowMode]); - return dataResult; + return imageResult; }; /** diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/common/screenshot/empty_thumbnail.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/common/screenshot/empty_thumbnail.tsx index 9602bf83b330a..7309d54aa08da 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/common/screenshot/empty_thumbnail.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/common/screenshot/empty_thumbnail.tsx @@ -11,8 +11,8 @@ import { useEuiTheme, useEuiBackgroundColor, EuiIcon, - EuiLoadingContent, EuiText, + EuiSkeletonRectangle, } from '@elastic/eui'; import { @@ -63,14 +63,23 @@ export const EmptyThumbnail = ({ ...(borderRadius ? { borderRadius } : {}), }} > - {isLoading ? ( - + {/* `children` is required by this component, even though we'll replace it. */} + + + ) : // when we're loading and `animateLoading` is false we simply render an un-animated div to fill the space + isLoading ? ( +
) : (
{ + onKeyDown={(_evt) => { // Just to satisfy ESLint }} > diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/common/screenshot/journey_step_screenshot_container.test.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/common/screenshot/journey_step_screenshot_container.test.tsx index 9f5e313b78dd3..70037635c4445 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/common/screenshot/journey_step_screenshot_container.test.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/common/screenshot/journey_step_screenshot_container.test.tsx @@ -9,7 +9,6 @@ import React from 'react'; import { fireEvent, waitFor, waitForElementToBeRemoved } from '@testing-library/react'; import { JourneyStepScreenshotContainer } from './journey_step_screenshot_container'; import { render } from '../../../utils/testing'; -import * as observabilityPublic from '@kbn/observability-plugin/public'; import * as retrieveHooks from '../monitor_test_result/use_retrieve_step_image'; import { getScreenshotUrl } from './journey_screenshot_dialog'; @@ -39,8 +38,8 @@ const testImageDataResult = { }; describe('JourneyStepScreenshotContainer', () => { + afterEach(() => jest.clearAllMocks()); let checkGroup: string; - const { FETCH_STATUS } = observabilityPublic; beforeAll(() => { checkGroup = 'test-check-group'; @@ -50,21 +49,8 @@ describe('JourneyStepScreenshotContainer', () => { jest.clearAllMocks(); }); - it.each([[FETCH_STATUS.PENDING], [FETCH_STATUS.LOADING]])( - 'displays spinner when loading step image', - (fetchStatus) => { - jest - .spyOn(observabilityPublic, 'useFetcher') - .mockReturnValue({ status: fetchStatus, data: null, refetch: () => null, loading: true }); - const { getByTestId } = render(); - expect(getByTestId('stepScreenshotPlaceholderLoading')).toBeInTheDocument(); - } - ); - it('displays no image available when img src is unavailable and fetch status is successful', () => { - jest - .spyOn(observabilityPublic, 'useFetcher') - .mockReturnValue({ status: FETCH_STATUS.SUCCESS, data: null, refetch: () => null }); + jest.spyOn(retrieveHooks, 'useRetrieveStepImage').mockReturnValue(undefined); const { getByTestId } = render( ); diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/common/screenshot/journey_step_screenshot_container.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/common/screenshot/journey_step_screenshot_container.tsx index f4fd7470545e7..174efc41dfe08 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/common/screenshot/journey_step_screenshot_container.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/common/screenshot/journey_step_screenshot_container.tsx @@ -23,17 +23,19 @@ interface Props { allStepsLoaded?: boolean; retryFetchOnRevisit?: boolean; // Set to `true` for "Run Once" / "Test Now" modes size?: ScreenshotImageSize; + testNowMode?: boolean; unavailableMessage?: string; borderRadius?: number | string; } export const JourneyStepScreenshotContainer = ({ + allStepsLoaded, timestamp, checkGroup, stepStatus, - allStepsLoaded, initialStepNumber = 1, retryFetchOnRevisit = false, + testNowMode, size = THUMBNAIL_SCREENSHOT_SIZE, unavailableMessage, borderRadius, @@ -58,6 +60,7 @@ export const JourneyStepScreenshotContainer = ({ imgPath, retryFetchOnRevisit, checkGroup, + testNowMode, timestamp, }); diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/state/browser_journey/api.test.ts b/x-pack/plugins/synthetics/public/apps/synthetics/state/browser_journey/api.test.ts index 7e5fd5aa8de05..35503455e4a8a 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/state/browser_journey/api.test.ts +++ b/x-pack/plugins/synthetics/public/apps/synthetics/state/browser_journey/api.test.ts @@ -81,7 +81,7 @@ describe('getJourneyScreenshot', () => { const mockFetch = jest.fn().mockResolvedValue(mockResponse); global.fetch = mockFetch; - const result = await getJourneyScreenshot(url, false); + const result = await getJourneyScreenshot(url, { shouldBackoff: false }); expect(result).toBeNull(); expect(mockFetch).toHaveBeenCalledTimes(1); }); @@ -146,7 +146,11 @@ describe('getJourneyScreenshot', () => { const mockFetch = jest.fn().mockReturnValue(mockResponse); global.fetch = mockFetch; - const result = await getJourneyScreenshot(url, true, maxRetry, initialBackoff); + const result = await getJourneyScreenshot(url, { + shouldBackoff: true, + maxRetry, + initialBackoff, + }); expect(result).toBeNull(); expect(mockFetch).toBeCalledTimes(maxRetry + 1); }); diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/state/browser_journey/api.ts b/x-pack/plugins/synthetics/public/apps/synthetics/state/browser_journey/api.ts index b4b352d388768..86b604e0f8f6b 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/state/browser_journey/api.ts +++ b/x-pack/plugins/synthetics/public/apps/synthetics/state/browser_journey/api.ts @@ -75,12 +75,24 @@ export async function fetchLastSuccessfulCheck({ ); } +export interface BackoffOptions { + shouldBackoff?: boolean; + maxRetry?: number; + initialBackoff?: number; +} + +const DEFAULT_SHOULD_BACKOFF = true; +const DEFAULT_MAX_RETRY = 8; +const DEFAULT_INITIAL_BACKOFF = 100; + export async function getJourneyScreenshot( imgSrc: string, - shouldBackoff = true, - maxRetry = 15, - initialBackoff = 100 + options?: Partial ): Promise { + const shouldBackoff = options?.shouldBackoff ?? DEFAULT_SHOULD_BACKOFF; + const maxRetry = options?.maxRetry ?? DEFAULT_MAX_RETRY; + const initialBackoff = options?.initialBackoff ?? DEFAULT_INITIAL_BACKOFF; + try { let retryCount = 0; @@ -89,8 +101,12 @@ export async function getJourneyScreenshot( while (response?.status !== 200) { const imgRequest = new Request(imgSrc); + if (retryCount > maxRetry) break; + response = await fetch(imgRequest); - if (!shouldBackoff || retryCount >= maxRetry || response.status !== 404) break; + + if (!shouldBackoff || response.status !== 404) break; + await new Promise((r) => setTimeout(r, (backoff *= 2))); retryCount++; } From 66f68d4123a333f4651662f70eb2465ade7e9081 Mon Sep 17 00:00:00 2001 From: Devon Thomson Date: Tue, 18 Apr 2023 13:04:26 -0400 Subject: [PATCH 10/78] [Controls] Add Expensive Queries Fallback (#155082) ## Summary If the user has no permission to check for the value of `allow_expensive_queries`, it will now default to true instead of false. --- .../options_list_cluster_settings_route.ts | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/plugins/controls/server/options_list/options_list_cluster_settings_route.ts b/src/plugins/controls/server/options_list/options_list_cluster_settings_route.ts index 3da1585405305..f3b4ea598b886 100644 --- a/src/plugins/controls/server/options_list/options_list_cluster_settings_route.ts +++ b/src/plugins/controls/server/options_list/options_list_cluster_settings_route.ts @@ -8,6 +8,7 @@ import { getKbnServerError, reportServerError } from '@kbn/kibana-utils-plugin/server'; import { CoreSetup } from '@kbn/core/server'; +import { errors } from '@elastic/elasticsearch'; export const setupOptionsListClusterSettingsRoute = ({ http }: CoreSetup) => { const router = http.createRouter(); @@ -39,6 +40,17 @@ export const setupOptionsListClusterSettingsRoute = ({ http }: CoreSetup) => { }, }); } catch (e) { + if (e instanceof errors.ResponseError && e.body.error.type === 'security_exception') { + /** + * in cases where the user does not have the 'monitor' permission this check will fail. In these cases, we will + * fall back to assume that the allowExpensiveQueries setting is on, because it defaults to true. + */ + return response.ok({ + body: { + allowExpensiveQueries: true, + }, + }); + } const kbnErr = getKbnServerError(e); return reportServerError(response, kbnErr); } From 17c4185fd7a21fea8be36d7c150accadf74d757a Mon Sep 17 00:00:00 2001 From: Kfir Peled <61654899+kfirpeled@users.noreply.github.com> Date: Tue, 18 Apr 2023 13:10:46 -0600 Subject: [PATCH 11/78] [Cloud Security] Update vulnerability management retention policy to 3 days (#155080) --- .../create_transforms/latest_vulnerabilities_transforms.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugins/cloud_security_posture/server/create_transforms/latest_vulnerabilities_transforms.ts b/x-pack/plugins/cloud_security_posture/server/create_transforms/latest_vulnerabilities_transforms.ts index 8fff7e910d471..9af2d91a2b730 100644 --- a/x-pack/plugins/cloud_security_posture/server/create_transforms/latest_vulnerabilities_transforms.ts +++ b/x-pack/plugins/cloud_security_posture/server/create_transforms/latest_vulnerabilities_transforms.ts @@ -32,7 +32,7 @@ export const latestVulnerabilitiesTransform: TransformPutTransformRequest = { retention_policy: { time: { field: '@timestamp', - max_age: '50h', + max_age: '3d', }, }, latest: { From 613619bee2c67b1db4a92166a9576d083fa34381 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alejandro=20Fern=C3=A1ndez=20Haro?= Date: Tue, 18 Apr 2023 21:38:18 +0200 Subject: [PATCH 12/78] [Telemetry] Use `soClient` in FTRs (#155175) --- test/api_integration/apis/telemetry/index.ts | 2 +- test/api_integration/apis/telemetry/opt_in.ts | 65 ++++----- .../apis/telemetry/telemetry_config.ts | 2 +- .../apis/telemetry/telemetry_last_reported.ts | 33 +++-- .../telemetry/telemetry_optin_notice_seen.ts | 22 ++- .../api_integration/apis/telemetry/index.ts | 2 - .../api_integration/apis/telemetry/opt_in.ts | 125 ------------------ .../telemetry/telemetry_optin_notice_seen.ts | 35 ----- 8 files changed, 59 insertions(+), 227 deletions(-) delete mode 100644 x-pack/test/api_integration/apis/telemetry/opt_in.ts delete mode 100644 x-pack/test/api_integration/apis/telemetry/telemetry_optin_notice_seen.ts diff --git a/test/api_integration/apis/telemetry/index.ts b/test/api_integration/apis/telemetry/index.ts index 3afe1ef304b27..a92ae4e1b8481 100644 --- a/test/api_integration/apis/telemetry/index.ts +++ b/test/api_integration/apis/telemetry/index.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import { FtrProviderContext } from '../../ftr_provider_context'; +import type { FtrProviderContext } from '../../ftr_provider_context'; export default function ({ loadTestFile }: FtrProviderContext) { describe('Telemetry', () => { diff --git a/test/api_integration/apis/telemetry/opt_in.ts b/test/api_integration/apis/telemetry/opt_in.ts index 8d0fb6725bacf..943d7534acc0a 100644 --- a/test/api_integration/apis/telemetry/opt_in.ts +++ b/test/api_integration/apis/telemetry/opt_in.ts @@ -7,17 +7,16 @@ */ import expect from '@kbn/expect'; -import { Client } from '@elastic/elasticsearch'; -import { TelemetrySavedObjectAttributes } from '@kbn/telemetry-plugin/server/saved_objects'; import SuperTest from 'supertest'; -import { FtrProviderContext } from '../../ftr_provider_context'; +import type { KbnClient } from '@kbn/test'; +import type { TelemetrySavedObjectAttributes } from '@kbn/telemetry-plugin/server/saved_objects'; +import type { FtrProviderContext } from '../../ftr_provider_context'; export default function optInTest({ getService }: FtrProviderContext) { const supertest = getService('supertest'); const kibanaServer = getService('kibanaServer'); const esArchiver = getService('esArchiver'); - const esClient: Client = getService('es'); describe('/api/telemetry/v2/optIn API', () => { let defaultAttributes: TelemetrySavedObjectAttributes; @@ -26,39 +25,39 @@ export default function optInTest({ getService }: FtrProviderContext) { await esArchiver.emptyKibanaIndex(); const kibanaVersionAccessor = kibanaServer.version; kibanaVersion = await kibanaVersionAccessor.get(); - defaultAttributes = await getSavedObjectAttributes(esClient); + defaultAttributes = await getSavedObjectAttributes(kibanaServer); expect(typeof kibanaVersion).to.eql('string'); expect(kibanaVersion.length).to.be.greaterThan(0); }); afterEach(async () => { - await updateSavedObjectAttributes(esClient, defaultAttributes); + await updateSavedObjectAttributes(kibanaServer, defaultAttributes); }); it('should support sending false with allowChangingOptInStatus true', async () => { - await updateSavedObjectAttributes(esClient, { + await updateSavedObjectAttributes(kibanaServer, { allowChangingOptInStatus: true, }); await postTelemetryV2OptIn(supertest, false, 200); - const { enabled, lastVersionChecked } = await getSavedObjectAttributes(esClient); + const { enabled, lastVersionChecked } = await getSavedObjectAttributes(kibanaServer); expect(enabled).to.be(false); expect(lastVersionChecked).to.be(kibanaVersion); }); it('should support sending true with allowChangingOptInStatus true', async () => { - await updateSavedObjectAttributes(esClient, { + await updateSavedObjectAttributes(kibanaServer, { ...defaultAttributes, allowChangingOptInStatus: true, }); await postTelemetryV2OptIn(supertest, true, 200); - const { enabled, lastVersionChecked } = await getSavedObjectAttributes(esClient); + const { enabled, lastVersionChecked } = await getSavedObjectAttributes(kibanaServer); expect(enabled).to.be(true); expect(lastVersionChecked).to.be(kibanaVersion); }); it('should not support sending false with allowChangingOptInStatus false', async () => { - await updateSavedObjectAttributes(esClient, { + await updateSavedObjectAttributes(kibanaServer, { ...defaultAttributes, allowChangingOptInStatus: false, }); @@ -66,7 +65,7 @@ export default function optInTest({ getService }: FtrProviderContext) { }); it('should not support sending true with allowChangingOptInStatus false', async () => { - await updateSavedObjectAttributes(esClient, { + await updateSavedObjectAttributes(kibanaServer, { ...defaultAttributes, allowChangingOptInStatus: false, }); @@ -98,29 +97,31 @@ async function postTelemetryV2OptIn( } async function updateSavedObjectAttributes( - es: Client, + client: KbnClient, attributes: TelemetrySavedObjectAttributes ): Promise { - // Directly writing to the `.kibana` index because the SO type now is hidden, meaning it's not exposed via the SO HTTP API - await es.update({ - index: '.kibana', - id: 'telemetry:telemetry', - doc: { - telemetry: attributes, - // there are many missing fields in the SO, hopefully it doesn't break Kibana - }, - doc_as_upsert: true, + await client.savedObjects.create({ + type: 'telemetry', + id: 'telemetry', + overwrite: true, + attributes, }); } -async function getSavedObjectAttributes(es: Client): Promise { - // Directly writing to the `.kibana` index because the SO type now is hidden, meaning it's not exposed via the SO HTTP API - const { _source: body } = await es.get<{ telemetry: TelemetrySavedObjectAttributes }>( - { - index: '.kibana', - id: 'telemetry:telemetry', - }, - { ignore: [404] } - ); - return body?.telemetry || {}; +async function getSavedObjectAttributes( + client: KbnClient +): Promise { + try { + const body = await client.savedObjects.get({ + type: 'telemetry', + id: 'telemetry', + }); + + return body.attributes; + } catch (err) { + if (err.response?.status === 404) { + return {}; + } + throw err; + } } diff --git a/test/api_integration/apis/telemetry/telemetry_config.ts b/test/api_integration/apis/telemetry/telemetry_config.ts index 7a03600a35647..f6dd12b0c2a9d 100644 --- a/test/api_integration/apis/telemetry/telemetry_config.ts +++ b/test/api_integration/apis/telemetry/telemetry_config.ts @@ -7,7 +7,7 @@ */ import { AxiosError } from 'axios'; -import { FtrProviderContext } from '../../ftr_provider_context'; +import type { FtrProviderContext } from '../../ftr_provider_context'; const TELEMETRY_SO_TYPE = 'telemetry'; const TELEMETRY_SO_ID = 'telemetry'; diff --git a/test/api_integration/apis/telemetry/telemetry_last_reported.ts b/test/api_integration/apis/telemetry/telemetry_last_reported.ts index 37097c4cbf2f5..e553fa0218aa1 100644 --- a/test/api_integration/apis/telemetry/telemetry_last_reported.ts +++ b/test/api_integration/apis/telemetry/telemetry_last_reported.ts @@ -7,21 +7,15 @@ */ import expect from '@kbn/expect'; -import { FtrProviderContext } from '../../ftr_provider_context'; +import type { FtrProviderContext } from '../../ftr_provider_context'; export default function optInTest({ getService }: FtrProviderContext) { - const client = getService('es'); + const client = getService('kibanaServer'); const supertest = getService('supertest'); describe('/api/telemetry/v2/last_reported API Telemetry lastReported', () => { before(async () => { - await client.delete( - { - index: '.kibana', - id: 'telemetry:telemetry', - }, - { ignore: [404] } - ); + await client.savedObjects.delete({ type: 'telemetry', id: 'telemetry' }); }); it('GET should return undefined when there is no stored telemetry.lastReported value', async () => { @@ -31,22 +25,25 @@ export default function optInTest({ getService }: FtrProviderContext) { it('PUT should update telemetry.lastReported to now', async () => { await supertest.put('/api/telemetry/v2/last_reported').set('kbn-xsrf', 'xxx').expect(200); - const { _source } = await client.get<{ telemetry: { lastReported: number } }>({ - index: '.kibana', - id: 'telemetry:telemetry', + const { + attributes: { lastReported }, + } = await client.savedObjects.get<{ lastReported: number }>({ + type: 'telemetry', + id: 'telemetry', }); - expect(_source?.telemetry.lastReported).to.be.a('number'); + expect(lastReported).to.be.a('number'); }); it('GET should return the previously stored lastReported value', async () => { - const { _source } = await client.get<{ telemetry: { lastReported: number } }>({ - index: '.kibana', - id: 'telemetry:telemetry', + const { + attributes: { lastReported }, + } = await client.savedObjects.get<{ lastReported: number }>({ + type: 'telemetry', + id: 'telemetry', }); - expect(_source?.telemetry.lastReported).to.be.a('number'); - const lastReported = _source?.telemetry.lastReported; + expect(lastReported).to.be.a('number'); await supertest .get('/api/telemetry/v2/last_reported') diff --git a/test/api_integration/apis/telemetry/telemetry_optin_notice_seen.ts b/test/api_integration/apis/telemetry/telemetry_optin_notice_seen.ts index a2c48996069b2..53b0d2cadca64 100644 --- a/test/api_integration/apis/telemetry/telemetry_optin_notice_seen.ts +++ b/test/api_integration/apis/telemetry/telemetry_optin_notice_seen.ts @@ -7,30 +7,26 @@ */ import expect from '@kbn/expect'; -import { FtrProviderContext } from '../../ftr_provider_context'; +import type { FtrProviderContext } from '../../ftr_provider_context'; export default function optInTest({ getService }: FtrProviderContext) { - const client = getService('es'); + const client = getService('kibanaServer'); const supertest = getService('supertest'); describe('/api/telemetry/v2/userHasSeenNotice API Telemetry User has seen OptIn Notice', () => { it('should update telemetry setting field via PUT', async () => { - await client.delete( - { - index: '.kibana', - id: 'telemetry:telemetry', - }, - { ignore: [404] } - ); + await client.savedObjects.delete({ type: 'telemetry', id: 'telemetry' }); await supertest.put('/api/telemetry/v2/userHasSeenNotice').set('kbn-xsrf', 'xxx').expect(200); - const { _source } = await client.get<{ telemetry: { userHasSeenNotice: boolean } }>({ - index: '.kibana', - id: 'telemetry:telemetry', + const { + attributes: { userHasSeenNotice }, + } = await client.savedObjects.get<{ userHasSeenNotice: boolean }>({ + type: 'telemetry', + id: 'telemetry', }); - expect(_source?.telemetry.userHasSeenNotice).to.be(true); + expect(userHasSeenNotice).to.be(true); }); }); } diff --git a/x-pack/test/api_integration/apis/telemetry/index.ts b/x-pack/test/api_integration/apis/telemetry/index.ts index 9fc67a35e6b19..e7e03d70aea72 100644 --- a/x-pack/test/api_integration/apis/telemetry/index.ts +++ b/x-pack/test/api_integration/apis/telemetry/index.ts @@ -11,7 +11,5 @@ export default function ({ loadTestFile }: FtrProviderContext) { describe('Telemetry', () => { loadTestFile(require.resolve('./telemetry')); loadTestFile(require.resolve('./telemetry_local')); - loadTestFile(require.resolve('./opt_in')); - loadTestFile(require.resolve('./telemetry_optin_notice_seen')); }); } diff --git a/x-pack/test/api_integration/apis/telemetry/opt_in.ts b/x-pack/test/api_integration/apis/telemetry/opt_in.ts deleted file mode 100644 index 33e977b118100..0000000000000 --- a/x-pack/test/api_integration/apis/telemetry/opt_in.ts +++ /dev/null @@ -1,125 +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 expect from '@kbn/expect'; -import { Client } from '@elastic/elasticsearch'; - -import { TelemetrySavedObjectAttributes } from '@kbn/telemetry-plugin/server/saved_objects'; -import SuperTest from 'supertest'; -import { FtrProviderContext } from '../../ftr_provider_context'; - -export default function optInTest({ getService }: FtrProviderContext) { - const supertest = getService('supertest'); - const kibanaServer = getService('kibanaServer'); - const esArchiver = getService('esArchiver'); - const esClient: Client = getService('es'); - - describe('/api/telemetry/v2/optIn API', () => { - let defaultAttributes: TelemetrySavedObjectAttributes; - let kibanaVersion: string; - before(async () => { - await esArchiver.emptyKibanaIndex(); - const kibanaVersionAccessor = kibanaServer.version; - kibanaVersion = await kibanaVersionAccessor.get(); - defaultAttributes = await getSavedObjectAttributes(esClient); - - expect(typeof kibanaVersion).to.eql('string'); - expect(kibanaVersion.length).to.be.greaterThan(0); - }); - - afterEach(async () => { - await updateSavedObjectAttributes(esClient, defaultAttributes); - }); - - it('should support sending false with allowChangingOptInStatus true', async () => { - await updateSavedObjectAttributes(esClient, { - allowChangingOptInStatus: true, - }); - await postTelemetryV2OptIn(supertest, false, 200); - const { enabled, lastVersionChecked } = await getSavedObjectAttributes(esClient); - expect(enabled).to.be(false); - expect(lastVersionChecked).to.be(kibanaVersion); - }); - - it('should support sending true with allowChangingOptInStatus true', async () => { - await updateSavedObjectAttributes(esClient, { - ...defaultAttributes, - allowChangingOptInStatus: true, - }); - await postTelemetryV2OptIn(supertest, true, 200); - const { enabled, lastVersionChecked } = await getSavedObjectAttributes(esClient); - expect(enabled).to.be(true); - expect(lastVersionChecked).to.be(kibanaVersion); - }); - - it('should not support sending false with allowChangingOptInStatus false', async () => { - await updateSavedObjectAttributes(esClient, { - ...defaultAttributes, - allowChangingOptInStatus: false, - }); - await postTelemetryV2OptIn(supertest, false, 400); - }); - - it('should not support sending true with allowChangingOptInStatus false', async () => { - await updateSavedObjectAttributes(esClient, { - ...defaultAttributes, - allowChangingOptInStatus: false, - }); - await postTelemetryV2OptIn(supertest, true, 400); - }); - - it('should not support sending null', async () => { - await postTelemetryV2OptIn(supertest, null, 400); - }); - - it('should not support sending junk', async () => { - await postTelemetryV2OptIn(supertest, 42, 400); - }); - }); -} - -async function postTelemetryV2OptIn( - supertest: SuperTest.SuperTest, - value: unknown, - statusCode: number -): Promise { - const { body } = await supertest - .post('/api/telemetry/v2/optIn') - .set('kbn-xsrf', 'xxx') - .send({ enabled: value }) - .expect(statusCode); - - return body; -} - -async function updateSavedObjectAttributes( - es: Client, - attributes: TelemetrySavedObjectAttributes -): Promise { - // Directly writing to the `.kibana` index because the SO type now is hidden, meaning it's not exposed via the SO HTTP API - await es.update({ - index: '.kibana', - id: 'telemetry:telemetry', - doc: { - telemetry: attributes, - // there are many missing fields in the SO, hopefully it doesn't break Kibana - }, - doc_as_upsert: true, - }); -} - -async function getSavedObjectAttributes(es: Client): Promise { - // Directly writing to the `.kibana` index because the SO type now is hidden, meaning it's not exposed via the SO HTTP API - const { _source: body } = await es.get<{ telemetry: TelemetrySavedObjectAttributes }>( - { - index: '.kibana', - id: 'telemetry:telemetry', - }, - { ignore: [404] } - ); - return body?.telemetry || {}; -} diff --git a/x-pack/test/api_integration/apis/telemetry/telemetry_optin_notice_seen.ts b/x-pack/test/api_integration/apis/telemetry/telemetry_optin_notice_seen.ts deleted file mode 100644 index 2a4725739e73f..0000000000000 --- a/x-pack/test/api_integration/apis/telemetry/telemetry_optin_notice_seen.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 expect from '@kbn/expect'; -import { FtrProviderContext } from '../../ftr_provider_context'; - -export default function optInTest({ getService }: FtrProviderContext) { - const client = getService('es'); - const supertest = getService('supertest'); - - describe('/api/telemetry/v2/userHasSeenNotice API Telemetry User has seen OptIn Notice', () => { - it('should update telemetry setting field via PUT', async () => { - await client.delete( - { - index: '.kibana', - id: 'telemetry:telemetry', - }, - { ignore: [404] } - ); - - await supertest.put('/api/telemetry/v2/userHasSeenNotice').set('kbn-xsrf', 'xxx').expect(200); - - const { _source } = await client.get<{ telemetry: { userHasSeenNotice: boolean } }>({ - index: '.kibana', - id: 'telemetry:telemetry', - }); - - expect(_source?.telemetry.userHasSeenNotice).to.be(true); - }); - }); -} From 98843eeef49a2446db79b3dcc592a0b2c7f91b8d Mon Sep 17 00:00:00 2001 From: Nick Peihl Date: Tue, 18 Apr 2023 16:22:49 -0400 Subject: [PATCH 13/78] [Maps] Use PageObject service methods to fix flaky test (#155161) Fixes #154913 ## Summary Changes functional test to use the maps PageObject methods for entering and exiting fullscreen. These methods provide better retry handling. --- x-pack/test/accessibility/apps/maps.ts | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/x-pack/test/accessibility/apps/maps.ts b/x-pack/test/accessibility/apps/maps.ts index 5a315260a0b2d..af74466fb8f24 100644 --- a/x-pack/test/accessibility/apps/maps.ts +++ b/x-pack/test/accessibility/apps/maps.ts @@ -14,8 +14,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const inspector = getService('inspector'); const PageObjects = getPageObjects(['common', 'settings', 'header', 'home', 'maps']); - // Failing: See https://github.com/elastic/kibana/issues/154913 - describe.skip('Maps app Accessibility', () => { + describe('Maps app Accessibility', () => { before(async () => { await PageObjects.common.navigateToUrl('home', '/tutorial_directory/sampleData', { useActualUrl: true, @@ -79,12 +78,12 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }); it('full screen button should exist', async () => { - await testSubjects.click('mapsFullScreenMode'); + await PageObjects.maps.existFullScreen(); await a11y.testAppSnapshot(); }); it('displays exit full screen logo button', async () => { - await testSubjects.click('exitFullScreenModeButton'); + await PageObjects.maps.enterFullScreen(); await a11y.testAppSnapshot(); }); From 3259647b00e24507ca1600e6b7ac7a4532ed7b9d Mon Sep 17 00:00:00 2001 From: Bree Hall <40739624+breehall@users.noreply.github.com> Date: Tue, 18 Apr 2023 16:51:39 -0400 Subject: [PATCH 14/78] Upgrade EUI to v77.1.1 (#154838) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit EUI `77.0.0` ➡️ `77.1.1` ## [`77.1.0`](https://github.com/elastic/eui/tree/v77.1.0) - Updated `EuiDatePicker` to display a warning icon and correctly set `aria-invalid` when `isInvalid` is passed ([#6677](https://github.com/elastic/eui/pull/6677)) - Updated `EuiFilePicker` to display an alert icon when `isInvalid` ([#6678](https://github.com/elastic/eui/pull/6678)) - Updated `EuiTextArea` to display an alert icon when `isInvalid` ([#6679](https://github.com/elastic/eui/pull/6679)) - Updated `EuiTextArea` to support the `isLoading` prop ([#6679](https://github.com/elastic/eui/pull/6679)) - Updated `EuiComboBox` to display a warning icon and correctly set `aria-invalid` when `isInvalid` is passed ([#6680](https://github.com/elastic/eui/pull/6680)) **Bug fixes** - Fixed `EuiAccordion` to not set an `aria-expanded` attribute on non-interactive `buttonElement`s ([#6694](https://github.com/elastic/eui/pull/6694)) - Fixed an `EuiPopoverFooter` bug causing nested popovers within popovers (note: not a recommended use-case) to unintentionally override its panel padding size inherited from context ([#6698](https://github.com/elastic/eui/pull/6698)) - Fixed `EuiComboBox` to only delete the last selected item on backspace if the input caret is present ([#6699](https://github.com/elastic/eui/pull/6699)) --------- Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: Jon --- package.json | 2 +- .../__snapshots__/index.test.tsx.snap | 24 ++++++++++++------- .../src/field_value_match/index.test.tsx | 6 +---- .../src/field_value_match_any/index.test.tsx | 6 +---- .../src/field_value_wildcard/index.test.tsx | 6 +---- .../__snapshots__/list_header.test.tsx.snap | 24 ++++++++++++------- .../__snapshots__/edit_modal.test.tsx.snap | 24 ++++++++++++------- src/dev/license_checker/config.ts | 2 +- .../eql_query_bar/eql_query_bar.test.tsx | 2 +- .../email/email_params.test.tsx | 2 +- .../server_log/server_log_params.test.tsx | 2 +- .../__snapshots__/wrapper.test.tsx.snap | 3 ++- .../field_selector.test.tsx.snap | 12 ++++++---- yarn.lock | 8 +++---- 14 files changed, 70 insertions(+), 53 deletions(-) diff --git a/package.json b/package.json index e439f886118ee..d36fc1284122b 100644 --- a/package.json +++ b/package.json @@ -96,7 +96,7 @@ "@elastic/datemath": "5.0.3", "@elastic/elasticsearch": "npm:@elastic/elasticsearch-canary@8.6.0-canary.3", "@elastic/ems-client": "8.4.0", - "@elastic/eui": "77.0.0", + "@elastic/eui": "77.1.1", "@elastic/filesaver": "1.1.2", "@elastic/node-crypto": "1.2.1", "@elastic/numeral": "^2.5.1", diff --git a/packages/kbn-securitysolution-autocomplete/src/field/__tests__/__snapshots__/index.test.tsx.snap b/packages/kbn-securitysolution-autocomplete/src/field/__tests__/__snapshots__/index.test.tsx.snap index daaf9c66c453f..23a56e38ba458 100644 --- a/packages/kbn-securitysolution-autocomplete/src/field/__tests__/__snapshots__/index.test.tsx.snap +++ b/packages/kbn-securitysolution-autocomplete/src/field/__tests__/__snapshots__/index.test.tsx.snap @@ -16,7 +16,7 @@ Object { class="euiFormControlLayout__childrenWrapper" >
@@ -33,6 +33,7 @@ Object { aria-autocomplete="list" aria-controls="" aria-expanded="false" + aria-invalid="false" data-test-subj="comboBoxSearchInput" id="generated-id__eui-combobox-id" role="combobox" @@ -89,7 +90,7 @@ Object { class="euiFormControlLayout__childrenWrapper" >
@@ -106,6 +107,7 @@ Object { aria-autocomplete="list" aria-controls="" aria-expanded="false" + aria-invalid="false" data-test-subj="comboBoxSearchInput" id="generated-id__eui-combobox-id" role="combobox" @@ -219,7 +221,7 @@ Object { class="euiFormControlLayout__childrenWrapper" >
@@ -236,6 +238,7 @@ Object { aria-autocomplete="list" aria-controls="" aria-expanded="false" + aria-invalid="false" data-test-subj="comboBoxSearchInput" disabled="" id="generated-id__eui-combobox-id" @@ -283,7 +286,7 @@ Object { class="euiFormControlLayout__childrenWrapper" >
@@ -300,6 +303,7 @@ Object { aria-autocomplete="list" aria-controls="" aria-expanded="false" + aria-invalid="false" data-test-subj="comboBoxSearchInput" disabled="" id="generated-id__eui-combobox-id" @@ -404,7 +408,7 @@ Object { class="euiFormControlLayout__childrenWrapper" >
@@ -421,6 +425,7 @@ Object { aria-autocomplete="list" aria-controls="" aria-expanded="false" + aria-invalid="false" data-test-subj="comboBoxSearchInput" id="generated-id__eui-combobox-id" role="combobox" @@ -466,7 +471,7 @@ Object { class="euiFormControlLayout__childrenWrapper" >
@@ -483,6 +488,7 @@ Object { aria-autocomplete="list" aria-controls="" aria-expanded="false" + aria-invalid="false" data-test-subj="comboBoxSearchInput" id="generated-id__eui-combobox-id" role="combobox" @@ -585,7 +591,7 @@ Object { class="euiFormControlLayout__childrenWrapper" >
@@ -602,6 +608,7 @@ Object { aria-autocomplete="list" aria-controls="" aria-expanded="false" + aria-invalid="false" data-test-subj="comboBoxSearchInput" disabled="" id="generated-id__eui-combobox-id" @@ -654,7 +661,7 @@ Object { class="euiFormControlLayout__childrenWrapper" >
@@ -671,6 +678,7 @@ Object { aria-autocomplete="list" aria-controls="" aria-expanded="false" + aria-invalid="false" data-test-subj="comboBoxSearchInput" disabled="" id="generated-id__eui-combobox-id" diff --git a/packages/kbn-securitysolution-autocomplete/src/field_value_match/index.test.tsx b/packages/kbn-securitysolution-autocomplete/src/field_value_match/index.test.tsx index 8fd88fe6a910b..24bb72c3058ac 100644 --- a/packages/kbn-securitysolution-autocomplete/src/field_value_match/index.test.tsx +++ b/packages/kbn-securitysolution-autocomplete/src/field_value_match/index.test.tsx @@ -142,11 +142,7 @@ describe('AutocompleteFieldMatchComponent', () => { /> ); - expect( - wrapper - .find('[data-test-subj="comboBoxInput"]') - .hasClass('euiComboBox__inputWrap-isClearable') - ).toBeTruthy(); + expect(wrapper.find(`[data-test-subj="comboBoxClearButton"]`)).toBeTruthy(); }); test('it correctly displays selected value', () => { diff --git a/packages/kbn-securitysolution-autocomplete/src/field_value_match_any/index.test.tsx b/packages/kbn-securitysolution-autocomplete/src/field_value_match_any/index.test.tsx index bfa4aa6de4d24..2fa8446bfa8b9 100644 --- a/packages/kbn-securitysolution-autocomplete/src/field_value_match_any/index.test.tsx +++ b/packages/kbn-securitysolution-autocomplete/src/field_value_match_any/index.test.tsx @@ -131,11 +131,7 @@ describe('AutocompleteFieldMatchAnyComponent', () => { /> ); - expect( - wrapper - .find(`[data-test-subj="comboBoxInput"]`) - .hasClass('euiComboBox__inputWrap-isClearable') - ).toBeTruthy(); + expect(wrapper.find(`[data-test-subj="comboBoxClearButton"]`)).toBeTruthy(); }); test('it correctly displays selected value', () => { diff --git a/packages/kbn-securitysolution-autocomplete/src/field_value_wildcard/index.test.tsx b/packages/kbn-securitysolution-autocomplete/src/field_value_wildcard/index.test.tsx index bad9f619b4994..f0b5931e54436 100644 --- a/packages/kbn-securitysolution-autocomplete/src/field_value_wildcard/index.test.tsx +++ b/packages/kbn-securitysolution-autocomplete/src/field_value_wildcard/index.test.tsx @@ -143,11 +143,7 @@ describe('AutocompleteFieldWildcardComponent', () => { /> ); - expect( - wrapper - .find('[data-test-subj="comboBoxInput"]') - .hasClass('euiComboBox__inputWrap-isClearable') - ).toBeTruthy(); + expect(wrapper.find(`[data-test-subj="comboBoxClearButton"]`)).toBeTruthy(); }); test('it correctly displays selected value', () => { diff --git a/packages/kbn-securitysolution-exception-list-components/src/list_header/__snapshots__/list_header.test.tsx.snap b/packages/kbn-securitysolution-exception-list-components/src/list_header/__snapshots__/list_header.test.tsx.snap index 976401785f88a..871e3ea311cd6 100644 --- a/packages/kbn-securitysolution-exception-list-components/src/list_header/__snapshots__/list_header.test.tsx.snap +++ b/packages/kbn-securitysolution-exception-list-components/src/list_header/__snapshots__/list_header.test.tsx.snap @@ -331,15 +331,23 @@ Object {
- +
+ +
+
diff --git a/packages/kbn-securitysolution-exception-list-components/src/list_header/edit_modal/__snapshots__/edit_modal.test.tsx.snap b/packages/kbn-securitysolution-exception-list-components/src/list_header/edit_modal/__snapshots__/edit_modal.test.tsx.snap index ed991c34d8870..4ab6fed810c0a 100644 --- a/packages/kbn-securitysolution-exception-list-components/src/list_header/edit_modal/__snapshots__/edit_modal.test.tsx.snap +++ b/packages/kbn-securitysolution-exception-list-components/src/list_header/edit_modal/__snapshots__/edit_modal.test.tsx.snap @@ -107,15 +107,23 @@ Object {
- +
+ +
+
diff --git a/src/dev/license_checker/config.ts b/src/dev/license_checker/config.ts index 28c22aad734d5..75a12b0884f8a 100644 --- a/src/dev/license_checker/config.ts +++ b/src/dev/license_checker/config.ts @@ -85,6 +85,6 @@ export const LICENSE_OVERRIDES = { 'jsts@1.6.2': ['Eclipse Distribution License - v 1.0'], // cf. https://github.com/bjornharrtell/jsts '@mapbox/jsonlint-lines-primitives@2.0.2': ['MIT'], // license in readme https://github.com/tmcw/jsonlint '@elastic/ems-client@8.4.0': ['Elastic License 2.0'], - '@elastic/eui@77.0.0': ['SSPL-1.0 OR Elastic License 2.0'], + '@elastic/eui@77.1.1': ['SSPL-1.0 OR Elastic License 2.0'], 'language-subtag-registry@0.3.21': ['CC-BY-4.0'], // retired ODC‑By license https://github.com/mattcg/language-subtag-registry }; diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/eql_query_bar/eql_query_bar.test.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/eql_query_bar/eql_query_bar.test.tsx index 51e1e0683d766..e07eacc363c92 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/eql_query_bar/eql_query_bar.test.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/eql_query_bar/eql_query_bar.test.tsx @@ -67,7 +67,7 @@ describe('EqlQueryBar', () => { wrapper .find('[data-test-subj="eqlQueryBarTextInput"]') - .first() + .last() .simulate('change', { target: { value: 'newQuery' } }); const expected = { diff --git a/x-pack/plugins/stack_connectors/public/connector_types/email/email_params.test.tsx b/x-pack/plugins/stack_connectors/public/connector_types/email/email_params.test.tsx index 7626606a0bf60..a0a41a57a8331 100644 --- a/x-pack/plugins/stack_connectors/public/connector_types/email/email_params.test.tsx +++ b/x-pack/plugins/stack_connectors/public/connector_types/email/email_params.test.tsx @@ -111,7 +111,7 @@ describe('EmailParamsFields renders', () => { const valueToSimulate = 'some new value'; wrapper .find('[data-test-subj="messageTextArea"]') - .first() + .last() .simulate('change', { target: { value: valueToSimulate } }); expect(editAction).toHaveBeenCalledWith('message', valueToSimulate, 0); wrapper.setProps({ diff --git a/x-pack/plugins/stack_connectors/public/connector_types/server_log/server_log_params.test.tsx b/x-pack/plugins/stack_connectors/public/connector_types/server_log/server_log_params.test.tsx index 6a5426d952897..e54ec5e9e5cb2 100644 --- a/x-pack/plugins/stack_connectors/public/connector_types/server_log/server_log_params.test.tsx +++ b/x-pack/plugins/stack_connectors/public/connector_types/server_log/server_log_params.test.tsx @@ -119,7 +119,7 @@ describe('ServerLogParamsFields renders', () => { const valueToSimulate = 'some new value'; wrapper .find('[data-test-subj="messageTextArea"]') - .first() + .last() .simulate('change', { target: { value: valueToSimulate } }); expect(editAction).toHaveBeenCalledWith('message', valueToSimulate, 0); wrapper.setProps({ diff --git a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/barchart/__snapshots__/wrapper.test.tsx.snap b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/barchart/__snapshots__/wrapper.test.tsx.snap index 91ca6c96a9f33..a5e92c9e90097 100644 --- a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/barchart/__snapshots__/wrapper.test.tsx.snap +++ b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/barchart/__snapshots__/wrapper.test.tsx.snap @@ -37,7 +37,7 @@ exports[` when not loading or refetching should ren class="euiFormControlLayout__childrenWrapper" >
@@ -54,6 +54,7 @@ exports[` when not loading or refetching should ren aria-autocomplete="list" aria-controls="" aria-expanded="false" + aria-invalid="false" data-test-subj="comboBoxSearchInput" id="generated-id__eui-combobox-id" role="combobox" diff --git a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/barchart/field_selector/__snapshots__/field_selector.test.tsx.snap b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/barchart/field_selector/__snapshots__/field_selector.test.tsx.snap index 9346a739b9dae..161894eaa1182 100644 --- a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/barchart/field_selector/__snapshots__/field_selector.test.tsx.snap +++ b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/barchart/field_selector/__snapshots__/field_selector.test.tsx.snap @@ -22,7 +22,7 @@ Object { class="euiFormControlLayout__childrenWrapper" >
@@ -39,6 +39,7 @@ Object { aria-autocomplete="list" aria-controls="" aria-expanded="false" + aria-invalid="false" data-test-subj="comboBoxSearchInput" id="generated-id__eui-combobox-id" role="combobox" @@ -90,7 +91,7 @@ Object { class="euiFormControlLayout__childrenWrapper" >
@@ -107,6 +108,7 @@ Object { aria-autocomplete="list" aria-controls="" aria-expanded="false" + aria-invalid="false" data-test-subj="comboBoxSearchInput" id="generated-id__eui-combobox-id" role="combobox" @@ -215,7 +217,7 @@ Object { class="euiFormControlLayout__childrenWrapper" >
@@ -232,6 +234,7 @@ Object { aria-autocomplete="list" aria-controls="" aria-expanded="false" + aria-invalid="false" data-test-subj="comboBoxSearchInput" id="generated-id__eui-combobox-id" role="combobox" @@ -283,7 +286,7 @@ Object { class="euiFormControlLayout__childrenWrapper" >
@@ -300,6 +303,7 @@ Object { aria-autocomplete="list" aria-controls="" aria-expanded="false" + aria-invalid="false" data-test-subj="comboBoxSearchInput" id="generated-id__eui-combobox-id" role="combobox" diff --git a/yarn.lock b/yarn.lock index 2776f05861c0c..bd8668fa91fc1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1543,10 +1543,10 @@ resolved "https://registry.yarnpkg.com/@elastic/eslint-plugin-eui/-/eslint-plugin-eui-0.0.2.tgz#56b9ef03984a05cc213772ae3713ea8ef47b0314" integrity sha512-IoxURM5zraoQ7C8f+mJb9HYSENiZGgRVcG4tLQxE61yHNNRDXtGDWTZh8N1KIHcsqN1CEPETjuzBXkJYF/fDiQ== -"@elastic/eui@77.0.0": - version "77.0.0" - resolved "https://registry.yarnpkg.com/@elastic/eui/-/eui-77.0.0.tgz#931bb7f8a109b571dedefe481ccf41a9229a6286" - integrity sha512-4RV6GxSFFWGEZtUkLpZWQrwTdEVMq/hmicSZiCpvJUnilLmhfnNEGqCn5Qu8g/OOb5AL3XayfytWMiiaJ9Yw+g== +"@elastic/eui@77.1.1": + version "77.1.1" + resolved "https://registry.yarnpkg.com/@elastic/eui/-/eui-77.1.1.tgz#82f4294bf3239d5d825c1d939c49d125bfdbeb72" + integrity sha512-guJmHoGDbvKh/738taKDZGSdNk+OXMse513oPaPf4NoXpQUeYvl3gLT50mX5J4nwILS1LFKNGrbU2Es77HM1cQ== dependencies: "@types/chroma-js" "^2.0.0" "@types/lodash" "^4.14.160" From f4af1e0b744f37caf2f4175b66c561cd72ec6d67 Mon Sep 17 00:00:00 2001 From: Cee Chen <549407+cee-chen@users.noreply.github.com> Date: Tue, 18 Apr 2023 14:07:18 -0700 Subject: [PATCH 15/78] Update references to EUI team name (#155196) ## Summary The EUI team recently changed its GitHub team name to @elastic/eui-team. We're updating all references in Kibana's CODEOWNERS as a result. ### Checklist N/A, CODEOWNERS change only --- .github/CODEOWNERS | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 80515067f7294..270c2ce5d2b48 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1221,8 +1221,8 @@ x-pack/test/threat_intelligence_cypress @elastic/protections-experience # Logstash #CC# /x-pack/plugins/logstash/ @elastic/logstash -# EUI design -/src/plugins/kibana_react/public/page_template/ @elastic/eui-design @elastic/appex-sharedux +# EUI team +/src/plugins/kibana_react/public/page_template/ @elastic/eui-team @elastic/appex-sharedux # Landing page for guided onboarding in Home plugin /src/plugins/home/public/application/components/guided_onboarding @elastic/platform-onboarding From d694a0d75fde6f216e9e38045835aed254af8eee Mon Sep 17 00:00:00 2001 From: Saarika Bhasi <55930906+saarikabhasi@users.noreply.github.com> Date: Tue, 18 Apr 2023 17:22:48 -0400 Subject: [PATCH 16/78] [ML Inference] Filter ELSER pipelines from existing pipelines list (#155085) ## Summary Remove ELSER pipeline from existing pipelines list in configuration screen. ### Screen Recording https://user-images.githubusercontent.com/55930906/232590535-5628d04b-a352-4888-a1c5-00226356ed3c.mov ### Checklist Delete any items that are not applicable to this PR. - [ ] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios --- .../ml_inference/ml_inference_logic.test.ts | 71 +++++++++++++++++++ .../ml_inference/ml_inference_logic.ts | 10 ++- 2 files changed, 80 insertions(+), 1 deletion(-) diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/ml_inference_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/ml_inference_logic.test.ts index 1c38c99bd5c93..a4562d66349cc 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/ml_inference_logic.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/ml_inference_logic.test.ts @@ -314,6 +314,77 @@ describe('MlInferenceLogic', () => { }, ]); }); + it('filter text expansion model from existing pipelines list', () => { + MLModelsApiLogic.actions.apiSuccess([ + { + inference_config: { + text_expansion: {}, + }, + input: { + field_names: ['text_field'], + }, + model_id: 'text-expansion-mocked-model', + model_type: 'pytorch', + tags: [], + version: '1', + }, + { + inference_config: { + classification: {}, + }, + input: { + field_names: ['text_field'], + }, + model_id: 'classification-mocked-model', + model_type: 'lang_ident', + tags: [], + version: '1', + }, + ]); + + FetchMlInferencePipelinesApiLogic.actions.apiSuccess({ + 'unit-test-1': { + processors: [ + { + inference: { + field_map: { + body: 'text_field', + }, + model_id: 'text-expansion-mocked-model', + target_field: 'ml.inference.test-field', + }, + }, + ], + version: 1, + }, + 'unit-test-2': { + processors: [ + { + inference: { + field_map: { + body: 'text_field', + }, + model_id: 'classification-mocked-model', + target_field: 'ml.inference.test-field', + }, + }, + ], + version: 1, + }, + }); + + expect(MLInferenceLogic.values.existingInferencePipelines).toEqual([ + { + destinationField: 'test-field', + disabled: false, + disabledReason: undefined, + pipelineName: 'unit-test-2', + modelType: 'lang_ident', + modelId: 'classification-mocked-model', + sourceField: 'body', + }, + ]); + }); }); describe('mlInferencePipeline', () => { it('returns undefined when configuration is invalid', () => { diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/ml_inference_logic.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/ml_inference_logic.ts index 98bcaa6671495..516d65df089b5 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/ml_inference_logic.ts +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/ml_inference_logic.ts @@ -9,6 +9,8 @@ import { kea, MakeLogicType } from 'kea'; import { IndicesGetMappingIndexMappingRecord } from '@elastic/elasticsearch/lib/api/types'; +import { SUPPORTED_PYTORCH_TASKS } from '@kbn/ml-trained-models-utils'; + import { FieldMapping, formatPipelineName, @@ -84,6 +86,10 @@ export const EMPTY_PIPELINE_CONFIGURATION: InferencePipelineConfiguration = { sourceField: '', }; +const isNotTextExpansionModel = (model: MLInferencePipelineOption): boolean => { + return model.modelType !== SUPPORTED_PYTORCH_TASKS.TEXT_EXPANSION; +}; + const API_REQUEST_COMPLETE_STATUSES = [Status.SUCCESS, Status.ERROR]; const DEFAULT_CONNECTOR_FIELDS = ['body', 'title', 'id', 'type', 'url']; @@ -537,7 +543,9 @@ export const MLInferenceLogic = kea< sourceField, }; }) - .filter((p): p is MLInferencePipelineOption => p !== undefined); + .filter( + (p): p is MLInferencePipelineOption => p !== undefined && isNotTextExpansionModel(p) + ); return existingPipelines; }, From 88f4f8082aafd5a81ac60b527e19ba6ac1ed9df4 Mon Sep 17 00:00:00 2001 From: Coen Warmer Date: Wed, 19 Apr 2023 00:00:15 +0200 Subject: [PATCH 17/78] [Synthetics][Ux][Uptime] Use Observability Page Template from Observability Shared (#154774) Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: Shahzad --- .../exploratory_view_example/public/app.tsx | 2 +- x-pack/plugins/exploratory_view/kibana.jsonc | 3 +- .../embeddable/embeddable.tsx | 2 +- .../exploratory_view/embeddable/index.tsx | 2 +- .../embeddable/use_app_data_view.ts | 2 +- .../embeddable/use_local_data_view.ts | 2 +- .../header/add_to_case_action.tsx | 5 +- .../exploratory_view/hooks/use_add_to_case.ts | 2 +- .../hooks/use_lens_formula_helper.ts | 2 +- .../shared/exploratory_view/index.tsx | 6 +- .../columns/chart_type_select.tsx | 2 +- .../series_editor/columns/chart_types.tsx | 2 +- .../public/components/shared/index.tsx | 2 +- .../public/hooks/use_kibana_space.tsx | 2 +- .../plugins/exploratory_view/public/plugin.ts | 9 +- x-pack/plugins/exploratory_view/tsconfig.json | 2 +- x-pack/plugins/observability/kibana.jsonc | 28 ++---- .../public/application/index.tsx | 2 +- x-pack/plugins/observability/public/plugin.ts | 42 +++------ x-pack/plugins/observability/tsconfig.json | 4 +- .../public/hooks/use_track_metric.tsx | 91 +++++++++++++++++++ .../observability_shared/public/index.ts | 9 +- .../observability_shared/tsconfig.json | 2 + .../profiling_app_page_template/index.tsx | 4 +- .../public/routing/router_error_boundary.tsx | 4 +- x-pack/plugins/profiling/public/types.ts | 6 +- x-pack/plugins/synthetics/kibana.jsonc | 16 +--- .../common/components/stderr_logs.tsx | 4 +- .../synthetics_page_template.tsx | 6 +- .../public/apps/synthetics/routes.tsx | 2 +- .../public/apps/synthetics/synthetics_app.tsx | 1 + .../synthetics/utils/testing/rtl_helpers.tsx | 9 ++ .../public/legacy_uptime/app/uptime_app.tsx | 1 + .../app/uptime_page_template.tsx | 4 +- .../legacy_uptime/lib/helper/rtl_helpers.tsx | 8 ++ .../public/legacy_uptime/routes.tsx | 2 +- x-pack/plugins/synthetics/public/plugin.ts | 8 +- x-pack/plugins/synthetics/tsconfig.json | 1 + x-pack/plugins/ux/kibana.jsonc | 1 + .../plugins/ux/public/application/ux_app.tsx | 2 + .../components/app/rum_dashboard/rum_home.tsx | 4 +- x-pack/plugins/ux/public/plugin.ts | 8 +- x-pack/plugins/ux/tsconfig.json | 1 + 43 files changed, 209 insertions(+), 108 deletions(-) create mode 100644 x-pack/plugins/observability_shared/public/hooks/use_track_metric.tsx diff --git a/x-pack/examples/exploratory_view_example/public/app.tsx b/x-pack/examples/exploratory_view_example/public/app.tsx index 96e44f62d2ab5..268f070b40d16 100644 --- a/x-pack/examples/exploratory_view_example/public/app.tsx +++ b/x-pack/examples/exploratory_view_example/public/app.tsx @@ -28,7 +28,7 @@ export const App = (props: { plugins: StartDependencies; defaultIndexPattern: DataView | null; }) => { - const ExploratoryViewComponent = props.plugins.observability.ExploratoryViewEmbeddable; + const ExploratoryViewComponent = props.plugins.exploratoryView.ExploratoryViewEmbeddable; const seriesList: AllSeries = [ { diff --git a/x-pack/plugins/exploratory_view/kibana.jsonc b/x-pack/plugins/exploratory_view/kibana.jsonc index 7ef4c044dbc4c..c381b9492a961 100644 --- a/x-pack/plugins/exploratory_view/kibana.jsonc +++ b/x-pack/plugins/exploratory_view/kibana.jsonc @@ -18,7 +18,7 @@ "guidedOnboarding", "inspector", "lens", - "observability", + "observabilityShared", "security", "share", "triggersActionsUi", @@ -32,7 +32,6 @@ "kibanaReact", "kibanaUtils", "lens", - "observability", "unifiedSearch", "visualizations" ], diff --git a/x-pack/plugins/exploratory_view/public/components/shared/exploratory_view/embeddable/embeddable.tsx b/x-pack/plugins/exploratory_view/public/components/shared/exploratory_view/embeddable/embeddable.tsx index 864e95affed61..4642e7fbec524 100644 --- a/x-pack/plugins/exploratory_view/public/components/shared/exploratory_view/embeddable/embeddable.tsx +++ b/x-pack/plugins/exploratory_view/public/components/shared/exploratory_view/embeddable/embeddable.tsx @@ -15,7 +15,7 @@ import { XYState, } from '@kbn/lens-plugin/public'; import { ViewMode } from '@kbn/embeddable-plugin/common'; -import { observabilityFeatureId } from '@kbn/observability-plugin/public'; +import { observabilityFeatureId } from '@kbn/observability-shared-plugin/public'; import styled from 'styled-components'; import { useKibanaSpace } from '../../../../hooks/use_kibana_space'; import { HeatMapLensAttributes } from '../configurations/lens_attributes/heatmap_attributes'; diff --git a/x-pack/plugins/exploratory_view/public/components/shared/exploratory_view/embeddable/index.tsx b/x-pack/plugins/exploratory_view/public/components/shared/exploratory_view/embeddable/index.tsx index 2311c016fc8b4..98e9d61e512a6 100644 --- a/x-pack/plugins/exploratory_view/public/components/shared/exploratory_view/embeddable/index.tsx +++ b/x-pack/plugins/exploratory_view/public/components/shared/exploratory_view/embeddable/index.tsx @@ -11,11 +11,11 @@ import { EuiThemeProvider } from '@kbn/kibana-react-plugin/common'; import type { CoreStart } from '@kbn/core/public'; import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public'; import { EuiErrorBoundary } from '@elastic/eui'; -import { useFetcher } from '@kbn/observability-plugin/public'; import styled from 'styled-components'; import { DataView } from '@kbn/data-views-plugin/common'; import { FormulaPublicApi } from '@kbn/lens-plugin/public'; import { i18n } from '@kbn/i18n'; +import { useFetcher } from '../../../../hooks/use_fetcher'; import { useAppDataView } from './use_app_data_view'; import type { ExploratoryViewPublicPluginsStart } from '../../../..'; import type { ExploratoryEmbeddableProps, ExploratoryEmbeddableComponentProps } from './embeddable'; diff --git a/x-pack/plugins/exploratory_view/public/components/shared/exploratory_view/embeddable/use_app_data_view.ts b/x-pack/plugins/exploratory_view/public/components/shared/exploratory_view/embeddable/use_app_data_view.ts index ca2b44f74c30a..ab1ca8827c58a 100644 --- a/x-pack/plugins/exploratory_view/public/components/shared/exploratory_view/embeddable/use_app_data_view.ts +++ b/x-pack/plugins/exploratory_view/public/components/shared/exploratory_view/embeddable/use_app_data_view.ts @@ -7,7 +7,7 @@ import { useState } from 'react'; import { DataView } from '@kbn/data-views-plugin/common'; -import { useFetcher } from '@kbn/observability-plugin/public'; +import { useFetcher } from '../../../../hooks/use_fetcher'; import { useLocalDataView } from './use_local_data_view'; import { ExploratoryEmbeddableProps, ExploratoryViewPublicPluginsStart } from '../../../..'; import type { DataViewState } from '../hooks/use_app_data_view'; diff --git a/x-pack/plugins/exploratory_view/public/components/shared/exploratory_view/embeddable/use_local_data_view.ts b/x-pack/plugins/exploratory_view/public/components/shared/exploratory_view/embeddable/use_local_data_view.ts index a02620e43feda..de92ec1420236 100644 --- a/x-pack/plugins/exploratory_view/public/components/shared/exploratory_view/embeddable/use_local_data_view.ts +++ b/x-pack/plugins/exploratory_view/public/components/shared/exploratory_view/embeddable/use_local_data_view.ts @@ -7,7 +7,7 @@ import { useEffect } from 'react'; import useLocalStorage from 'react-use/lib/useLocalStorage'; -import { useFetcher } from '@kbn/observability-plugin/public'; +import { useFetcher } from '../../../../hooks/use_fetcher'; import { getDataTypeIndices } from '../../../../utils/observability_data_views'; import { AppDataType } from '../types'; import { ExploratoryEmbeddableProps } from '../../../..'; diff --git a/x-pack/plugins/exploratory_view/public/components/shared/exploratory_view/header/add_to_case_action.tsx b/x-pack/plugins/exploratory_view/public/components/shared/exploratory_view/header/add_to_case_action.tsx index 2b0f2a4ffc190..c374beee658c3 100644 --- a/x-pack/plugins/exploratory_view/public/components/shared/exploratory_view/header/add_to_case_action.tsx +++ b/x-pack/plugins/exploratory_view/public/components/shared/exploratory_view/header/add_to_case_action.tsx @@ -15,7 +15,10 @@ import { GetAllCasesSelectorModalProps, } from '@kbn/cases-plugin/public'; import { TypedLensByValueInput } from '@kbn/lens-plugin/public'; -import { observabilityFeatureId, observabilityAppId } from '@kbn/observability-plugin/public'; +import { + observabilityFeatureId, + observabilityAppId, +} from '@kbn/observability-shared-plugin/public'; import { useGetUserCasesPermissions } from '../../../../hooks/use_get_user_cases_permissions'; import { ObservabilityAppServices } from '../../../../application/types'; import { useAddToCase } from '../hooks/use_add_to_case'; diff --git a/x-pack/plugins/exploratory_view/public/components/shared/exploratory_view/hooks/use_add_to_case.ts b/x-pack/plugins/exploratory_view/public/components/shared/exploratory_view/hooks/use_add_to_case.ts index 0d2b9dd724077..67f79242a9376 100644 --- a/x-pack/plugins/exploratory_view/public/components/shared/exploratory_view/hooks/use_add_to_case.ts +++ b/x-pack/plugins/exploratory_view/public/components/shared/exploratory_view/hooks/use_add_to_case.ts @@ -11,7 +11,7 @@ import { HttpSetup, MountPoint } from '@kbn/core/public'; import { Case } from '@kbn/cases-plugin/common'; import { TypedLensByValueInput } from '@kbn/lens-plugin/public'; import { CasesDeepLinkId, DRAFT_COMMENT_STORAGE_ID } from '@kbn/cases-plugin/public'; -import { observabilityFeatureId } from '@kbn/observability-plugin/public'; +import { observabilityFeatureId } from '@kbn/observability-shared-plugin/public'; import { useKibana } from '../../../../utils/kibana_react'; import { AddToCaseProps } from '../header/add_to_case_action'; diff --git a/x-pack/plugins/exploratory_view/public/components/shared/exploratory_view/hooks/use_lens_formula_helper.ts b/x-pack/plugins/exploratory_view/public/components/shared/exploratory_view/hooks/use_lens_formula_helper.ts index 9a2b7c115051c..cad011de0e3c1 100644 --- a/x-pack/plugins/exploratory_view/public/components/shared/exploratory_view/hooks/use_lens_formula_helper.ts +++ b/x-pack/plugins/exploratory_view/public/components/shared/exploratory_view/hooks/use_lens_formula_helper.ts @@ -7,7 +7,7 @@ import { useMemo } from 'react'; import { useKibana } from '@kbn/kibana-react-plugin/public'; -import { useFetcher } from '@kbn/observability-plugin/public'; +import { useFetcher } from '../../../../hooks/use_fetcher'; import { ExploratoryViewPublicPluginsStart } from '../../../..'; export const useLensFormulaHelper = () => { diff --git a/x-pack/plugins/exploratory_view/public/components/shared/exploratory_view/index.tsx b/x-pack/plugins/exploratory_view/public/components/shared/exploratory_view/index.tsx index 9e6ac7d0394e3..fb333939722bb 100644 --- a/x-pack/plugins/exploratory_view/public/components/shared/exploratory_view/index.tsx +++ b/x-pack/plugins/exploratory_view/public/components/shared/exploratory_view/index.tsx @@ -15,7 +15,7 @@ import { createSessionStorageStateStorage, } from '@kbn/kibana-utils-plugin/public'; import { TypedLensByValueInput } from '@kbn/lens-plugin/public'; -import { useTrackPageview } from '@kbn/observability-plugin/public'; +import { useTrackPageview } from '../../../hooks/use_track_metric'; import { ExploratoryView } from './exploratory_view'; import { ExploratoryViewPublicPluginsStart } from '../../../plugin'; import { useBreadcrumbs } from '../../../hooks/use_breadcrumbs'; @@ -39,12 +39,12 @@ export function ExploratoryViewPage({ useSessionStorage = false, }: ExploratoryViewPageProps) { const { - services: { uiSettings, notifications, observability }, + services: { uiSettings, notifications, observabilityShared }, } = useKibana(); const history = useHistory(); - const ObservabilityPageTemplate = observability.navigation.PageTemplate; + const ObservabilityPageTemplate = observabilityShared.navigation.PageTemplate; useTrackPageview({ app: 'observability-overview', path: 'exploratory-view' }); useTrackPageview({ diff --git a/x-pack/plugins/exploratory_view/public/components/shared/exploratory_view/series_editor/columns/chart_type_select.tsx b/x-pack/plugins/exploratory_view/public/components/shared/exploratory_view/series_editor/columns/chart_type_select.tsx index 1fd75d47846ab..2ec37b2a60609 100644 --- a/x-pack/plugins/exploratory_view/public/components/shared/exploratory_view/series_editor/columns/chart_type_select.tsx +++ b/x-pack/plugins/exploratory_view/public/components/shared/exploratory_view/series_editor/columns/chart_type_select.tsx @@ -9,7 +9,7 @@ import React, { useState } from 'react'; import { EuiPopover, EuiToolTip, EuiButtonEmpty, EuiIcon } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { useKibana } from '@kbn/kibana-react-plugin/public'; -import { useFetcher } from '@kbn/observability-plugin/public'; +import { useFetcher } from '../../../../../hooks/use_fetcher'; import { ExploratoryViewPublicPluginsStart } from '../../../../../plugin'; import { SeriesUrl } from '../../../../..'; import { SeriesConfig } from '../../types'; diff --git a/x-pack/plugins/exploratory_view/public/components/shared/exploratory_view/series_editor/columns/chart_types.tsx b/x-pack/plugins/exploratory_view/public/components/shared/exploratory_view/series_editor/columns/chart_types.tsx index 68ccd782e4536..1eb5de5552aee 100644 --- a/x-pack/plugins/exploratory_view/public/components/shared/exploratory_view/series_editor/columns/chart_types.tsx +++ b/x-pack/plugins/exploratory_view/public/components/shared/exploratory_view/series_editor/columns/chart_types.tsx @@ -10,7 +10,7 @@ import { EuiFlexGroup, EuiFlexItem, EuiFormRow, EuiIcon, EuiSuperSelect } from ' import { i18n } from '@kbn/i18n'; import { useKibana } from '@kbn/kibana-react-plugin/public'; import { SeriesType } from '@kbn/lens-plugin/public'; -import { useFetcher } from '@kbn/observability-plugin/public'; +import { useFetcher } from '../../../../../hooks/use_fetcher'; import { ExploratoryViewPublicPluginsStart } from '../../../../../plugin'; import { SeriesUrl } from '../../../../..'; import { useSeriesStorage } from '../../hooks/use_series_storage'; diff --git a/x-pack/plugins/exploratory_view/public/components/shared/index.tsx b/x-pack/plugins/exploratory_view/public/components/shared/index.tsx index 0ca2b301c4ab4..1b685134bfb71 100644 --- a/x-pack/plugins/exploratory_view/public/components/shared/index.tsx +++ b/x-pack/plugins/exploratory_view/public/components/shared/index.tsx @@ -17,7 +17,7 @@ import type { DatePickerProps } from './date_picker'; import type { FilterValueLabelProps } from './filter_value_label/filter_value_label'; import type { SelectableUrlListProps } from './exploratory_view/components/url_search/selectable_url_list'; import type { ExploratoryViewPageProps } from './exploratory_view'; -export type { LazyObservabilityPageTemplateProps } from '@kbn/observability-plugin/public'; +export type { LazyObservabilityPageTemplateProps } from '@kbn/observability-shared-plugin/public'; const CoreVitalsLazy = lazy(() => import('./core_web_vitals')); diff --git a/x-pack/plugins/exploratory_view/public/hooks/use_kibana_space.tsx b/x-pack/plugins/exploratory_view/public/hooks/use_kibana_space.tsx index d09cea114c144..2a1459611cc0a 100644 --- a/x-pack/plugins/exploratory_view/public/hooks/use_kibana_space.tsx +++ b/x-pack/plugins/exploratory_view/public/hooks/use_kibana_space.tsx @@ -6,7 +6,7 @@ */ import type { Space } from '@kbn/spaces-plugin/common'; import { useKibana } from '@kbn/kibana-react-plugin/public'; -import { useFetcher } from '@kbn/observability-plugin/public'; +import { useFetcher } from './use_fetcher'; import { ExploratoryViewPublicPluginsStart } from '..'; export const useKibanaSpace = () => { diff --git a/x-pack/plugins/exploratory_view/public/plugin.ts b/x-pack/plugins/exploratory_view/public/plugin.ts index 3f4c949f1cc71..99b811c69d8c9 100644 --- a/x-pack/plugins/exploratory_view/public/plugin.ts +++ b/x-pack/plugins/exploratory_view/public/plugin.ts @@ -18,6 +18,7 @@ import { Plugin as PluginClass, PluginInitializerContext, } from '@kbn/core/public'; +import type { ObservabilitySharedPluginStart } from '@kbn/observability-shared-plugin/public'; import type { DataPublicPluginSetup, DataPublicPluginStart } from '@kbn/data-plugin/public'; import type { DataViewsPublicPluginStart } from '@kbn/data-views-plugin/public'; import type { DiscoverStart } from '@kbn/discover-plugin/public'; @@ -36,10 +37,6 @@ import { GuidedOnboardingPluginStart } from '@kbn/guided-onboarding-plugin/publi import { SpacesPluginStart } from '@kbn/spaces-plugin/public'; import { LicensingPluginStart } from '@kbn/licensing-plugin/public'; import { UnifiedSearchPublicPluginStart } from '@kbn/unified-search-plugin/public'; -import { - ObservabilityPublicSetup, - ObservabilityPublicStart, -} from '@kbn/observability-plugin/public'; import { getExploratoryViewEmbeddable } from './components/shared/exploratory_view/embeddable'; import { createExploratoryViewUrl } from './components/shared/exploratory_view/configurations/exploratory_view_url'; import getAppDataView from './utils/observability_data_views/get_app_data_view'; @@ -48,7 +45,6 @@ import { APP_ROUTE } from './constants'; export interface ExploratoryViewPublicPluginsSetup { data: DataPublicPluginSetup; - observability: ObservabilityPublicSetup; share: SharePluginSetup; triggersActionsUi: TriggersAndActionsUIPublicPluginSetup; usageCollection: UsageCollectionSetup; @@ -62,10 +58,11 @@ export interface ExploratoryViewPublicPluginsStart { dataViews: DataViewsPublicPluginStart; discover: DiscoverStart; embeddable: EmbeddableStart; + guidedOnboarding: GuidedOnboardingPluginStart; lens: LensPublicStart; licensing: LicensingPluginStart; - observability: ObservabilityPublicStart; + observabilityShared: ObservabilitySharedPluginStart; security: SecurityPluginStart; share: SharePluginStart; spaces?: SpacesPluginStart; diff --git a/x-pack/plugins/exploratory_view/tsconfig.json b/x-pack/plugins/exploratory_view/tsconfig.json index 6b740c0ea3e4c..5b8fd3df1a227 100644 --- a/x-pack/plugins/exploratory_view/tsconfig.json +++ b/x-pack/plugins/exploratory_view/tsconfig.json @@ -55,7 +55,7 @@ "@kbn/charts-plugin", "@kbn/shared-ux-router", "@kbn/core-application-browser", - "@kbn/observability-plugin" + "@kbn/observability-shared-plugin" ], "exclude": ["target/**/*"] } diff --git a/x-pack/plugins/observability/kibana.jsonc b/x-pack/plugins/observability/kibana.jsonc index de3927c6cb729..070638c42f1ce 100644 --- a/x-pack/plugins/observability/kibana.jsonc +++ b/x-pack/plugins/observability/kibana.jsonc @@ -13,36 +13,22 @@ "charts", "data", "dataViews", + "embeddable", "features", "files", + "guidedOnboarding", "inspector", + "lens", + "observabilityShared", "ruleRegistry", "triggersActionsUi", - "inspector", - "unifiedSearch", "security", - "guidedOnboarding", - "share" - ], - "optionalPlugins": [ - "discover", - "embeddable", - "home", - "lens", - "licensing", - "spaces", - "usageCollection" - ], - "requiredBundles": [ + "share", "unifiedSearch", - "data", - "dataViews", - "embeddable", - "kibanaReact", - "kibanaUtils", - "lens", "visualizations" ], + "optionalPlugins": ["discover", "home", "licensing", "spaces", "usageCollection"], + "requiredBundles": ["data", "kibanaReact", "kibanaUtils", "unifiedSearch"], "extraPublicDirs": ["common"] } } diff --git a/x-pack/plugins/observability/public/application/index.tsx b/x-pack/plugins/observability/public/application/index.tsx index 20ca530d1a3c8..a82a5a1dce2dc 100644 --- a/x-pack/plugins/observability/public/application/index.tsx +++ b/x-pack/plugins/observability/public/application/index.tsx @@ -14,6 +14,7 @@ import { i18n } from '@kbn/i18n'; import { Route } from '@kbn/shared-ux-router'; import { AppMountParameters, APP_WRAPPER_CLASS, CoreStart } from '@kbn/core/public'; import { EuiThemeProvider } from '@kbn/kibana-react-plugin/common'; +import type { LazyObservabilityPageTemplateProps } from '@kbn/observability-shared-plugin/public'; import { KibanaContextProvider, KibanaThemeProvider, @@ -21,7 +22,6 @@ import { } from '@kbn/kibana-react-plugin/public'; import { Storage } from '@kbn/kibana-utils-plugin/public'; import { UsageCollectionSetup } from '@kbn/usage-collection-plugin/public'; -import type { LazyObservabilityPageTemplateProps } from '../components/shared/page_template/lazy_page_template'; import { HasDataContextProvider } from '../context/has_data_context'; import { PluginContext } from '../context/plugin_context'; import { ConfigSchema, ObservabilityPublicPluginsStart } from '../plugin'; diff --git a/x-pack/plugins/observability/public/plugin.ts b/x-pack/plugins/observability/public/plugin.ts index d497e02d533e1..6c2874f39b6d8 100644 --- a/x-pack/plugins/observability/public/plugin.ts +++ b/x-pack/plugins/observability/public/plugin.ts @@ -24,8 +24,14 @@ import type { DataPublicPluginSetup, DataPublicPluginStart } from '@kbn/data-plu import type { DataViewsPublicPluginStart } from '@kbn/data-views-plugin/public'; import type { DiscoverStart } from '@kbn/discover-plugin/public'; import type { EmbeddableStart } from '@kbn/embeddable-plugin/public'; +import type { ExploratoryViewPublicPluginsStart } from '@kbn/exploratory-view-plugin/public'; import type { HomePublicPluginSetup, HomePublicPluginStart } from '@kbn/home-plugin/public'; import type { ChartsPluginStart } from '@kbn/charts-plugin/public'; +import type { + ObservabilitySharedPluginSetup, + ObservabilitySharedPluginStart, + NavigationEntry, +} from '@kbn/observability-shared-plugin/public'; import { CasesDeepLinkId, CasesUiStart, getCasesDeepLinks } from '@kbn/cases-plugin/public'; import type { LensPublicStart } from '@kbn/lens-plugin/public'; import { @@ -45,19 +51,13 @@ import { LicensingPluginStart } from '@kbn/licensing-plugin/public'; import { UnifiedSearchPublicPluginStart } from '@kbn/unified-search-plugin/public'; import { RuleDetailsLocatorDefinition } from './locators/rule_details'; import { observabilityAppId, observabilityFeatureId, casesPath } from '../common'; -import { createLazyObservabilityPageTemplate } from './components/shared'; import { registerDataHandler } from './data_handler'; import { createObservabilityRuleTypeRegistry, ObservabilityRuleTypeRegistry, } from './rules/create_observability_rule_type_registry'; import { createCallObservabilityApi } from './services/call_observability_api'; -import { createNavigationRegistry, NavigationEntry } from './services/navigation_registry'; -import { updateGlobalNavigation } from './update_global_navigation'; -import { getExploratoryViewEmbeddable } from './components/shared/exploratory_view/embeddable'; -import { createExploratoryViewUrl } from './components/shared/exploratory_view/configurations/exploratory_view_url'; import { createUseRulesLink } from './hooks/create_use_rules_link'; -import getAppDataView from './utils/observability_data_views/get_app_data_view'; import { registerObservabilityRuleTypes } from './rules/register_observability_rule_types'; export interface ConfigSchema { @@ -79,6 +79,7 @@ export type ObservabilityPublicSetup = ReturnType; export interface ObservabilityPublicPluginsSetup { data: DataPublicPluginSetup; + observabilityShared: ObservabilitySharedPluginSetup; share: SharePluginSetup; triggersActionsUi: TriggersAndActionsUIPublicPluginSetup; home?: HomePublicPluginSetup; @@ -93,9 +94,11 @@ export interface ObservabilityPublicPluginsStart { dataViews: DataViewsPublicPluginStart; discover: DiscoverStart; embeddable: EmbeddableStart; + exploratoryView: ExploratoryViewPublicPluginsStart; guidedOnboarding: GuidedOnboardingPluginStart; lens: LensPublicStart; licensing: LicensingPluginStart; + observabilityShared: ObservabilitySharedPluginStart; ruleTypeRegistry: RuleTypeRegistryContract; security: SecurityPluginStart; share: SharePluginStart; @@ -118,7 +121,6 @@ export class Plugin > { private readonly appUpdater$ = new BehaviorSubject(() => ({})); - private readonly navigationRegistry = createNavigationRegistry(); private observabilityRuleTypeRegistry: ObservabilityRuleTypeRegistry = {} as ObservabilityRuleTypeRegistry; @@ -194,7 +196,7 @@ export class Plugin // Load application bundle const { renderApp } = await import('./application'); // Get start services - const [coreStart, pluginsStart, { navigation }] = await coreSetup.getStartServices(); + const [coreStart, pluginsStart] = await coreSetup.getStartServices(); const { ruleTypeRegistry, actionTypeRegistry } = pluginsStart.triggersActionsUi; @@ -204,7 +206,7 @@ export class Plugin plugins: { ...pluginsStart, ruleTypeRegistry, actionTypeRegistry }, appMountParameters: params, observabilityRuleTypeRegistry: this.observabilityRuleTypeRegistry, - ObservabilityPageTemplate: navigation.PageTemplate, + ObservabilityPageTemplate: pluginsStart.observabilityShared.navigation.PageTemplate, usageCollection: pluginsSetup.usageCollection, isDev: this.initContext.env.mode.dev, kibanaVersion, @@ -260,7 +262,7 @@ export class Plugin }); } - this.navigationRegistry.registerSections( + pluginsSetup.observabilityShared.navigation.registerSections( from(appUpdater$).pipe( map((value) => { const deepLinks = value(app)?.deepLinks ?? []; @@ -307,9 +309,6 @@ export class Plugin return { dashboard: { register: registerDataHandler }, observabilityRuleTypeRegistry: this.observabilityRuleTypeRegistry, - navigation: { - registerSections: this.navigationRegistry.registerSections, - }, useRulesLink: createUseRulesLink(), }; } @@ -318,21 +317,12 @@ export class Plugin const { application } = coreStart; const config = this.initContext.config.get(); - updateGlobalNavigation({ + pluginsStart.observabilityShared.updateGlobalNavigation({ capabilities: application.capabilities, deepLinks: this.deepLinks, updater$: this.appUpdater$, }); - const PageTemplate = createLazyObservabilityPageTemplate({ - currentAppId$: application.currentAppId$, - getUrlForApp: application.getUrlForApp, - navigateToApp: application.navigateToApp, - navigationSections$: this.navigationRegistry.sections$, - guidedOnboardingApi: pluginsStart.guidedOnboarding.guidedOnboardingApi, - getPageTemplateServices: () => ({ coreStart }), - }); - const getAsyncO11yAlertsTableConfiguration = async () => { const { getAlertsTableConfiguration } = await import( './components/alerts_table/get_alerts_table_configuration' @@ -348,12 +338,6 @@ export class Plugin return { observabilityRuleTypeRegistry: this.observabilityRuleTypeRegistry, - navigation: { - PageTemplate, - }, - createExploratoryViewUrl, - getAppDataView: getAppDataView(pluginsStart.dataViews), - ExploratoryViewEmbeddable: getExploratoryViewEmbeddable({ ...coreStart, ...pluginsStart }), useRulesLink: createUseRulesLink(), }; } diff --git a/x-pack/plugins/observability/tsconfig.json b/x-pack/plugins/observability/tsconfig.json index a4f56eea4f135..d4bddba180e7a 100644 --- a/x-pack/plugins/observability/tsconfig.json +++ b/x-pack/plugins/observability/tsconfig.json @@ -74,7 +74,9 @@ "@kbn/core-application-browser", "@kbn/files-plugin", "@kbn/core-theme-browser", - "@kbn/core-elasticsearch-server" + "@kbn/core-elasticsearch-server", + "@kbn/observability-shared-plugin", + "@kbn/exploratory-view-plugin" ], "exclude": ["target/**/*"] } diff --git a/x-pack/plugins/observability_shared/public/hooks/use_track_metric.tsx b/x-pack/plugins/observability_shared/public/hooks/use_track_metric.tsx new file mode 100644 index 0000000000000..7138c20ef6aa6 --- /dev/null +++ b/x-pack/plugins/observability_shared/public/hooks/use_track_metric.tsx @@ -0,0 +1,91 @@ +/* + * Copyright 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 { useEffect, useMemo } from 'react'; +import { METRIC_TYPE, UiCounterMetricType } from '@kbn/analytics'; +import { UsageCollectionSetup } from '@kbn/usage-collection-plugin/public'; +import { useKibana } from '@kbn/kibana-react-plugin/public'; +import { ObservabilityApp } from '../../typings/common'; + +/** + * Note: The usage_collection plugin will take care of sending this data to the telemetry server. + * You can find the metrics that are collected by these hooks in Stack Telemetry. + * Search the index `kibana-ui-counter`. You can filter for `eventName` and/or `appName`. + */ + +interface TrackOptions { + app?: ObservabilityApp; + metricType?: UiCounterMetricType; + delay?: number; // in ms +} +type EffectDeps = unknown[]; + +interface ServiceDeps { + usageCollection: UsageCollectionSetup; // TODO: This should really be start. Looking into it. +} + +export type TrackMetricOptions = TrackOptions & { metric: string }; +export type UiTracker = ReturnType; +export type TrackEvent = (options: TrackMetricOptions) => void; + +export { METRIC_TYPE }; + +export function useUiTracker({ + app: defaultApp, +}: { app?: ObservabilityApp } = {}): TrackEvent { + const reportUiCounter = useKibana().services?.usageCollection?.reportUiCounter; + const trackEvent = useMemo(() => { + return ({ app = defaultApp, metric, metricType = METRIC_TYPE.COUNT }: TrackMetricOptions) => { + if (reportUiCounter) { + reportUiCounter(app as string, metricType, metric); + } + }; + }, [defaultApp, reportUiCounter]); + return trackEvent; +} + +export function useTrackMetric( + { app, metric, metricType = METRIC_TYPE.COUNT, delay = 0 }: TrackMetricOptions, + effectDependencies: EffectDeps = [] +) { + const reportUiCounter = useKibana().services?.usageCollection?.reportUiCounter; + + useEffect(() => { + if (!reportUiCounter) { + // eslint-disable-next-line no-console + console.log( + 'usageCollection.reportUiCounter is unavailable. Ensure this is setup via .' + ); + } else { + let decoratedMetric = metric; + if (delay > 0) { + decoratedMetric += `__delayed_${delay}ms`; + } + const id = setTimeout( + () => reportUiCounter(app as string, metricType, decoratedMetric), + Math.max(delay, 0) + ); + return () => clearTimeout(id); + } + // the dependencies are managed externally + // eslint-disable-next-line react-hooks/exhaustive-deps + }, effectDependencies); +} + +/** + * useTrackPageview is a convenience wrapper for tracking a pageview + * Its metrics will be found at: + * stack_stats.kibana.plugins.ui_metric.{app}.pageview__{path}(__delayed_{n}ms)? + */ +type TrackPageviewProps = TrackOptions & { path: string }; + +export function useTrackPageview( + { path, ...rest }: TrackPageviewProps, + effectDependencies: EffectDeps = [] +) { + useTrackMetric({ ...rest, metric: `pageview__${path}` }, effectDependencies); +} diff --git a/x-pack/plugins/observability_shared/public/index.ts b/x-pack/plugins/observability_shared/public/index.ts index ecb2095e4eb6f..83cafa04bd2b5 100644 --- a/x-pack/plugins/observability_shared/public/index.ts +++ b/x-pack/plugins/observability_shared/public/index.ts @@ -19,8 +19,15 @@ export type { export type { NavigationEntry } from './components/page_template/page_template'; +export { useObservabilityTourContext } from './components/tour'; + export const plugin = () => { return new ObservabilitySharedPlugin(); }; -export { observabilityFeatureId, casesFeatureId, sloFeatureId } from '../common'; +export { + observabilityFeatureId, + observabilityAppId, + casesFeatureId, + sloFeatureId, +} from '../common'; diff --git a/x-pack/plugins/observability_shared/tsconfig.json b/x-pack/plugins/observability_shared/tsconfig.json index fba33ee93d5f4..58d99188c9216 100644 --- a/x-pack/plugins/observability_shared/tsconfig.json +++ b/x-pack/plugins/observability_shared/tsconfig.json @@ -20,6 +20,8 @@ "@kbn/shared-ux-page-kibana-template", "@kbn/i18n-react", "@kbn/shared-ux-page-kibana-template-mocks", + "@kbn/analytics", + "@kbn/usage-collection-plugin", ], "exclude": ["target/**/*"] } diff --git a/x-pack/plugins/profiling/public/components/profiling_app_page_template/index.tsx b/x-pack/plugins/profiling/public/components/profiling_app_page_template/index.tsx index 4114082efe9e5..2a353f3b4db61 100644 --- a/x-pack/plugins/profiling/public/components/profiling_app_page_template/index.tsx +++ b/x-pack/plugins/profiling/public/components/profiling_app_page_template/index.tsx @@ -39,10 +39,10 @@ export function ProfilingAppPageTemplate({ pageTitle?: React.ReactNode; }) { const { - start: { observability }, + start: { observabilityShared }, } = useProfilingDependencies(); - const { PageTemplate: ObservabilityPageTemplate } = observability.navigation; + const { PageTemplate: ObservabilityPageTemplate } = observabilityShared.navigation; const history = useHistory(); diff --git a/x-pack/plugins/profiling/public/routing/router_error_boundary.tsx b/x-pack/plugins/profiling/public/routing/router_error_boundary.tsx index 355ee1bd42f79..0134f70ab1ec0 100644 --- a/x-pack/plugins/profiling/public/routing/router_error_boundary.tsx +++ b/x-pack/plugins/profiling/public/routing/router_error_boundary.tsx @@ -44,9 +44,9 @@ const pageHeader = { function ErrorWithTemplate({ error }: { error: Error }) { const { services } = useKibana(); - const { observability } = services; + const { observabilityShared } = services; - const ObservabilityPageTemplate = observability.navigation.PageTemplate; + const ObservabilityPageTemplate = observabilityShared.navigation.PageTemplate; if (error instanceof NotFoundRouteException) { return ( diff --git a/x-pack/plugins/profiling/public/types.ts b/x-pack/plugins/profiling/public/types.ts index da0bc65f02d34..7db396acd5e3b 100644 --- a/x-pack/plugins/profiling/public/types.ts +++ b/x-pack/plugins/profiling/public/types.ts @@ -13,7 +13,10 @@ import type { ObservabilityPublicSetup, ObservabilityPublicStart, } from '@kbn/observability-plugin/public'; -import { ObservabilitySharedPluginSetup } from '@kbn/observability-shared-plugin/public/plugin'; +import { + ObservabilitySharedPluginSetup, + ObservabilitySharedPluginStart, +} from '@kbn/observability-shared-plugin/public/plugin'; import { ChartsPluginSetup, ChartsPluginStart } from '@kbn/charts-plugin/public'; import { LicensingPluginSetup } from '@kbn/licensing-plugin/public'; @@ -28,6 +31,7 @@ export interface ProfilingPluginPublicSetupDeps { export interface ProfilingPluginPublicStartDeps { observability: ObservabilityPublicStart; + observabilityShared: ObservabilitySharedPluginStart; dataViews: DataViewsPublicPluginStart; data: DataPublicPluginStart; charts: ChartsPluginStart; diff --git a/x-pack/plugins/synthetics/kibana.jsonc b/x-pack/plugins/synthetics/kibana.jsonc index 79e31899b3dac..a70202e3126a1 100644 --- a/x-pack/plugins/synthetics/kibana.jsonc +++ b/x-pack/plugins/synthetics/kibana.jsonc @@ -7,10 +7,7 @@ "id": "synthetics", "server": true, "browser": true, - "configPath": [ - "xpack", - "uptime" - ], + "configPath": ["xpack", "uptime"], "requiredPlugins": [ "actions", "alerting", @@ -24,6 +21,7 @@ "inspector", "licensing", "observability", + "observabilityShared", "ruleRegistry", "security", "share", @@ -33,15 +31,7 @@ "unifiedSearch", "bfetch" ], - "optionalPlugins": [ - "cloud", - "data", - "fleet", - "home", - "ml", - "spaces", - "telemetry" - ], + "optionalPlugins": ["cloud", "data", "fleet", "home", "ml", "spaces", "telemetry"], "requiredBundles": [ "data", "unifiedSearch", diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/common/components/stderr_logs.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/common/components/stderr_logs.tsx index 3093c2e8534cd..6c5e80c9f28e4 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/common/components/stderr_logs.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/common/components/stderr_logs.tsx @@ -68,10 +68,10 @@ export const StdErrorLogs = ({ const { items, loading } = useStdErrorLogs({ monitorId, checkGroup }); - const { discover, observability } = useKibana().services; + const { discover, exploratoryView } = useKibana().services; const { data: discoverLink } = useFetcher(async () => { - const dataView = await observability.getAppDataView('synthetics', SYNTHETICS_INDEX_PATTERN); + const dataView = await exploratoryView.getAppDataView('synthetics', SYNTHETICS_INDEX_PATTERN); return discover.locator?.getUrl({ query: { language: 'kuery', query: `monitor.check_group: ${checkGroup}` }, indexPatternId: dataView?.id, diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/common/page_template/synthetics_page_template.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/common/page_template/synthetics_page_template.tsx index b9ce0e722f24e..9647e7592a0b9 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/common/page_template/synthetics_page_template.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/common/page_template/synthetics_page_template.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import { LazyObservabilityPageTemplateProps } from '@kbn/observability-plugin/public'; +import { LazyObservabilityPageTemplateProps } from '@kbn/observability-shared-plugin/public'; import React from 'react'; import { euiStyled } from '@kbn/kibana-react-plugin/common'; import { useKibana } from '@kbn/kibana-react-plugin/public'; @@ -13,8 +13,8 @@ import { useKibana } from '@kbn/kibana-react-plugin/public'; import { ClientPluginsStart } from '../../../../../plugin'; export const WrappedPageTemplate = (props: LazyObservabilityPageTemplateProps) => { - const { observability } = useKibana().services; - const PageTemplateComponent = observability.navigation.PageTemplate; + const { observabilityShared } = useKibana().services; + const PageTemplateComponent = observabilityShared.navigation.PageTemplate; return ; }; diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/routes.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/routes.tsx index 487e82e5538d9..e082a318fe568 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/routes.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/routes.tsx @@ -16,7 +16,7 @@ import { i18n } from '@kbn/i18n'; import { NotFoundPrompt } from '@kbn/shared-ux-prompt-not-found'; import { APP_WRAPPER_CLASS } from '@kbn/core/public'; import { useKibana } from '@kbn/kibana-react-plugin/public'; -import type { LazyObservabilityPageTemplateProps } from '@kbn/observability-plugin/public'; +import type { LazyObservabilityPageTemplateProps } from '@kbn/observability-shared-plugin/public'; import { useInspectorContext } from '@kbn/observability-plugin/public'; import { useSyntheticsPrivileges } from './hooks/use_synthetics_priviliges'; import { ClientPluginsStart } from '../../plugin'; diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/synthetics_app.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/synthetics_app.tsx index 1b67c882869c7..8cb9c8b3364fc 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/synthetics_app.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/synthetics_app.tsx @@ -91,6 +91,7 @@ const Application = (props: SyntheticsAppProps) => { inspector: startPlugins.inspector, triggersActionsUi: startPlugins.triggersActionsUi, observability: startPlugins.observability, + observabilityShared: startPlugins.observabilityShared, exploratoryView: startPlugins.exploratoryView, cases: startPlugins.cases, spaces: startPlugins.spaces, diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/utils/testing/rtl_helpers.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/utils/testing/rtl_helpers.tsx index ad79d6f293a45..59fd849d2e3f4 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/utils/testing/rtl_helpers.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/utils/testing/rtl_helpers.tsx @@ -135,8 +135,16 @@ export const mockCore: () => Partial = () => { triggersActionsUi: triggersActionsUiMock.createStart(), storage: createMockStore(), data: dataPluginMock.createStartContract(), + // @ts-ignore observability: { useRulesLink: () => ({ href: 'newRuleLink' }), + observabilityRuleTypeRegistry: { + register: jest.fn(), + getFormatter: jest.fn(), + list: jest.fn(), + }, + }, + observabilityShared: { navigation: { // @ts-ignore PageTemplate: EuiPageTemplate, @@ -168,6 +176,7 @@ export function MockKibanaProvider({ diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/app/uptime_app.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/app/uptime_app.tsx index df6f287f8ab03..83f05eefa2577 100644 --- a/x-pack/plugins/synthetics/public/legacy_uptime/app/uptime_app.tsx +++ b/x-pack/plugins/synthetics/public/legacy_uptime/app/uptime_app.tsx @@ -124,6 +124,7 @@ const Application = (props: UptimeAppProps) => { inspector: startPlugins.inspector, triggersActionsUi: startPlugins.triggersActionsUi, observability: startPlugins.observability, + observabilityShared: startPlugins.observabilityShared, exploratoryView: startPlugins.exploratoryView, cases: startPlugins.cases, }} diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/app/uptime_page_template.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/app/uptime_page_template.tsx index c1003f969586c..ed294cca26f7d 100644 --- a/x-pack/plugins/synthetics/public/legacy_uptime/app/uptime_page_template.tsx +++ b/x-pack/plugins/synthetics/public/legacy_uptime/app/uptime_page_template.tsx @@ -28,10 +28,10 @@ export const UptimePageTemplateComponent: React.FC ...pageTemplateProps }) => { const { - services: { observability }, + services: { observabilityShared }, } = useKibana(); - const PageTemplateComponent = observability.navigation.PageTemplate; + const PageTemplateComponent = observabilityShared.navigation.PageTemplate; const noDataConfig = useNoDataConfig(); diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/lib/helper/rtl_helpers.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/lib/helper/rtl_helpers.tsx index 6908ebb4677e3..5aab33986d0e6 100644 --- a/x-pack/plugins/synthetics/public/legacy_uptime/lib/helper/rtl_helpers.tsx +++ b/x-pack/plugins/synthetics/public/legacy_uptime/lib/helper/rtl_helpers.tsx @@ -133,8 +133,16 @@ export const mockCore: () => Partial = () => { triggersActionsUi: triggersActionsUiMock.createStart(), storage: createMockStore(), data: dataPluginMock.createStartContract(), + // @ts-ignore observability: { useRulesLink: () => ({ href: 'newRuleLink' }), + observabilityRuleTypeRegistry: { + register: jest.fn(), + getFormatter: jest.fn(), + list: jest.fn(), + }, + }, + observabilityShared: { navigation: { // @ts-ignore PageTemplate: EuiPageTemplate, diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/routes.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/routes.tsx index c4032142c55d9..ad1f536bb2921 100644 --- a/x-pack/plugins/synthetics/public/legacy_uptime/routes.tsx +++ b/x-pack/plugins/synthetics/public/legacy_uptime/routes.tsx @@ -13,7 +13,7 @@ import { FormattedMessage } from '@kbn/i18n-react'; import { i18n } from '@kbn/i18n'; import { APP_WRAPPER_CLASS } from '@kbn/core/public'; import { useInspectorContext } from '@kbn/observability-plugin/public'; -import type { LazyObservabilityPageTemplateProps } from '@kbn/observability-plugin/public'; +import type { LazyObservabilityPageTemplateProps } from '@kbn/observability-shared-plugin/public'; import { CERTIFICATES_ROUTE, MAPPING_ERROR_ROUTE, diff --git a/x-pack/plugins/synthetics/public/plugin.ts b/x-pack/plugins/synthetics/public/plugin.ts index cdc32a32b0951..370dd2e802bdf 100644 --- a/x-pack/plugins/synthetics/public/plugin.ts +++ b/x-pack/plugins/synthetics/public/plugin.ts @@ -46,6 +46,10 @@ import { DataViewsPublicPluginStart } from '@kbn/data-views-plugin/public'; import { SpacesPluginStart } from '@kbn/spaces-plugin/public'; import type { DocLinksStart } from '@kbn/core-doc-links-browser'; import type { UsageCollectionStart } from '@kbn/usage-collection-plugin/public'; +import type { + ObservabilitySharedPluginSetup, + ObservabilitySharedPluginStart, +} from '@kbn/observability-shared-plugin/public'; import { PLUGIN } from '../common/constants/plugin'; import { OVERVIEW_ROUTE } from '../common/constants/ui'; import { @@ -66,6 +70,7 @@ export interface ClientPluginsSetup { data: DataPublicPluginSetup; exploratoryView: ExploratoryViewPublicSetup; observability: ObservabilityPublicSetup; + observabilityShared: ObservabilitySharedPluginSetup; share: SharePluginSetup; triggersActionsUi: TriggersAndActionsUIPublicPluginSetup; cloud?: CloudSetup; @@ -80,6 +85,7 @@ export interface ClientPluginsStart { embeddable: EmbeddableStart; exploratoryView: ExploratoryViewPublicStart; observability: ObservabilityPublicStart; + observabilityShared: ObservabilitySharedPluginStart; share: SharePluginStart; triggersActionsUi: TriggersAndActionsUIPublicPluginStart; cases: CasesUiStart; @@ -273,7 +279,7 @@ function registerUptimeRoutesWithNavigation( core: CoreSetup, plugins: ClientPluginsSetup ) { - plugins.observability.navigation.registerSections( + plugins.observabilityShared.navigation.registerSections( from(core.getStartServices()).pipe( map(([coreStart]) => { if (coreStart.application.capabilities.uptime.show) { diff --git a/x-pack/plugins/synthetics/tsconfig.json b/x-pack/plugins/synthetics/tsconfig.json index 1809306e08b7e..a02e14d577b35 100644 --- a/x-pack/plugins/synthetics/tsconfig.json +++ b/x-pack/plugins/synthetics/tsconfig.json @@ -77,6 +77,7 @@ "@kbn/shared-ux-router", "@kbn/alerts-as-data-utils", "@kbn/exploratory-view-plugin", + "@kbn/observability-shared-plugin", ], "exclude": [ "target/**/*", diff --git a/x-pack/plugins/ux/kibana.jsonc b/x-pack/plugins/ux/kibana.jsonc index c9b02848460bd..341d9d8fcc1f8 100644 --- a/x-pack/plugins/ux/kibana.jsonc +++ b/x-pack/plugins/ux/kibana.jsonc @@ -14,6 +14,7 @@ "exploratoryView", "licensing", "triggersActionsUi", + "observabilityShared", "embeddable", "infra", "inspector", diff --git a/x-pack/plugins/ux/public/application/ux_app.tsx b/x-pack/plugins/ux/public/application/ux_app.tsx index 7d6957f0abade..42e7d8109f8ed 100644 --- a/x-pack/plugins/ux/public/application/ux_app.tsx +++ b/x-pack/plugins/ux/public/application/ux_app.tsx @@ -110,6 +110,7 @@ export function UXAppRoot({ inspector, maps, observability, + observabilityShared, exploratoryView, data, dataViews, @@ -138,6 +139,7 @@ export function UXAppRoot({ ...plugins, inspector, observability, + observabilityShared, embeddable, exploratoryView, data, diff --git a/x-pack/plugins/ux/public/components/app/rum_dashboard/rum_home.tsx b/x-pack/plugins/ux/public/components/app/rum_dashboard/rum_home.tsx index 00c4c2be6d3e0..36844867be4f5 100644 --- a/x-pack/plugins/ux/public/components/app/rum_dashboard/rum_home.tsx +++ b/x-pack/plugins/ux/public/components/app/rum_dashboard/rum_home.tsx @@ -25,9 +25,9 @@ export const DASHBOARD_LABEL = i18n.translate('xpack.ux.title', { }); export function RumHome() { - const { docLinks, http, observability } = useKibanaServices(); + const { docLinks, http, observabilityShared } = useKibanaServices(); - const PageTemplateComponent = observability.navigation.PageTemplate; + const PageTemplateComponent = observabilityShared.navigation.PageTemplate; const { hasData, loading: isLoading } = useHasRumData(); diff --git a/x-pack/plugins/ux/public/plugin.ts b/x-pack/plugins/ux/public/plugin.ts index d5246fca56108..a578b6aca0b79 100644 --- a/x-pack/plugins/ux/public/plugin.ts +++ b/x-pack/plugins/ux/public/plugin.ts @@ -38,6 +38,10 @@ import { MapsStartApi } from '@kbn/maps-plugin/public'; import { Start as InspectorPluginStart } from '@kbn/inspector-plugin/public'; import { DataViewsPublicPluginStart } from '@kbn/data-views-plugin/public'; import { LensPublicStart } from '@kbn/lens-plugin/public'; +import { + ObservabilitySharedPluginSetup, + ObservabilitySharedPluginStart, +} from '@kbn/observability-shared-plugin/public'; export type UxPluginSetup = void; export type UxPluginStart = void; @@ -49,6 +53,7 @@ export interface ApmPluginSetupDeps { home?: HomePublicPluginSetup; licensing: LicensingPluginSetup; observability: ObservabilityPublicSetup; + observabilityShared: ObservabilitySharedPluginSetup; } export interface ApmPluginStartDeps { @@ -59,6 +64,7 @@ export interface ApmPluginStartDeps { maps?: MapsStartApi; inspector: InspectorPluginStart; observability: ObservabilityPublicStart; + observabilityShared: ObservabilitySharedPluginStart; exploratoryView: ExploratoryViewPublicStart; dataViews: DataViewsPublicPluginStart; lens: LensPublicStart; @@ -126,7 +132,7 @@ export class UxPlugin implements Plugin { } // register observability nav if user has access to plugin - plugins.observability.navigation.registerSections( + plugins.observabilityShared.navigation.registerSections( from(core.getStartServices()).pipe( map(([coreStart]) => { // checking apm capability, since ux for now doesn't have it's diff --git a/x-pack/plugins/ux/tsconfig.json b/x-pack/plugins/ux/tsconfig.json index 336dbf9b94e5d..b1406ed25aa9c 100644 --- a/x-pack/plugins/ux/tsconfig.json +++ b/x-pack/plugins/ux/tsconfig.json @@ -37,6 +37,7 @@ "@kbn/i18n-react", "@kbn/es-query", "@kbn/exploratory-view-plugin", + "@kbn/observability-shared-plugin", ], "exclude": [ "target/**/*", From 065fdf9a4985f5dc39758bfb05a484861099631a Mon Sep 17 00:00:00 2001 From: GitStart <1501599+gitstart@users.noreply.github.com> Date: Wed, 19 Apr 2023 01:44:01 +0300 Subject: [PATCH 18/78] Space switcher not visible when sidebar is expanded (Accessibility) (#154622) Space switcher not visible when sidebar is expanded (Accessibility) Resolves https://github.com/elastic/kibana/issues/153464 ### Loom video/Screenshot #### BEFORE ![image](https://user-images.githubusercontent.com/50892465/227374824-0d8d08b7-d542-4572-870a-4d5b0f8fd98e.png) #### AFTER ![image](https://user-images.githubusercontent.com/50892465/227374655-ddf208c9-959c-4251-ac4d-4a70848999f7.png) --- This code was written and reviewed by GitStart Community. Growing future engineers, one PR at a time. --------- Co-authored-by: KlingerMatheus Co-authored-by: gitstart_bot Co-authored-by: Klinger Matheus <50892465+KlingerMatheus@users.noreply.github.com> Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../nav_control_popover.test.tsx.snap | 210 ++++++++++++++---- .../nav_control/nav_control_popover.tsx | 16 +- 2 files changed, 175 insertions(+), 51 deletions(-) diff --git a/x-pack/plugins/spaces/public/nav_control/__snapshots__/nav_control_popover.test.tsx.snap b/x-pack/plugins/spaces/public/nav_control/__snapshots__/nav_control_popover.test.tsx.snap index ea194bafa3d3c..e89342884cc2d 100644 --- a/x-pack/plugins/spaces/public/nav_control/__snapshots__/nav_control_popover.test.tsx.snap +++ b/x-pack/plugins/spaces/public/nav_control/__snapshots__/nav_control_popover.test.tsx.snap @@ -1,55 +1,169 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`NavControlPopover renders without crashing 1`] = ` - - - - + capabilities={ + Object { + "catalogue": Object {}, + "management": Object {}, + "navLinks": Object {}, + "spaces": Object { + "manage": true, + }, + } + } + navigateToApp={[MockFunction]} + navigateToUrl={[MockFunction]} + serverBasePath="/server-base-path" + spacesManager={ + Object { + "copySavedObjects": [MockFunction], + "createSpace": [MockFunction], + "deleteSpace": [MockFunction], + "disableLegacyUrlAliases": [MockFunction], + "getActiveSpace": [MockFunction], + "getShareSavedObjectPermissions": [MockFunction], + "getShareableReferences": [MockFunction], + "getSpace": [MockFunction], + "getSpaces": [MockFunction], + "onActiveSpaceChange$": Observable { + "_subscribe": [Function], + }, + "redirectToSpaceSelector": [MockFunction], + "resolveCopySavedObjectsErrors": [MockFunction], + "updateSavedObjectsSpaces": [MockFunction], + "updateSpace": [MockFunction], + } } - closePopover={[Function]} - display="inline-block" - hasArrow={true} - id="spcMenuPopover" - isOpen={false} - ownFocus={true} - panelPaddingSize="none" - repositionOnScroll={true} -> - - + } +/> `; diff --git a/x-pack/plugins/spaces/public/nav_control/nav_control_popover.tsx b/x-pack/plugins/spaces/public/nav_control/nav_control_popover.tsx index 1e75bd0ae0f65..45a6c21c86f04 100644 --- a/x-pack/plugins/spaces/public/nav_control/nav_control_popover.tsx +++ b/x-pack/plugins/spaces/public/nav_control/nav_control_popover.tsx @@ -5,8 +5,13 @@ * 2.0. */ -import type { PopoverAnchorPosition } from '@elastic/eui'; -import { EuiHeaderSectionItemButton, EuiLoadingSpinner, EuiPopover } from '@elastic/eui'; +import type { PopoverAnchorPosition, WithEuiThemeProps } from '@elastic/eui'; +import { + EuiHeaderSectionItemButton, + EuiLoadingSpinner, + EuiPopover, + withEuiTheme, +} from '@elastic/eui'; import React, { Component, lazy, Suspense } from 'react'; import type { Subscription } from 'rxjs'; @@ -31,6 +36,7 @@ interface Props { navigateToApp: ApplicationStart['navigateToApp']; navigateToUrl: ApplicationStart['navigateToUrl']; serverBasePath: string; + theme: WithEuiThemeProps['theme']; } interface State { @@ -42,7 +48,7 @@ interface State { const popoutContentId = 'headerSpacesMenuContent'; -export class NavControlPopover extends Component { +class NavControlPopoverUI extends Component { private activeSpace$?: Subscription; constructor(props: Props) { @@ -73,6 +79,7 @@ export class NavControlPopover extends Component { public render() { const button = this.getActiveSpaceButton(); + const { theme } = this.props; let element: React.ReactNode; if (this.state.loading || this.state.spaces.length < 2) { @@ -110,6 +117,7 @@ export class NavControlPopover extends Component { panelPaddingSize="none" repositionOnScroll ownFocus + zIndex={Number(theme.euiTheme.levels.navigation) + 1} // it needs to sit above the collapsible nav menu > {element} @@ -199,3 +207,5 @@ export class NavControlPopover extends Component { }); }; } + +export const NavControlPopover = withEuiTheme(NavControlPopoverUI); From 904ed92c3358feacf3bdebf4f4aa9f169915627b Mon Sep 17 00:00:00 2001 From: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Date: Wed, 19 Apr 2023 00:58:14 -0400 Subject: [PATCH 19/78] [api-docs] 2023-04-19 Daily api_docs build (#155217) Generated by https://buildkite.com/elastic/kibana-api-docs-daily/builds/312 --- api_docs/actions.mdx | 2 +- api_docs/advanced_settings.mdx | 2 +- api_docs/aiops.mdx | 2 +- api_docs/alerting.mdx | 2 +- api_docs/apm.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.devdocs.json | 24 ++++ api_docs/cases.mdx | 2 +- api_docs/charts.mdx | 2 +- api_docs/cloud.mdx | 2 +- api_docs/cloud_chat.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.mdx | 2 +- api_docs/dashboard_enhanced.mdx | 2 +- api_docs/data.devdocs.json | 64 ++++----- api_docs/data.mdx | 2 +- api_docs/data_query.mdx | 2 +- api_docs/data_search.mdx | 2 +- 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.devdocs.json | 96 +++++++------- api_docs/data_views.mdx | 2 +- api_docs/data_visualizer.mdx | 2 +- api_docs/deprecations_by_api.mdx | 14 +- api_docs/deprecations_by_plugin.mdx | 2 +- api_docs/deprecations_by_team.mdx | 2 +- api_docs/dev_tools.mdx | 2 +- api_docs/discover.devdocs.json | 8 +- api_docs/discover.mdx | 2 +- api_docs/discover_enhanced.mdx | 2 +- api_docs/ecs_data_quality_dashboard.mdx | 2 +- api_docs/embeddable.mdx | 2 +- api_docs/embeddable_enhanced.mdx | 2 +- 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_log.mdx | 2 +- api_docs/exploratory_view.devdocs.json | 124 +++--------------- api_docs/exploratory_view.mdx | 4 +- 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.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.devdocs.json | 24 ++++ api_docs/files.mdx | 2 +- api_docs/files_management.mdx | 2 +- api_docs/fleet.devdocs.json | 45 ++++++- api_docs/fleet.mdx | 4 +- api_docs/global_search.mdx | 2 +- 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/inspector.mdx | 2 +- api_docs/interactive_setup.mdx | 2 +- api_docs/kbn_ace.mdx | 2 +- api_docs/kbn_aiops_components.mdx | 2 +- api_docs/kbn_aiops_utils.mdx | 2 +- api_docs/kbn_alerting_state_types.mdx | 2 +- api_docs/kbn_alerts.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 +- ..._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_analytics_shippers_gainsight.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_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_mocks.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 +- .../kbn_content_management_table_list.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.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 +- 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 +- ...core_saved_objects_api_server_internal.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 +- ...bn_core_saved_objects_browser.devdocs.json | 16 +-- 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 +- ...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_internal.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_crypto.mdx | 2 +- api_docs/kbn_crypto_browser.mdx | 2 +- api_docs/kbn_cypress_config.mdx | 2 +- api_docs/kbn_datemath.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_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_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_expandable_flyout.mdx | 2 +- api_docs/kbn_field_types.mdx | 2 +- api_docs/kbn_find_used_node_modules.mdx | 2 +- .../kbn_ftr_common_functional_services.mdx | 2 +- api_docs/kbn_generate.mdx | 2 +- api_docs/kbn_generate_csv.mdx | 2 +- api_docs/kbn_generate_csv_types.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_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_logging.mdx | 2 +- api_docs/kbn_logging_mocks.mdx | 2 +- api_docs/kbn_managed_vscode_config.mdx | 2 +- api_docs/kbn_mapbox_gl.mdx | 2 +- api_docs/kbn_ml_agg_utils.mdx | 2 +- api_docs/kbn_ml_date_picker.mdx | 2 +- api_docs/kbn_ml_is_defined.mdx | 2 +- api_docs/kbn_ml_is_populated_object.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_string_hash.mdx | 2 +- api_docs/kbn_ml_trained_models_utils.mdx | 2 +- api_docs/kbn_ml_url_state.mdx | 2 +- api_docs/kbn_monaco.mdx | 2 +- api_docs/kbn_object_versioning.mdx | 2 +- api_docs/kbn_observability_alert_details.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 +- ..._performance_testing_dataset_extractor.mdx | 2 +- api_docs/kbn_plugin_generator.mdx | 2 +- api_docs/kbn_plugin_helpers.mdx | 2 +- api_docs/kbn_react_field.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_rison.mdx | 2 +- api_docs/kbn_rule_data_utils.mdx | 2 +- api_docs/kbn_saved_objects_settings.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_shared_svg.mdx | 2 +- api_docs/kbn_shared_ux_avatar_solution.mdx | 2 +- ...ared_ux_avatar_user_profile_components.mdx | 2 +- .../kbn_shared_ux_button_exit_full_screen.mdx | 2 +- ...hared_ux_button_exit_full_screen_mocks.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_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_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_jest_helpers.mdx | 2 +- api_docs/kbn_test_subj_selector.mdx | 2 +- api_docs/kbn_tooling_log.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_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_yarn_lock_validator.mdx | 2 +- api_docs/kibana_overview.mdx | 2 +- api_docs/kibana_react.devdocs.json | 24 ++-- api_docs/kibana_react.mdx | 2 +- api_docs/kibana_utils.mdx | 2 +- api_docs/kubernetes_security.mdx | 2 +- api_docs/lens.devdocs.json | 44 ++++++- api_docs/lens.mdx | 4 +- api_docs/license_api_guard.mdx | 2 +- api_docs/license_management.mdx | 2 +- api_docs/licensing.mdx | 2 +- api_docs/lists.mdx | 2 +- api_docs/management.mdx | 2 +- api_docs/maps.devdocs.json | 4 +- api_docs/maps.mdx | 2 +- api_docs/maps_ems.mdx | 2 +- api_docs/ml.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/notifications.mdx | 2 +- api_docs/observability.devdocs.json | 116 ++++++++++------ api_docs/observability.mdx | 4 +- api_docs/observability_shared.devdocs.json | 36 ++++- api_docs/observability_shared.mdx | 7 +- api_docs/osquery.mdx | 2 +- api_docs/plugin_directory.mdx | 16 +-- api_docs/presentation_util.mdx | 2 +- api_docs/profiling.mdx | 2 +- api_docs/remote_clusters.mdx | 2 +- api_docs/reporting.mdx | 2 +- api_docs/rollup.mdx | 2 +- api_docs/rule_registry.devdocs.json | 48 ++++++- api_docs/rule_registry.mdx | 4 +- 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.mdx | 2 +- api_docs/screenshot_mode.mdx | 2 +- api_docs/screenshotting.mdx | 2 +- api_docs/security.mdx | 2 +- api_docs/security_solution.devdocs.json | 2 +- api_docs/security_solution.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.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/threat_intelligence.mdx | 2 +- api_docs/timelines.mdx | 2 +- api_docs/transform.mdx | 2 +- api_docs/triggers_actions_ui.mdx | 2 +- api_docs/ui_actions.mdx | 2 +- api_docs/ui_actions_enhanced.mdx | 2 +- api_docs/unified_field_list.mdx | 2 +- api_docs/unified_histogram.mdx | 2 +- api_docs/unified_search.mdx | 2 +- api_docs/unified_search_autocomplete.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.mdx | 2 +- 513 files changed, 930 insertions(+), 782 deletions(-) diff --git a/api_docs/actions.mdx b/api_docs/actions.mdx index 0811555cbf3dc..139f36cb6eeff 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: 2023-04-18 +date: 2023-04-19 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 04cd347c6770b..ef559a0a5d7d7 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: 2023-04-18 +date: 2023-04-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'advancedSettings'] --- import advancedSettingsObj from './advanced_settings.devdocs.json'; diff --git a/api_docs/aiops.mdx b/api_docs/aiops.mdx index ea3fcfd19787f..06179f373550e 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: 2023-04-18 +date: 2023-04-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'aiops'] --- import aiopsObj from './aiops.devdocs.json'; diff --git a/api_docs/alerting.mdx b/api_docs/alerting.mdx index 644fb8c1b3f4f..45d873622cc86 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: 2023-04-18 +date: 2023-04-19 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 3f389b67d3199..e5bbbe4962403 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: 2023-04-18 +date: 2023-04-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'apm'] --- import apmObj from './apm.devdocs.json'; diff --git a/api_docs/asset_manager.mdx b/api_docs/asset_manager.mdx index 5c7743d5b59f4..3ac0fced2b457 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: 2023-04-18 +date: 2023-04-19 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 47119b7dbf18e..f6f092e5107d3 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: 2023-04-18 +date: 2023-04-19 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 8113d67e3d175..c414bab498ebb 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: 2023-04-18 +date: 2023-04-19 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 69e7e654ce7f1..92e14941438e6 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: 2023-04-18 +date: 2023-04-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'canvas'] --- import canvasObj from './canvas.devdocs.json'; diff --git a/api_docs/cases.devdocs.json b/api_docs/cases.devdocs.json index 638d5dffb0c7d..890144e0b538d 100644 --- a/api_docs/cases.devdocs.json +++ b/api_docs/cases.devdocs.json @@ -794,6 +794,30 @@ "section": "def-common.CommentType", "text": "CommentType" }, + ".externalReference; owner: string; } | { externalReferenceId: string; externalReferenceStorage: { type: ", + { + "pluginId": "cases", + "scope": "common", + "docId": "kibCasesPluginApi", + "section": "def-common.ExternalReferenceStorageType", + "text": "ExternalReferenceStorageType" + }, + ".savedObject; soType: string; }; externalReferenceAttachmentTypeId: string; externalReferenceMetadata: { [x: string]: ", + { + "pluginId": "@kbn/utility-types", + "scope": "common", + "docId": "kibKbnUtilityTypesPluginApi", + "section": "def-common.JsonValue", + "text": "JsonValue" + }, + "; } | null; type: ", + { + "pluginId": "cases", + "scope": "common", + "docId": "kibCasesPluginApi", + "section": "def-common.CommentType", + "text": "CommentType" + }, ".externalReference; owner: string; } | { type: ", { "pluginId": "cases", diff --git a/api_docs/cases.mdx b/api_docs/cases.mdx index ec6e6659e6d9e..0d6a40ab12ce6 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: 2023-04-18 +date: 2023-04-19 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 4fef26eee2b88..3eeb9afd99e19 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: 2023-04-18 +date: 2023-04-19 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 599523319184f..c06de42b2b643 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: 2023-04-18 +date: 2023-04-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'cloud'] --- import cloudObj from './cloud.devdocs.json'; diff --git a/api_docs/cloud_chat.mdx b/api_docs/cloud_chat.mdx index 9ef4e7334aee7..58149b8a1328b 100644 --- a/api_docs/cloud_chat.mdx +++ b/api_docs/cloud_chat.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/cloudChat title: "cloudChat" image: https://source.unsplash.com/400x175/?github description: API docs for the cloudChat plugin -date: 2023-04-18 +date: 2023-04-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'cloudChat'] --- import cloudChatObj from './cloud_chat.devdocs.json'; diff --git a/api_docs/cloud_data_migration.mdx b/api_docs/cloud_data_migration.mdx index 04c2ffb06b560..cfb98e4160b23 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: 2023-04-18 +date: 2023-04-19 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 5a4286d8b7f58..3ea5f9b64d1cd 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: 2023-04-18 +date: 2023-04-19 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 30e04d9f7e252..19c8ac364cca9 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: 2023-04-18 +date: 2023-04-19 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 7841b498ed166..bb4a888ea42b7 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: 2023-04-18 +date: 2023-04-19 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 9ae830f51efd2..57dff11f3c70c 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: 2023-04-18 +date: 2023-04-19 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 12bf411f06b8f..86adcb31a05df 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: 2023-04-18 +date: 2023-04-19 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 b6800d6e4309f..3288f1d17baab 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: 2023-04-18 +date: 2023-04-19 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 15fc539467e67..e13ca8288243b 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: 2023-04-18 +date: 2023-04-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'customIntegrations'] --- import customIntegrationsObj from './custom_integrations.devdocs.json'; diff --git a/api_docs/dashboard.mdx b/api_docs/dashboard.mdx index 29addf383b79c..1cf38f45d5ed3 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: 2023-04-18 +date: 2023-04-19 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 5ebd9bcf8f56f..30a849b2cd99a 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: 2023-04-18 +date: 2023-04-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'dashboardEnhanced'] --- import dashboardEnhancedObj from './dashboard_enhanced.devdocs.json'; diff --git a/api_docs/data.devdocs.json b/api_docs/data.devdocs.json index c51c052cb9caa..db375b8cd5665 100644 --- a/api_docs/data.devdocs.json +++ b/api_docs/data.devdocs.json @@ -13821,22 +13821,6 @@ "plugin": "apm", "path": "x-pack/plugins/apm/server/routes/data_view/create_static_data_view.ts" }, - { - "plugin": "observability", - "path": "x-pack/plugins/observability/public/utils/observability_data_views/observability_data_views.ts" - }, - { - "plugin": "observability", - "path": "x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/report_definition_field.tsx" - }, - { - "plugin": "observability", - "path": "x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/use_filter_values.ts" - }, - { - "plugin": "observability", - "path": "x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/filter_value_btn.tsx" - }, { "plugin": "exploratoryView", "path": "x-pack/plugins/exploratory_view/public/utils/observability_data_views/observability_data_views.ts" @@ -13853,6 +13837,22 @@ "plugin": "exploratoryView", "path": "x-pack/plugins/exploratory_view/public/components/shared/exploratory_view/series_editor/columns/filter_value_btn.tsx" }, + { + "plugin": "observability", + "path": "x-pack/plugins/observability/public/utils/observability_data_views/observability_data_views.ts" + }, + { + "plugin": "observability", + "path": "x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/report_definition_field.tsx" + }, + { + "plugin": "observability", + "path": "x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/use_filter_values.ts" + }, + { + "plugin": "observability", + "path": "x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/filter_value_btn.tsx" + }, { "plugin": "dataVisualizer", "path": "x-pack/plugins/data_visualizer/public/application/index_data_visualizer/hooks/use_data_visualizer_grid_data.ts" @@ -21451,22 +21451,6 @@ "plugin": "apm", "path": "x-pack/plugins/apm/server/routes/data_view/create_static_data_view.ts" }, - { - "plugin": "observability", - "path": "x-pack/plugins/observability/public/utils/observability_data_views/observability_data_views.ts" - }, - { - "plugin": "observability", - "path": "x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/report_definition_field.tsx" - }, - { - "plugin": "observability", - "path": "x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/use_filter_values.ts" - }, - { - "plugin": "observability", - "path": "x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/filter_value_btn.tsx" - }, { "plugin": "exploratoryView", "path": "x-pack/plugins/exploratory_view/public/utils/observability_data_views/observability_data_views.ts" @@ -21483,6 +21467,22 @@ "plugin": "exploratoryView", "path": "x-pack/plugins/exploratory_view/public/components/shared/exploratory_view/series_editor/columns/filter_value_btn.tsx" }, + { + "plugin": "observability", + "path": "x-pack/plugins/observability/public/utils/observability_data_views/observability_data_views.ts" + }, + { + "plugin": "observability", + "path": "x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/report_definition_field.tsx" + }, + { + "plugin": "observability", + "path": "x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/use_filter_values.ts" + }, + { + "plugin": "observability", + "path": "x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/filter_value_btn.tsx" + }, { "plugin": "dataVisualizer", "path": "x-pack/plugins/data_visualizer/public/application/index_data_visualizer/hooks/use_data_visualizer_grid_data.ts" diff --git a/api_docs/data.mdx b/api_docs/data.mdx index db8730d065cbe..6d7ecdba213c3 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: 2023-04-18 +date: 2023-04-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'data'] --- import dataObj from './data.devdocs.json'; diff --git a/api_docs/data_query.mdx b/api_docs/data_query.mdx index 601219b420239..89f562f1f76aa 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: 2023-04-18 +date: 2023-04-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'data.query'] --- import dataQueryObj from './data_query.devdocs.json'; diff --git a/api_docs/data_search.mdx b/api_docs/data_search.mdx index d1df7dcccc874..e134bb5db798f 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: 2023-04-18 +date: 2023-04-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'data.search'] --- import dataSearchObj from './data_search.devdocs.json'; diff --git a/api_docs/data_view_editor.mdx b/api_docs/data_view_editor.mdx index 5cae668f7fdfa..aaa7085b8fc33 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: 2023-04-18 +date: 2023-04-19 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 157b97bbfe74b..fa2bd9ba306da 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: 2023-04-18 +date: 2023-04-19 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 58241023b4a1d..d70538091beeb 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: 2023-04-18 +date: 2023-04-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'dataViewManagement'] --- import dataViewManagementObj from './data_view_management.devdocs.json'; diff --git a/api_docs/data_views.devdocs.json b/api_docs/data_views.devdocs.json index c4b78d31c56ca..7d419403e8ded 100644 --- a/api_docs/data_views.devdocs.json +++ b/api_docs/data_views.devdocs.json @@ -379,22 +379,6 @@ "plugin": "apm", "path": "x-pack/plugins/apm/server/routes/data_view/create_static_data_view.ts" }, - { - "plugin": "observability", - "path": "x-pack/plugins/observability/public/utils/observability_data_views/observability_data_views.ts" - }, - { - "plugin": "observability", - "path": "x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/report_definition_field.tsx" - }, - { - "plugin": "observability", - "path": "x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/use_filter_values.ts" - }, - { - "plugin": "observability", - "path": "x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/filter_value_btn.tsx" - }, { "plugin": "exploratoryView", "path": "x-pack/plugins/exploratory_view/public/utils/observability_data_views/observability_data_views.ts" @@ -411,6 +395,22 @@ "plugin": "exploratoryView", "path": "x-pack/plugins/exploratory_view/public/components/shared/exploratory_view/series_editor/columns/filter_value_btn.tsx" }, + { + "plugin": "observability", + "path": "x-pack/plugins/observability/public/utils/observability_data_views/observability_data_views.ts" + }, + { + "plugin": "observability", + "path": "x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/report_definition_field.tsx" + }, + { + "plugin": "observability", + "path": "x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/use_filter_values.ts" + }, + { + "plugin": "observability", + "path": "x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/filter_value_btn.tsx" + }, { "plugin": "dataVisualizer", "path": "x-pack/plugins/data_visualizer/public/application/index_data_visualizer/hooks/use_data_visualizer_grid_data.ts" @@ -8636,22 +8636,6 @@ "plugin": "apm", "path": "x-pack/plugins/apm/server/routes/data_view/create_static_data_view.ts" }, - { - "plugin": "observability", - "path": "x-pack/plugins/observability/public/utils/observability_data_views/observability_data_views.ts" - }, - { - "plugin": "observability", - "path": "x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/report_definition_field.tsx" - }, - { - "plugin": "observability", - "path": "x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/use_filter_values.ts" - }, - { - "plugin": "observability", - "path": "x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/filter_value_btn.tsx" - }, { "plugin": "exploratoryView", "path": "x-pack/plugins/exploratory_view/public/utils/observability_data_views/observability_data_views.ts" @@ -8668,6 +8652,22 @@ "plugin": "exploratoryView", "path": "x-pack/plugins/exploratory_view/public/components/shared/exploratory_view/series_editor/columns/filter_value_btn.tsx" }, + { + "plugin": "observability", + "path": "x-pack/plugins/observability/public/utils/observability_data_views/observability_data_views.ts" + }, + { + "plugin": "observability", + "path": "x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/report_definition_field.tsx" + }, + { + "plugin": "observability", + "path": "x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/use_filter_values.ts" + }, + { + "plugin": "observability", + "path": "x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/filter_value_btn.tsx" + }, { "plugin": "dataVisualizer", "path": "x-pack/plugins/data_visualizer/public/application/index_data_visualizer/hooks/use_data_visualizer_grid_data.ts" @@ -15950,22 +15950,6 @@ "plugin": "apm", "path": "x-pack/plugins/apm/server/routes/data_view/create_static_data_view.ts" }, - { - "plugin": "observability", - "path": "x-pack/plugins/observability/public/utils/observability_data_views/observability_data_views.ts" - }, - { - "plugin": "observability", - "path": "x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/report_definition_field.tsx" - }, - { - "plugin": "observability", - "path": "x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/use_filter_values.ts" - }, - { - "plugin": "observability", - "path": "x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/filter_value_btn.tsx" - }, { "plugin": "exploratoryView", "path": "x-pack/plugins/exploratory_view/public/utils/observability_data_views/observability_data_views.ts" @@ -15982,6 +15966,22 @@ "plugin": "exploratoryView", "path": "x-pack/plugins/exploratory_view/public/components/shared/exploratory_view/series_editor/columns/filter_value_btn.tsx" }, + { + "plugin": "observability", + "path": "x-pack/plugins/observability/public/utils/observability_data_views/observability_data_views.ts" + }, + { + "plugin": "observability", + "path": "x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/report_definition_field.tsx" + }, + { + "plugin": "observability", + "path": "x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/use_filter_values.ts" + }, + { + "plugin": "observability", + "path": "x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/filter_value_btn.tsx" + }, { "plugin": "dataVisualizer", "path": "x-pack/plugins/data_visualizer/public/application/index_data_visualizer/hooks/use_data_visualizer_grid_data.ts" diff --git a/api_docs/data_views.mdx b/api_docs/data_views.mdx index c49c1f554773f..c7a761b939ded 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: 2023-04-18 +date: 2023-04-19 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 0d531ef7a344a..c62fc0e0da847 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: 2023-04-18 +date: 2023-04-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'dataVisualizer'] --- import dataVisualizerObj from './data_visualizer.devdocs.json'; diff --git a/api_docs/deprecations_by_api.mdx b/api_docs/deprecations_by_api.mdx index fb0d3e1744199..36b1791990c4b 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: 2023-04-18 +date: 2023-04-19 tags: ['contributor', 'dev', 'apidocs', 'kibana'] --- @@ -17,10 +17,10 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | Deprecated API | Referencing plugin(s) | Remove By | | ---------------|-----------|-----------| | | ml, stackAlerts | - | -| | @kbn/es-query, securitySolution, timelines, lists, threatIntelligence, dataViews, savedObjectsManagement, unifiedSearch, controls, unifiedFieldList, lens, triggersActionsUi, aiops, ml, infra, visTypeTimeseries, apm, observability, exploratoryView, dataVisualizer, fleet, canvas, graph, stackAlerts, synthetics, transform, upgradeAssistant, ux, maps, dataViewManagement, inputControlVis, visDefaultEditor, presentationUtil, visTypeTimelion, visTypeVega, discover, data | - | -| | @kbn/es-query, securitySolution, timelines, lists, threatIntelligence, dataViews, savedObjectsManagement, unifiedSearch, controls, unifiedFieldList, lens, triggersActionsUi, aiops, ml, infra, visTypeTimeseries, apm, observability, exploratoryView, dataVisualizer, fleet, canvas, graph, stackAlerts, synthetics, transform, upgradeAssistant, ux, maps, dataViewManagement, inputControlVis, visDefaultEditor, presentationUtil, visTypeTimelion, visTypeVega, discover, data | - | -| | @kbn/es-query, securitySolution, timelines, lists, threatIntelligence, data, savedObjectsManagement, unifiedSearch, controls, unifiedFieldList, lens, triggersActionsUi, aiops, ml, infra, visTypeTimeseries, apm, observability, exploratoryView, dataVisualizer, fleet, canvas, graph, stackAlerts, synthetics, transform, upgradeAssistant, ux, maps, dataViewManagement, inputControlVis, visDefaultEditor, presentationUtil, visTypeTimelion, visTypeVega, discover | - | -| | home, data, esUiShared, spaces, savedObjectsManagement, observability, exploratoryView, fleet, ml, apm, enterpriseSearch, indexLifecycleManagement, synthetics, upgradeAssistant, ux, kibanaOverview | - | +| | @kbn/es-query, securitySolution, timelines, lists, threatIntelligence, dataViews, savedObjectsManagement, unifiedSearch, controls, unifiedFieldList, lens, triggersActionsUi, aiops, ml, infra, visTypeTimeseries, apm, exploratoryView, observability, dataVisualizer, fleet, canvas, graph, stackAlerts, synthetics, transform, upgradeAssistant, ux, maps, dataViewManagement, inputControlVis, visDefaultEditor, presentationUtil, visTypeTimelion, visTypeVega, discover, data | - | +| | @kbn/es-query, securitySolution, timelines, lists, threatIntelligence, dataViews, savedObjectsManagement, unifiedSearch, controls, unifiedFieldList, lens, triggersActionsUi, aiops, ml, infra, visTypeTimeseries, apm, exploratoryView, observability, dataVisualizer, fleet, canvas, graph, stackAlerts, synthetics, transform, upgradeAssistant, ux, maps, dataViewManagement, inputControlVis, visDefaultEditor, presentationUtil, visTypeTimelion, visTypeVega, discover, data | - | +| | @kbn/es-query, securitySolution, timelines, lists, threatIntelligence, data, savedObjectsManagement, unifiedSearch, controls, unifiedFieldList, lens, triggersActionsUi, aiops, ml, infra, visTypeTimeseries, apm, exploratoryView, observability, dataVisualizer, fleet, canvas, graph, stackAlerts, synthetics, transform, upgradeAssistant, ux, maps, dataViewManagement, inputControlVis, visDefaultEditor, presentationUtil, visTypeTimelion, visTypeVega, discover | - | +| | home, data, esUiShared, spaces, savedObjectsManagement, exploratoryView, fleet, observability, ml, apm, enterpriseSearch, indexLifecycleManagement, synthetics, upgradeAssistant, ux, kibanaOverview | - | | | encryptedSavedObjects, actions, data, ml, logstash, securitySolution, cloudChat | - | | | actions, ml, savedObjectsTagging, enterpriseSearch | - | | | @kbn/core-plugins-browser-internal, @kbn/core-root-browser-internal, dataViews, home, data, savedObjects, unifiedSearch, presentationUtil, visualizations, dashboard, lens, discover, fileUpload, ml, infra, fleet, canvas, dashboardEnhanced, graph, monitoring, synthetics, transform, dataVisualizer, cloudSecurityPosture, securitySolution | - | @@ -83,7 +83,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | @kbn/core-saved-objects-browser-internal, @kbn/core | - | | | @kbn/core-saved-objects-browser-internal, @kbn/core | - | | | @kbn/core-saved-objects-browser-internal | - | -| | @kbn/core-lifecycle-browser, @kbn/core-saved-objects-browser-internal, @kbn/core, savedSearch, visualizations, dashboard, lens, observability, exploratoryView, canvas, transform, @kbn/core-saved-objects-browser-mocks | - | +| | @kbn/core-lifecycle-browser, @kbn/core-saved-objects-browser-internal, @kbn/core, savedSearch, visualizations, dashboard, lens, exploratoryView, observability, canvas, transform, @kbn/core-saved-objects-browser-mocks | - | | | @kbn/core | - | | | @kbn/core | - | | | @kbn/core, lens, savedObjects, visualizations | - | @@ -98,7 +98,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | dataViewManagement, dataViews | - | | | visTypeTimeseries, graph, dataViewManagement, dataViews | - | | | dataViews, dataViewManagement | - | -| | observability, exploratoryView, dataVisualizer, fleet, cloudSecurityPosture, discoverEnhanced, osquery, synthetics | - | +| | exploratoryView, observability, dataVisualizer, fleet, cloudSecurityPosture, discoverEnhanced, osquery, synthetics | - | | | @kbn/core-saved-objects-api-server-internal, fleet | - | | | canvas | - | | | canvas | - | diff --git a/api_docs/deprecations_by_plugin.mdx b/api_docs/deprecations_by_plugin.mdx index e7a872a0aeb7c..747673056fa15 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: 2023-04-18 +date: 2023-04-19 tags: ['contributor', 'dev', 'apidocs', 'kibana'] --- diff --git a/api_docs/deprecations_by_team.mdx b/api_docs/deprecations_by_team.mdx index 8a54dff579e5f..f5763c23b9b37 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: 2023-04-18 +date: 2023-04-19 tags: ['contributor', 'dev', 'apidocs', 'kibana'] --- diff --git a/api_docs/dev_tools.mdx b/api_docs/dev_tools.mdx index c74b6557d7426..bcf610652648e 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: 2023-04-18 +date: 2023-04-19 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 61aa5a32721fb..71254ce683ac4 100644 --- a/api_docs/discover.devdocs.json +++ b/api_docs/discover.devdocs.json @@ -1158,14 +1158,14 @@ "deprecated": true, "trackAdoption": false, "references": [ - { - "plugin": "observability", - "path": "x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_discover_link.tsx" - }, { "plugin": "exploratoryView", "path": "x-pack/plugins/exploratory_view/public/components/shared/exploratory_view/hooks/use_discover_link.tsx" }, + { + "plugin": "observability", + "path": "x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_discover_link.tsx" + }, { "plugin": "dataVisualizer", "path": "x-pack/plugins/data_visualizer/public/application/common/components/results_links/results_links.tsx" diff --git a/api_docs/discover.mdx b/api_docs/discover.mdx index 8148d36e69bd3..5fbb097256f14 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: 2023-04-18 +date: 2023-04-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'discover'] --- import discoverObj from './discover.devdocs.json'; diff --git a/api_docs/discover_enhanced.mdx b/api_docs/discover_enhanced.mdx index 6506ac759fa7a..54a9f327c9f01 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: 2023-04-18 +date: 2023-04-19 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 83f166dcad9b5..846c5ca955d1a 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: 2023-04-18 +date: 2023-04-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'ecsDataQualityDashboard'] --- import ecsDataQualityDashboardObj from './ecs_data_quality_dashboard.devdocs.json'; diff --git a/api_docs/embeddable.mdx b/api_docs/embeddable.mdx index 94a4960c02079..02977e3729e9f 100644 --- a/api_docs/embeddable.mdx +++ b/api_docs/embeddable.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/embeddable title: "embeddable" image: https://source.unsplash.com/400x175/?github description: API docs for the embeddable plugin -date: 2023-04-18 +date: 2023-04-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'embeddable'] --- import embeddableObj from './embeddable.devdocs.json'; diff --git a/api_docs/embeddable_enhanced.mdx b/api_docs/embeddable_enhanced.mdx index 8e86c1bdf95dc..14506c9223e2f 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: 2023-04-18 +date: 2023-04-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'embeddableEnhanced'] --- import embeddableEnhancedObj from './embeddable_enhanced.devdocs.json'; diff --git a/api_docs/encrypted_saved_objects.mdx b/api_docs/encrypted_saved_objects.mdx index 971596d4509a9..6b95e96118bbe 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: 2023-04-18 +date: 2023-04-19 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 76aeb4cff30d7..103ba9e65a7f3 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: 2023-04-18 +date: 2023-04-19 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 9291c86e82e60..e35fd39160a86 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: 2023-04-18 +date: 2023-04-19 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 e9eb2b400ee05..e516bb6a05bb9 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: 2023-04-18 +date: 2023-04-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'eventAnnotation'] --- import eventAnnotationObj from './event_annotation.devdocs.json'; diff --git a/api_docs/event_log.mdx b/api_docs/event_log.mdx index 87cb559074c9a..b56a56228963f 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: 2023-04-18 +date: 2023-04-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'eventLog'] --- import eventLogObj from './event_log.devdocs.json'; diff --git a/api_docs/exploratory_view.devdocs.json b/api_docs/exploratory_view.devdocs.json index e3a6af7a52ef5..8d7c0c15654a4 100644 --- a/api_docs/exploratory_view.devdocs.json +++ b/api_docs/exploratory_view.devdocs.json @@ -1884,66 +1884,6 @@ "deprecated": false, "trackAdoption": false }, - { - "parentPluginId": "exploratoryView", - "id": "def-public.ExploratoryViewPublicPluginsSetup.observability", - "type": "Object", - "tags": [], - "label": "observability", - "description": [], - "signature": [ - "{ dashboard: { register: ({ appName, fetchData, hasData, }: { appName: T; } & ", - { - "pluginId": "observability", - "scope": "public", - "docId": "kibObservabilityPluginApi", - "section": "def-public.DataHandler", - "text": "DataHandler" - }, - ") => void; }; observabilityRuleTypeRegistry: { register: (type: ", - { - "pluginId": "observability", - "scope": "public", - "docId": "kibObservabilityPluginApi", - "section": "def-public.ObservabilityRuleTypeModel", - "text": "ObservabilityRuleTypeModel" - }, - ") => void; getFormatter: (typeId: string) => ", - { - "pluginId": "observability", - "scope": "public", - "docId": "kibObservabilityPluginApi", - "section": "def-public.ObservabilityRuleTypeFormatter", - "text": "ObservabilityRuleTypeFormatter" - }, - " | undefined; list: () => string[]; }; navigation: { registerSections: (sections$: ", - "Observable", - "<", - { - "pluginId": "observability", - "scope": "public", - "docId": "kibObservabilityPluginApi", - "section": "def-public.NavigationSection", - "text": "NavigationSection" - }, - "[]>) => void; }; useRulesLink: (options?: ", - "Options", - ") => ", - "LinkProps", - "; }" - ], - "path": "x-pack/plugins/exploratory_view/public/plugin.ts", - "deprecated": false, - "trackAdoption": false - }, { "parentPluginId": "exploratoryView", "id": "def-public.ExploratoryViewPublicPluginsSetup.share", @@ -2252,63 +2192,33 @@ }, { "parentPluginId": "exploratoryView", - "id": "def-public.ExploratoryViewPublicPluginsStart.observability", + "id": "def-public.ExploratoryViewPublicPluginsStart.observabilityShared", "type": "Object", "tags": [], - "label": "observability", + "label": "observabilityShared", "description": [], "signature": [ - "{ observabilityRuleTypeRegistry: { register: (type: ", - { - "pluginId": "observability", - "scope": "public", - "docId": "kibObservabilityPluginApi", - "section": "def-public.ObservabilityRuleTypeModel", - "text": "ObservabilityRuleTypeModel" - }, - ") => void; getFormatter: (typeId: string) => ", - { - "pluginId": "observability", - "scope": "public", - "docId": "kibObservabilityPluginApi", - "section": "def-public.ObservabilityRuleTypeFormatter", - "text": "ObservabilityRuleTypeFormatter" - }, - " | undefined; list: () => string[]; }; navigation: { PageTemplate: (pageTemplateProps: ", + "{ navigation: { PageTemplate: (pageTemplateProps: ", "WrappedPageTemplateProps", - ") => JSX.Element; }; createExploratoryViewUrl: ({ reportType, allSeries }: { reportType: ", - "ReportViewType", - "; allSeries: ", - { - "pluginId": "observability", - "scope": "public", - "docId": "kibObservabilityPluginApi", - "section": "def-public.AllSeries", - "text": "AllSeries" - }, - "; }, baseHref?: string, appId?: string) => string; getAppDataView: (appId: ", - "AppDataType", - ", indexPattern?: string | undefined) => Promise<", + ") => JSX.Element; }; updateGlobalNavigation: ({ capabilities, deepLinks, updater$, }: { capabilities: Readonly<{ [x: string]: Readonly<{ [x: string]: boolean | Readonly<{ [x: string]: boolean; }>; }>; navLinks: Readonly<{ [x: string]: boolean; }>; management: Readonly<{ [x: string]: Readonly<{ [x: string]: boolean; }>; }>; catalogue: Readonly<{ [x: string]: boolean; }>; }>; deepLinks: ", { - "pluginId": "dataViews", + "pluginId": "@kbn/core-application-browser", "scope": "common", - "docId": "kibDataViewsPluginApi", - "section": "def-common.DataView", - "text": "DataView" + "docId": "kibKbnCoreApplicationBrowserPluginApi", + "section": "def-common.AppDeepLink", + "text": "AppDeepLink" }, - " | null | undefined>; ExploratoryViewEmbeddable: (props: ", + "[]; updater$: ", + "Subject", + "<", { - "pluginId": "observability", - "scope": "public", - "docId": "kibObservabilityPluginApi", - "section": "def-public.ExploratoryEmbeddableProps", - "text": "ExploratoryEmbeddableProps" + "pluginId": "@kbn/core-application-browser", + "scope": "common", + "docId": "kibKbnCoreApplicationBrowserPluginApi", + "section": "def-common.AppUpdater", + "text": "AppUpdater" }, - ") => JSX.Element | null; useRulesLink: (options?: ", - "Options", - ") => ", - "LinkProps", - "; }" + ">; }) => void; }" ], "path": "x-pack/plugins/exploratory_view/public/plugin.ts", "deprecated": false, diff --git a/api_docs/exploratory_view.mdx b/api_docs/exploratory_view.mdx index a59314c8667fd..849453338e010 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: 2023-04-18 +date: 2023-04-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'exploratoryView'] --- import exploratoryViewObj from './exploratory_view.devdocs.json'; @@ -21,7 +21,7 @@ Contact [@elastic/uptime](https://github.com/orgs/elastic/teams/uptime) for ques | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 352 | 4 | 349 | 22 | +| 351 | 4 | 348 | 22 | ## Client diff --git a/api_docs/expression_error.mdx b/api_docs/expression_error.mdx index 058907c640274..45f3eb5cbf61f 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: 2023-04-18 +date: 2023-04-19 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 d005bd65c76ee..c0bccdf35a0fe 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: 2023-04-18 +date: 2023-04-19 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 631985dfa297a..d934e55b7f2c6 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: 2023-04-18 +date: 2023-04-19 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 58e8ab5d87739..f64aa1581a3c5 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: 2023-04-18 +date: 2023-04-19 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 4cf6d1dd4a9de..22e421096034b 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: 2023-04-18 +date: 2023-04-19 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 3d65bc107e617..b5e8ad43772ee 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: 2023-04-18 +date: 2023-04-19 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 75517415267e0..b83a4acedad45 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: 2023-04-18 +date: 2023-04-19 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 42a25678e777d..613f702138868 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: 2023-04-18 +date: 2023-04-19 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 9bafa35f80794..3f5cb2ac0f28b 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: 2023-04-18 +date: 2023-04-19 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 e0cdc69d636d9..b3235f04d4e6d 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: 2023-04-18 +date: 2023-04-19 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 62aa475f1156c..dbb0961004d02 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: 2023-04-18 +date: 2023-04-19 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 cd5d90a3e6cf3..f10426042f970 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: 2023-04-18 +date: 2023-04-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionTagcloud'] --- import expressionTagcloudObj from './expression_tagcloud.devdocs.json'; diff --git a/api_docs/expression_x_y.mdx b/api_docs/expression_x_y.mdx index e338d57e6f0f1..1fb52cf6c77eb 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: 2023-04-18 +date: 2023-04-19 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 c187ed5a0c268..1bd11ec87fad7 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: 2023-04-18 +date: 2023-04-19 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 268a930b7c64d..21867f9dcb7c3 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: 2023-04-18 +date: 2023-04-19 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 c2955918d2cbb..03df4c3fcfb44 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: 2023-04-18 +date: 2023-04-19 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 951a040359fc0..cef26e17b6d33 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: 2023-04-18 +date: 2023-04-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'fileUpload'] --- import fileUploadObj from './file_upload.devdocs.json'; diff --git a/api_docs/files.devdocs.json b/api_docs/files.devdocs.json index 7f34684f9d5ad..284fc310bee93 100644 --- a/api_docs/files.devdocs.json +++ b/api_docs/files.devdocs.json @@ -553,6 +553,30 @@ "deprecated": false, "trackAdoption": true, "references": [ + { + "plugin": "cases", + "path": "x-pack/plugins/cases/public/application.tsx" + }, + { + "plugin": "cases", + "path": "x-pack/plugins/cases/public/plugin.ts" + }, + { + "plugin": "cases", + "path": "x-pack/plugins/cases/public/plugin.ts" + }, + { + "plugin": "cases", + "path": "x-pack/plugins/cases/public/plugin.ts" + }, + { + "plugin": "cases", + "path": "x-pack/plugins/cases/public/plugin.ts" + }, + { + "plugin": "cases", + "path": "x-pack/plugins/cases/public/plugin.ts" + }, { "plugin": "imageEmbeddable", "path": "src/plugins/image_embeddable/public/plugin.ts" diff --git a/api_docs/files.mdx b/api_docs/files.mdx index 5b9d6f3da2059..9a81f60a70ba5 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: 2023-04-18 +date: 2023-04-19 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 46c3e88281155..e25bf2cf7fee0 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: 2023-04-18 +date: 2023-04-19 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 942a2edd93055..c8b3355a17f38 100644 --- a/api_docs/fleet.devdocs.json +++ b/api_docs/fleet.devdocs.json @@ -20188,7 +20188,7 @@ "label": "data_stream", "description": [], "signature": [ - "{ dataset: string; type: string; elasticsearch?: { privileges?: { indices?: string[] | undefined; } | undefined; index_mode?: string | undefined; source_mode?: string | undefined; } | undefined; }" + "{ dataset: string; type: string; elasticsearch?: { dynamic_dataset?: boolean | undefined; dynamic_namespace?: boolean | undefined; privileges?: { indices?: string[] | undefined; } | undefined; index_mode?: string | undefined; source_mode?: string | undefined; } | undefined; }" ], "path": "x-pack/plugins/fleet/common/types/models/package_policy.ts", "deprecated": false, @@ -21511,6 +21511,34 @@ "path": "x-pack/plugins/fleet/common/types/models/epm.ts", "deprecated": false, "trackAdoption": false + }, + { + "parentPluginId": "fleet", + "id": "def-common.RegistryElasticsearch.dynamic_dataset", + "type": "CompoundType", + "tags": [], + "label": "dynamic_dataset", + "description": [], + "signature": [ + "boolean | undefined" + ], + "path": "x-pack/plugins/fleet/common/types/models/epm.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "fleet", + "id": "def-common.RegistryElasticsearch.dynamic_namespace", + "type": "CompoundType", + "tags": [], + "label": "dynamic_namespace", + "description": [], + "signature": [ + "boolean | undefined" + ], + "path": "x-pack/plugins/fleet/common/types/models/epm.ts", + "deprecated": false, + "trackAdoption": false } ], "initialIsOpen": false @@ -25192,6 +25220,21 @@ ], "initialIsOpen": false }, + { + "parentPluginId": "fleet", + "id": "def-common.DASHBOARD_LOCATORS_IDS", + "type": "Object", + "tags": [], + "label": "DASHBOARD_LOCATORS_IDS", + "description": [], + "signature": [ + "{ readonly ELASTIC_AGENT_OVERVIEW: \"elastic_agent-a148dc70-6b3c-11ed-98de-67bdecd21824\"; readonly ELASTIC_AGENT_AGENT_INFO: \"elastic_agent-0600ffa0-6b5e-11ed-98de-67bdecd21824\"; readonly ELASTIC_AGENT_AGENT_METRICS: \"elastic_agent-f47f18cc-9c7d-4278-b2ea-a6dee816d395\"; readonly ELASTIC_AGENT_INTEGRATIONS: \"elastic_agent-1a4e7280-6b5e-11ed-98de-67bdecd21824\"; }" + ], + "path": "x-pack/plugins/fleet/common/constants/locators.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, { "parentPluginId": "fleet", "id": "def-common.ENDPOINT_PRIVILEGES", diff --git a/api_docs/fleet.mdx b/api_docs/fleet.mdx index 624c2d71bce89..765d621169bd4 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: 2023-04-18 +date: 2023-04-19 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 | |-------------------|-----------|------------------------|-----------------| -| 1098 | 3 | 993 | 27 | +| 1101 | 3 | 996 | 27 | ## Client diff --git a/api_docs/global_search.mdx b/api_docs/global_search.mdx index 2aef1eb1e7d44..0b55b1f3091c7 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: 2023-04-18 +date: 2023-04-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'globalSearch'] --- import globalSearchObj from './global_search.devdocs.json'; diff --git a/api_docs/guided_onboarding.mdx b/api_docs/guided_onboarding.mdx index 600524142c9e8..4ae55190ce38e 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: 2023-04-18 +date: 2023-04-19 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 18bc7c3f5e0f6..bc635db196102 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: 2023-04-18 +date: 2023-04-19 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 77912afc2126a..dda6824fbe32f 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: 2023-04-18 +date: 2023-04-19 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 577a0ee22da7a..d7eb6a2fdad96 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: 2023-04-18 +date: 2023-04-19 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 22fa699bdcd19..089c8288851a9 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: 2023-04-18 +date: 2023-04-19 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 a9c227d21af98..9bd4b9bb764dd 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: 2023-04-18 +date: 2023-04-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'infra'] --- import infraObj from './infra.devdocs.json'; diff --git a/api_docs/inspector.mdx b/api_docs/inspector.mdx index 6ae66934a55d1..42ee86d841d19 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: 2023-04-18 +date: 2023-04-19 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 2ab5d39b10e30..2759a7bf7be21 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: 2023-04-18 +date: 2023-04-19 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 67ee43007fdc9..e4d2390ade27b 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: 2023-04-18 +date: 2023-04-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ace'] --- import kbnAceObj from './kbn_ace.devdocs.json'; diff --git a/api_docs/kbn_aiops_components.mdx b/api_docs/kbn_aiops_components.mdx index 3803893785185..f4e8bc70d1518 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: 2023-04-18 +date: 2023-04-19 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 c71c2df062db1..c73719f74b85f 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: 2023-04-18 +date: 2023-04-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/aiops-utils'] --- import kbnAiopsUtilsObj from './kbn_aiops_utils.devdocs.json'; diff --git a/api_docs/kbn_alerting_state_types.mdx b/api_docs/kbn_alerting_state_types.mdx index dc2bd696405a8..a7333dbad536a 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: 2023-04-18 +date: 2023-04-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/alerting-state-types'] --- import kbnAlertingStateTypesObj from './kbn_alerting_state_types.devdocs.json'; diff --git a/api_docs/kbn_alerts.mdx b/api_docs/kbn_alerts.mdx index 5610770bef960..c15074ba938bf 100644 --- a/api_docs/kbn_alerts.mdx +++ b/api_docs/kbn_alerts.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-alerts title: "@kbn/alerts" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/alerts plugin -date: 2023-04-18 +date: 2023-04-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/alerts'] --- import kbnAlertsObj from './kbn_alerts.devdocs.json'; diff --git a/api_docs/kbn_alerts_as_data_utils.mdx b/api_docs/kbn_alerts_as_data_utils.mdx index dbc0fccbc39ff..41cbc25fe19e0 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: 2023-04-18 +date: 2023-04-19 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 1f152e91c60e2..f0910f5fc35a6 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: 2023-04-18 +date: 2023-04-19 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 a5b9c7ed5ca56..ac5cff2d62a3b 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: 2023-04-18 +date: 2023-04-19 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 26bc13c4db5fc..8f9f6554e4150 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: 2023-04-18 +date: 2023-04-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/analytics-client'] --- import kbnAnalyticsClientObj from './kbn_analytics_client.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 242a6221a751d..f4161c633a6c0 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: 2023-04-18 +date: 2023-04-19 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 74799d1f0d30f..974386ed1b8e4 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: 2023-04-18 +date: 2023-04-19 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 fa20d286793fb..5fc1dc2f5919a 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: 2023-04-18 +date: 2023-04-19 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 c46bd2626a0d7..79b0ae9090ca8 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: 2023-04-18 +date: 2023-04-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/analytics-shippers-fullstory'] --- import kbnAnalyticsShippersFullstoryObj from './kbn_analytics_shippers_fullstory.devdocs.json'; diff --git a/api_docs/kbn_analytics_shippers_gainsight.mdx b/api_docs/kbn_analytics_shippers_gainsight.mdx index 2f789d03d3064..9867bcd91ff48 100644 --- a/api_docs/kbn_analytics_shippers_gainsight.mdx +++ b/api_docs/kbn_analytics_shippers_gainsight.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-analytics-shippers-gainsight title: "@kbn/analytics-shippers-gainsight" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/analytics-shippers-gainsight plugin -date: 2023-04-18 +date: 2023-04-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/analytics-shippers-gainsight'] --- import kbnAnalyticsShippersGainsightObj from './kbn_analytics_shippers_gainsight.devdocs.json'; diff --git a/api_docs/kbn_apm_config_loader.mdx b/api_docs/kbn_apm_config_loader.mdx index 646095277ae18..eeda8b4391283 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: 2023-04-18 +date: 2023-04-19 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 440bc59095fb9..1f768ab557e33 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: 2023-04-18 +date: 2023-04-19 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 fb4fc2d58e1d7..ef2fe4abe9747 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: 2023-04-18 +date: 2023-04-19 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 1a24a499081d6..2d1f6245bdf03 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: 2023-04-18 +date: 2023-04-19 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 cfc894c0aed33..a43abcdbeded7 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: 2023-04-18 +date: 2023-04-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/axe-config'] --- import kbnAxeConfigObj from './kbn_axe_config.devdocs.json'; diff --git a/api_docs/kbn_cases_components.mdx b/api_docs/kbn_cases_components.mdx index 20950b0b5504b..802c542ff1d9d 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: 2023-04-18 +date: 2023-04-19 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 39e5c1f3876ed..d14cdd10cb5cd 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: 2023-04-18 +date: 2023-04-19 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 4b9682a493355..c2d0094e972d7 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: 2023-04-18 +date: 2023-04-19 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 6ee7741aac660..1d3086cc5fa5a 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: 2023-04-18 +date: 2023-04-19 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 7535c8f6802bb..d4fe8d7090728 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: 2023-04-18 +date: 2023-04-19 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 e38354234f2a1..eb4a00a5ef79f 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: 2023-04-18 +date: 2023-04-19 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 436a35b857d97..abe5feff261ca 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: 2023-04-18 +date: 2023-04-19 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 72b4147358ebf..273214fcd1e9c 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: 2023-04-18 +date: 2023-04-19 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 a438c3ac72212..a2ca9df0c6105 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: 2023-04-18 +date: 2023-04-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/code-editor'] --- import kbnCodeEditorObj from './kbn_code_editor.devdocs.json'; diff --git a/api_docs/kbn_code_editor_mocks.mdx b/api_docs/kbn_code_editor_mocks.mdx index 6faa2e668df36..5e946e9d7a68b 100644 --- a/api_docs/kbn_code_editor_mocks.mdx +++ b/api_docs/kbn_code_editor_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-code-editor-mocks title: "@kbn/code-editor-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/code-editor-mocks plugin -date: 2023-04-18 +date: 2023-04-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/code-editor-mocks'] --- import kbnCodeEditorMocksObj from './kbn_code_editor_mocks.devdocs.json'; diff --git a/api_docs/kbn_coloring.mdx b/api_docs/kbn_coloring.mdx index d0f93bffc6bcd..119058d250f7f 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: 2023-04-18 +date: 2023-04-19 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 0c1bc4ec3037a..5056c3647e515 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: 2023-04-18 +date: 2023-04-19 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 654be1f4dc6f6..4dec3e2cd3f5d 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: 2023-04-18 +date: 2023-04-19 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 e33c9b85eb791..368b3abc63350 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: 2023-04-18 +date: 2023-04-19 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 8080fd41a6217..b0ab27ae78600 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: 2023-04-18 +date: 2023-04-19 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_table_list.mdx b/api_docs/kbn_content_management_table_list.mdx index 8eb1ca361def0..3ecf51fd33244 100644 --- a/api_docs/kbn_content_management_table_list.mdx +++ b/api_docs/kbn_content_management_table_list.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-content-management-table-list title: "@kbn/content-management-table-list" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/content-management-table-list plugin -date: 2023-04-18 +date: 2023-04-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/content-management-table-list'] --- import kbnContentManagementTableListObj from './kbn_content_management_table_list.devdocs.json'; diff --git a/api_docs/kbn_core_analytics_browser.mdx b/api_docs/kbn_core_analytics_browser.mdx index 7e6d452bd3dc8..f30b5bae17d46 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: 2023-04-18 +date: 2023-04-19 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 f88d22357c501..28e92d54f0efb 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: 2023-04-18 +date: 2023-04-19 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 9ed87daf3b53d..67b75e7d10057 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: 2023-04-18 +date: 2023-04-19 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 3ee3bc858e18f..8189da0441012 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: 2023-04-18 +date: 2023-04-19 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 944c7c7e5e095..58893459be1ef 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: 2023-04-18 +date: 2023-04-19 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 e8b3ed64abd21..aca802c739677 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: 2023-04-18 +date: 2023-04-19 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 73a96764f0a20..d8ff0064c33a3 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: 2023-04-18 +date: 2023-04-19 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 65e5281d02558..48946a11b73d9 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: 2023-04-18 +date: 2023-04-19 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 e0544bfb741b8..b7d1bd73c2ad6 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: 2023-04-18 +date: 2023-04-19 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 abdcdcab8b3da..7c641e8a33eab 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: 2023-04-18 +date: 2023-04-19 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 5a7df9b5c701d..67c181dbe353c 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: 2023-04-18 +date: 2023-04-19 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 4f7120a3dcfe7..3e937416907f7 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: 2023-04-18 +date: 2023-04-19 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 04b4becdc9ed7..0fb69311a41d3 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: 2023-04-18 +date: 2023-04-19 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 7edc1c2bf0108..723403c04a56b 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: 2023-04-18 +date: 2023-04-19 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 31241bd3ebab8..38c200dd4fc4e 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: 2023-04-18 +date: 2023-04-19 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 c77e0e47adc5e..2822cebc8f2b6 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: 2023-04-18 +date: 2023-04-19 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 415fe2fed1189..85f67738b5127 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: 2023-04-18 +date: 2023-04-19 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 b98ffe805e860..a195e14f8eb08 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: 2023-04-18 +date: 2023-04-19 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 4777bb07d8cb8..dee08ccafaa3b 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: 2023-04-18 +date: 2023-04-19 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 4f83cbf656d29..0500817634ff9 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: 2023-04-18 +date: 2023-04-19 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 b29b724f4ff08..69b03a9701053 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: 2023-04-18 +date: 2023-04-19 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 4f4b841e7c133..5d39c7f82fc91 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: 2023-04-18 +date: 2023-04-19 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 78e1da6e97f8d..a7455a3d124fe 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: 2023-04-18 +date: 2023-04-19 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 c387e23236985..f148b51bf4f79 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: 2023-04-18 +date: 2023-04-19 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 c85986cb5d39c..a8931e398dba4 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: 2023-04-18 +date: 2023-04-19 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 6cd7e8cdd4dd8..7e42f2af72f22 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: 2023-04-18 +date: 2023-04-19 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 5828e4b3e796f..6e16eb883e32b 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: 2023-04-18 +date: 2023-04-19 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 024319834e756..fd22accfa2fd9 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: 2023-04-18 +date: 2023-04-19 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 ffb1c6e6442ca..69a6b20efc1f4 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: 2023-04-18 +date: 2023-04-19 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 7bbf0fae324be..096bf2db5c03c 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: 2023-04-18 +date: 2023-04-19 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 417f2f0c4d598..06faf0dd23001 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: 2023-04-18 +date: 2023-04-19 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 bf994f0f500a0..903e4f01a2028 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: 2023-04-18 +date: 2023-04-19 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 8c304566110e9..4a90ce31147d8 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: 2023-04-18 +date: 2023-04-19 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 5c4bc6c627f55..f0c9aff947736 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: 2023-04-18 +date: 2023-04-19 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 e63ecc8a6ff52..11e72887b3c3c 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: 2023-04-18 +date: 2023-04-19 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 387f9924f6896..832893355aa18 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: 2023-04-18 +date: 2023-04-19 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 353c19d437d5e..e13e0e5865ab0 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: 2023-04-18 +date: 2023-04-19 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 6d146e44d7510..5155aeece5ccc 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: 2023-04-18 +date: 2023-04-19 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 ebfad96952499..e75e82dadd7b5 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: 2023-04-18 +date: 2023-04-19 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 9dab9c9bbbcad..270978b9a289b 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: 2023-04-18 +date: 2023-04-19 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 32dddf219dbdd..eb15cc5893e3a 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: 2023-04-18 +date: 2023-04-19 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 57321c54b22b9..38a2642f3ca7f 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: 2023-04-18 +date: 2023-04-19 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 e91f768a9b11d..c06dbb2c4cabf 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: 2023-04-18 +date: 2023-04-19 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 6b9414e0651b1..855c92977a56a 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: 2023-04-18 +date: 2023-04-19 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 145c7f1af2727..72e01b8ab8af3 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: 2023-04-18 +date: 2023-04-19 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 b545705cc9937..34f856af0a5b0 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: 2023-04-18 +date: 2023-04-19 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 b0c53699fdaf4..6525743a6df56 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: 2023-04-18 +date: 2023-04-19 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 bf60d16f51e7e..e947629304916 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: 2023-04-18 +date: 2023-04-19 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 53bc31f39d5ac..6dbf86aff88ba 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: 2023-04-18 +date: 2023-04-19 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 f172396845792..3d31ac3c5a93f 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: 2023-04-18 +date: 2023-04-19 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 8e788545d793c..14668296fd09b 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: 2023-04-18 +date: 2023-04-19 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 b06caeb0c3c3d..bac6725f95815 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: 2023-04-18 +date: 2023-04-19 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 cd8d797b06c1b..3a75e07c2d942 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: 2023-04-18 +date: 2023-04-19 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 a57c274f69d25..d647820be221a 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: 2023-04-18 +date: 2023-04-19 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 0eba46d8c5a93..e6599896f5008 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: 2023-04-18 +date: 2023-04-19 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 aead6167b10ea..78898bec9c262 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: 2023-04-18 +date: 2023-04-19 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 a980e30066c77..2cdc139e6a958 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: 2023-04-18 +date: 2023-04-19 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 fa51d22025e81..71ad5cf9a4746 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: 2023-04-18 +date: 2023-04-19 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 b74fbf8ec1b93..f46e20977cc14 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: 2023-04-18 +date: 2023-04-19 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 0bc4d6205ab8a..2c2a798a89c6c 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: 2023-04-18 +date: 2023-04-19 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 7e3f26557d9db..dc84030777a3f 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: 2023-04-18 +date: 2023-04-19 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 01974c526d562..f46f80f157990 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: 2023-04-18 +date: 2023-04-19 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 1ef26910b0abc..2bcecde44aae2 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: 2023-04-18 +date: 2023-04-19 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 b7a7d506ca531..728e77b24337f 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: 2023-04-18 +date: 2023-04-19 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 35b029295c88a..15d62f1afa04a 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: 2023-04-18 +date: 2023-04-19 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 0064704c524f7..753a64b907940 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: 2023-04-18 +date: 2023-04-19 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 ac43fb7e82ffc..af0d51dc7dcb2 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: 2023-04-18 +date: 2023-04-19 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 3acf14a21fdfb..b18c188eb87ba 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: 2023-04-18 +date: 2023-04-19 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 8cb95d181376b..ce5cbd31fa38c 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: 2023-04-18 +date: 2023-04-19 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.mdx b/api_docs/kbn_core_http_server.mdx index b9282cf2c5cc4..edea32f36d0a3 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: 2023-04-18 +date: 2023-04-19 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 6b9719e5fe08e..7f9c6838f648f 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: 2023-04-18 +date: 2023-04-19 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 c9abe3268ff3e..8f98589ac9bb2 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: 2023-04-18 +date: 2023-04-19 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 bf69c5ce0ced0..48717a4928ceb 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: 2023-04-18 +date: 2023-04-19 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 ced2f59a04bc6..91c88b93b9899 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: 2023-04-18 +date: 2023-04-19 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 3fb1ad0fbd480..dfd6759f82c3e 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: 2023-04-18 +date: 2023-04-19 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 b15078d9792e1..2fb875c29aedb 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: 2023-04-18 +date: 2023-04-19 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 25a5f3a2ca0a2..d7374f1d2ea55 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: 2023-04-18 +date: 2023-04-19 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 fc7ff72335b58..106e6b27d00fb 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: 2023-04-18 +date: 2023-04-19 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 1ee295744c671..6c8f8c341950d 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: 2023-04-18 +date: 2023-04-19 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 6cfe8aed78427..b753450d5d6ce 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: 2023-04-18 +date: 2023-04-19 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 1921f2d7981df..3a36ca3942e7a 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: 2023-04-18 +date: 2023-04-19 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 99b286b50fd86..475eb48ccbccb 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: 2023-04-18 +date: 2023-04-19 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 2899d24f85f98..0ef4bf8d43a32 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: 2023-04-18 +date: 2023-04-19 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 d45cec86f6c29..3649461b7c5a6 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: 2023-04-18 +date: 2023-04-19 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 1ce36deed650a..052bf0ba74258 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: 2023-04-18 +date: 2023-04-19 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 0440a99935453..07e96d1422f6a 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: 2023-04-18 +date: 2023-04-19 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 e61272d2b8f3c..ce0ac13ded0d6 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: 2023-04-18 +date: 2023-04-19 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 d441fba7df7e0..55bed0b817434 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: 2023-04-18 +date: 2023-04-19 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 3d9fb1ec20a50..1425aa6b5cd5d 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: 2023-04-18 +date: 2023-04-19 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 b1d5addb6e328..6e0c42f2ccb83 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: 2023-04-18 +date: 2023-04-19 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 6a39200864922..5cac745fb95e6 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: 2023-04-18 +date: 2023-04-19 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 9fc468eed40ba..8b0e0e1d6c1ca 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: 2023-04-18 +date: 2023-04-19 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 21fb4380378b4..1c31017866648 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: 2023-04-18 +date: 2023-04-19 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 c8492f5909518..768a293980d04 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: 2023-04-18 +date: 2023-04-19 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 22cb2ce1146b2..48dc25bf2cf39 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: 2023-04-18 +date: 2023-04-19 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 3a1fd042efd8b..9fb1afd0b61fb 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: 2023-04-18 +date: 2023-04-19 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 86ab31ae15db5..55ae8a712881b 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: 2023-04-18 +date: 2023-04-19 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 ebf5d96d68d32..63681b4273d7a 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: 2023-04-18 +date: 2023-04-19 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 ca32afc17128c..20ab26c0bc749 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: 2023-04-18 +date: 2023-04-19 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 c1b9cd2cb048d..d824c45cc7aac 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: 2023-04-18 +date: 2023-04-19 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 c19f99c51a76b..82241455ede13 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: 2023-04-18 +date: 2023-04-19 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 85299008590c8..bd6e525f0fb93 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: 2023-04-18 +date: 2023-04-19 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 1df34a6718024..023c363df1f90 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: 2023-04-18 +date: 2023-04-19 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 7f7318e1e7bbe..82863e8d7c246 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: 2023-04-18 +date: 2023-04-19 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 3787319cb72ac..79c15731be923 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: 2023-04-18 +date: 2023-04-19 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 d0cc0e312d275..98c3abd58f3a3 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: 2023-04-18 +date: 2023-04-19 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_server.mdx b/api_docs/kbn_core_plugins_server.mdx index 4accc1effb67b..186767ae5b0ca 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: 2023-04-18 +date: 2023-04-19 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 dc7ada51253ea..5c1c91d0d9180 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: 2023-04-18 +date: 2023-04-19 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 3a0e3a2d07668..3fccd50f19133 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: 2023-04-18 +date: 2023-04-19 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 f07d5958f5ee7..5fa6b166396df 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: 2023-04-18 +date: 2023-04-19 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 33fd78a2fd135..91c5d2eaf7349 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: 2023-04-18 +date: 2023-04-19 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 6d6fa4111b0d4..1181c8e829960 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: 2023-04-18 +date: 2023-04-19 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 b2fa6c7df66e6..541950376bd3a 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: 2023-04-18 +date: 2023-04-19 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 e51f7a895dd7d..515e475f7ea60 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: 2023-04-18 +date: 2023-04-19 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 c933f2d88e056..bbb5648505e86 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: 2023-04-18 +date: 2023-04-19 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 bc6057ff8197f..80723a578e801 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: 2023-04-18 +date: 2023-04-19 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_internal.mdx b/api_docs/kbn_core_saved_objects_api_server_internal.mdx index 2a1c53c28631e..364e3d6c2e0d0 100644 --- a/api_docs/kbn_core_saved_objects_api_server_internal.mdx +++ b/api_docs/kbn_core_saved_objects_api_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-api-server-internal title: "@kbn/core-saved-objects-api-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-api-server-internal plugin -date: 2023-04-18 +date: 2023-04-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-api-server-internal'] --- import kbnCoreSavedObjectsApiServerInternalObj from './kbn_core_saved_objects_api_server_internal.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 adef2a71d375a..9b2c6c049793a 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: 2023-04-18 +date: 2023-04-19 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 20ccc6c58b0e3..2dbf03e97e737 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: 2023-04-18 +date: 2023-04-19 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 6faaed1ffe963..784bad9b0340a 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: 2023-04-18 +date: 2023-04-19 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.devdocs.json b/api_docs/kbn_core_saved_objects_browser.devdocs.json index b7d377b2da608..1a9ced3fa1030 100644 --- a/api_docs/kbn_core_saved_objects_browser.devdocs.json +++ b/api_docs/kbn_core_saved_objects_browser.devdocs.json @@ -89,14 +89,6 @@ "plugin": "lens", "path": "x-pack/plugins/lens/public/app_plugin/types.ts" }, - { - "plugin": "observability", - "path": "x-pack/plugins/observability/public/application/types.ts" - }, - { - "plugin": "observability", - "path": "x-pack/plugins/observability/public/application/types.ts" - }, { "plugin": "exploratoryView", "path": "x-pack/plugins/exploratory_view/public/application/types.ts" @@ -105,6 +97,14 @@ "plugin": "exploratoryView", "path": "x-pack/plugins/exploratory_view/public/application/types.ts" }, + { + "plugin": "observability", + "path": "x-pack/plugins/observability/public/application/types.ts" + }, + { + "plugin": "observability", + "path": "x-pack/plugins/observability/public/application/types.ts" + }, { "plugin": "canvas", "path": "x-pack/plugins/canvas/public/services/platform.ts" diff --git a/api_docs/kbn_core_saved_objects_browser.mdx b/api_docs/kbn_core_saved_objects_browser.mdx index 79d10e9f12bb2..ac17cc0f50ddc 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: 2023-04-18 +date: 2023-04-19 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 2c587119409cf..35eb402d34bad 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: 2023-04-18 +date: 2023-04-19 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 97d7ea86b6137..655b42e41a076 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: 2023-04-18 +date: 2023-04-19 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 3783c6ead532a..6aa791f02b305 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: 2023-04-18 +date: 2023-04-19 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 a75d12fdd58ca..ef9c2eee7dda8 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: 2023-04-18 +date: 2023-04-19 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 d8b3a4d217c3a..9c2ac13d123a4 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: 2023-04-18 +date: 2023-04-19 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 9eb556d39981e..9211f55ae686f 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: 2023-04-18 +date: 2023-04-19 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 56b4cfe2ecd4c..6ab358dddacee 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: 2023-04-18 +date: 2023-04-19 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 a48fa96e1fbb5..0ab1681109f8b 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: 2023-04-18 +date: 2023-04-19 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 133a501912d9c..2bfef7896ed9e 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: 2023-04-18 +date: 2023-04-19 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 e8baf46b04b57..5969fa3fec42b 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: 2023-04-18 +date: 2023-04-19 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 7be39b3ddfd27..f03b52f175605 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: 2023-04-18 +date: 2023-04-19 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 b43a1574290d7..98e14db6a9771 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: 2023-04-18 +date: 2023-04-19 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 ff765eefc4403..138054ebf6074 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: 2023-04-18 +date: 2023-04-19 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 71b5c9ad74a7d..cec848d089f1a 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: 2023-04-18 +date: 2023-04-19 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 6bf95189f71e7..6dd568e68595b 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: 2023-04-18 +date: 2023-04-19 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 1ba66cbcba441..b1b3f3c84cf7f 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: 2023-04-18 +date: 2023-04-19 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 1265fd8c4c41c..b9e2efa25823f 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: 2023-04-18 +date: 2023-04-19 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 e42ae9f1452a9..d89c14bb7d6f3 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: 2023-04-18 +date: 2023-04-19 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 d818c868e946a..8bba1c8faaadd 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: 2023-04-18 +date: 2023-04-19 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_so_type_serializer.mdx b/api_docs/kbn_core_test_helpers_so_type_serializer.mdx index 4e19bbc6dda04..9e3173f339a99 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: 2023-04-18 +date: 2023-04-19 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 45a40df59f54c..737f37c0f6807 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: 2023-04-18 +date: 2023-04-19 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 c917ce6f4508a..8af892b1926aa 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: 2023-04-18 +date: 2023-04-19 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_internal.mdx b/api_docs/kbn_core_theme_browser_internal.mdx index bf7ca3a681be8..ec571030356a6 100644 --- a/api_docs/kbn_core_theme_browser_internal.mdx +++ b/api_docs/kbn_core_theme_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-theme-browser-internal title: "@kbn/core-theme-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-theme-browser-internal plugin -date: 2023-04-18 +date: 2023-04-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-theme-browser-internal'] --- import kbnCoreThemeBrowserInternalObj from './kbn_core_theme_browser_internal.devdocs.json'; diff --git a/api_docs/kbn_core_theme_browser_mocks.mdx b/api_docs/kbn_core_theme_browser_mocks.mdx index 9f99419f85f7e..2334b44813bba 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: 2023-04-18 +date: 2023-04-19 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 676be86e74cdd..cdb58a7f10ff6 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: 2023-04-18 +date: 2023-04-19 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 812718a2f7545..40806c06351df 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: 2023-04-18 +date: 2023-04-19 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 0647caa343e58..d917f1e1aad65 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: 2023-04-18 +date: 2023-04-19 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 eeb3a779d35b5..e018d2c0411e5 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: 2023-04-18 +date: 2023-04-19 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 f0d34230776c8..b7ea799469138 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: 2023-04-18 +date: 2023-04-19 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 32b32d0e7fd94..6f49e6af8e8cc 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: 2023-04-18 +date: 2023-04-19 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 1119ea24ebfb0..fbb82389c6e58 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: 2023-04-18 +date: 2023-04-19 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 d6e8a9d0818f9..40ce42a44cf15 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: 2023-04-18 +date: 2023-04-19 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 9d0c2a074e8c1..f53a1d696836e 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: 2023-04-18 +date: 2023-04-19 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 39b7f3a676bf2..63c67653c704c 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: 2023-04-18 +date: 2023-04-19 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_crypto.mdx b/api_docs/kbn_crypto.mdx index cebe27a86e77a..e579c0b88b82b 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: 2023-04-18 +date: 2023-04-19 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 91bb769b9a842..c6ac7c1e36a6f 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: 2023-04-18 +date: 2023-04-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/crypto-browser'] --- import kbnCryptoBrowserObj from './kbn_crypto_browser.devdocs.json'; diff --git a/api_docs/kbn_cypress_config.mdx b/api_docs/kbn_cypress_config.mdx index 936c0194cf100..425ea8f8ebd24 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: 2023-04-18 +date: 2023-04-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/cypress-config'] --- import kbnCypressConfigObj from './kbn_cypress_config.devdocs.json'; diff --git a/api_docs/kbn_datemath.mdx b/api_docs/kbn_datemath.mdx index f4d70bb7beaf9..63d561e44cf27 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: 2023-04-18 +date: 2023-04-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/datemath'] --- import kbnDatemathObj from './kbn_datemath.devdocs.json'; diff --git a/api_docs/kbn_dev_cli_errors.mdx b/api_docs/kbn_dev_cli_errors.mdx index bf21852659826..0d605704eab6e 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: 2023-04-18 +date: 2023-04-19 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 af8aa3f690f90..a14f794d444e4 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: 2023-04-18 +date: 2023-04-19 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 de10bacc58724..6c434f94f2b97 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: 2023-04-18 +date: 2023-04-19 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 7e7d3a57e59ad..a74d64dd14608 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: 2023-04-18 +date: 2023-04-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/dev-utils'] --- import kbnDevUtilsObj from './kbn_dev_utils.devdocs.json'; diff --git a/api_docs/kbn_doc_links.mdx b/api_docs/kbn_doc_links.mdx index f2bab21b1bf69..022b4c600076b 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: 2023-04-18 +date: 2023-04-19 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 136cbbc27c6dd..dd54a131f03c0 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: 2023-04-18 +date: 2023-04-19 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 43c5a487fa41a..d97372d79ebdd 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: 2023-04-18 +date: 2023-04-19 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 8ee7f09600b9c..c4e26078f26c2 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: 2023-04-18 +date: 2023-04-19 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 dabd67ea639b2..589a982102229 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: 2023-04-18 +date: 2023-04-19 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 3f8b2071edb6f..11f7705253010 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: 2023-04-18 +date: 2023-04-19 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_es.mdx b/api_docs/kbn_es.mdx index d0211ec7b739d..19d33d2cc2bf6 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: 2023-04-18 +date: 2023-04-19 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 c47aa16f5d9aa..ea829b18afe8c 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: 2023-04-18 +date: 2023-04-19 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 b23404d6e673c..baedea81274c2 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: 2023-04-18 +date: 2023-04-19 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 b3484d0acfb86..98d2f9264c732 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: 2023-04-18 +date: 2023-04-19 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 ae320937273a3..2c4e5b793efdd 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: 2023-04-18 +date: 2023-04-19 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 c0cd7f247745c..b09632ba7c10f 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: 2023-04-18 +date: 2023-04-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/eslint-plugin-imports'] --- import kbnEslintPluginImportsObj from './kbn_eslint_plugin_imports.devdocs.json'; diff --git a/api_docs/kbn_expandable_flyout.mdx b/api_docs/kbn_expandable_flyout.mdx index bd67f9c70c6ee..1cb4678323b20 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: 2023-04-18 +date: 2023-04-19 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 d24f4d36aacaa..33c796182f8d8 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: 2023-04-18 +date: 2023-04-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/field-types'] --- import kbnFieldTypesObj from './kbn_field_types.devdocs.json'; diff --git a/api_docs/kbn_find_used_node_modules.mdx b/api_docs/kbn_find_used_node_modules.mdx index 0ff18adb57009..22e741bcea6c2 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: 2023-04-18 +date: 2023-04-19 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 b5cb393ecee58..e2b7a8557341b 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: 2023-04-18 +date: 2023-04-19 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_generate.mdx b/api_docs/kbn_generate.mdx index af71fb24bab46..c367835ac3fe0 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: 2023-04-18 +date: 2023-04-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/generate'] --- import kbnGenerateObj from './kbn_generate.devdocs.json'; diff --git a/api_docs/kbn_generate_csv.mdx b/api_docs/kbn_generate_csv.mdx index 0974d13e014d5..e61ee026319bb 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: 2023-04-18 +date: 2023-04-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/generate-csv'] --- import kbnGenerateCsvObj from './kbn_generate_csv.devdocs.json'; diff --git a/api_docs/kbn_generate_csv_types.mdx b/api_docs/kbn_generate_csv_types.mdx index b34fdeed86825..bc547e035a7c9 100644 --- a/api_docs/kbn_generate_csv_types.mdx +++ b/api_docs/kbn_generate_csv_types.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-generate-csv-types title: "@kbn/generate-csv-types" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/generate-csv-types plugin -date: 2023-04-18 +date: 2023-04-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/generate-csv-types'] --- import kbnGenerateCsvTypesObj from './kbn_generate_csv_types.devdocs.json'; diff --git a/api_docs/kbn_guided_onboarding.mdx b/api_docs/kbn_guided_onboarding.mdx index 42afc5141731d..6c1f189614fab 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: 2023-04-18 +date: 2023-04-19 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 c40b6ea1d58ce..ebddb3c829bea 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: 2023-04-18 +date: 2023-04-19 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 bdf2a88a68487..7e931a6919a3c 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: 2023-04-18 +date: 2023-04-19 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 cdc18073f6f93..09cda43a18ac0 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: 2023-04-18 +date: 2023-04-19 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 fec966b810f85..3b51d62c6a7a5 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: 2023-04-18 +date: 2023-04-19 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 203bb222166b2..54626a8c45b6f 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: 2023-04-18 +date: 2023-04-19 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 55b34ed2a0bb2..85a07c8287f09 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: 2023-04-18 +date: 2023-04-19 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 e32b616b7d68e..7159df8ea8c7c 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: 2023-04-18 +date: 2023-04-19 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 eccc8f7a032b2..ce0e9d0f943da 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: 2023-04-18 +date: 2023-04-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/import-resolver'] --- import kbnImportResolverObj from './kbn_import_resolver.devdocs.json'; diff --git a/api_docs/kbn_interpreter.mdx b/api_docs/kbn_interpreter.mdx index 21b0f06c85c3c..ff307710d9591 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: 2023-04-18 +date: 2023-04-19 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 9b1ab460a48e2..5513baf02af0f 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: 2023-04-18 +date: 2023-04-19 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 a24ea34372a88..01f83e2645e3c 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: 2023-04-18 +date: 2023-04-19 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 b12d5db04de4e..7bb8caca77b03 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: 2023-04-18 +date: 2023-04-19 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 cf09efc67ac87..df2ecccb26274 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: 2023-04-18 +date: 2023-04-19 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 7a779ddd3c4bd..b370b7075838f 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: 2023-04-18 +date: 2023-04-19 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 16fbaa5a83b23..f71c8943b6932 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: 2023-04-18 +date: 2023-04-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/language-documentation-popover'] --- import kbnLanguageDocumentationPopoverObj from './kbn_language_documentation_popover.devdocs.json'; diff --git a/api_docs/kbn_logging.mdx b/api_docs/kbn_logging.mdx index 7b06571307450..6ccc85fc8b17d 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: 2023-04-18 +date: 2023-04-19 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 518342efda00a..be471478b0784 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: 2023-04-18 +date: 2023-04-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/logging-mocks'] --- import kbnLoggingMocksObj from './kbn_logging_mocks.devdocs.json'; diff --git a/api_docs/kbn_managed_vscode_config.mdx b/api_docs/kbn_managed_vscode_config.mdx index 98ff5384db391..62bfc0653e309 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: 2023-04-18 +date: 2023-04-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/managed-vscode-config'] --- import kbnManagedVscodeConfigObj from './kbn_managed_vscode_config.devdocs.json'; diff --git a/api_docs/kbn_mapbox_gl.mdx b/api_docs/kbn_mapbox_gl.mdx index 4ae224c44b852..59d73c4659fc7 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: 2023-04-18 +date: 2023-04-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/mapbox-gl'] --- import kbnMapboxGlObj from './kbn_mapbox_gl.devdocs.json'; diff --git a/api_docs/kbn_ml_agg_utils.mdx b/api_docs/kbn_ml_agg_utils.mdx index d5a6b147d2164..e044be4e2d738 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: 2023-04-18 +date: 2023-04-19 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_date_picker.mdx b/api_docs/kbn_ml_date_picker.mdx index ad1a76cda77fd..c8f1b2857f741 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: 2023-04-18 +date: 2023-04-19 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_is_defined.mdx b/api_docs/kbn_ml_is_defined.mdx index 53f52958f230d..c882708c87d62 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: 2023-04-18 +date: 2023-04-19 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 df2d44a227076..35cac8263fb52 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: 2023-04-18 +date: 2023-04-19 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_local_storage.mdx b/api_docs/kbn_ml_local_storage.mdx index 74d5d50e2ffd7..bfc1083f66c9a 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: 2023-04-18 +date: 2023-04-19 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 db930f178714d..7a96b5d544120 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: 2023-04-18 +date: 2023-04-19 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 ae2c03e78f9ff..b9b82f44e2f05 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: 2023-04-18 +date: 2023-04-19 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 9c81b23eb01dd..65b228df42293 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: 2023-04-18 +date: 2023-04-19 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 7042b416b5611..c01e9794722c5 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: 2023-04-18 +date: 2023-04-19 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 f84a407e7f61a..849f20fba0795 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: 2023-04-18 +date: 2023-04-19 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_string_hash.mdx b/api_docs/kbn_ml_string_hash.mdx index 00bf605ac7816..7e786976d78b9 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: 2023-04-18 +date: 2023-04-19 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 f5dd277729937..51dec0f93ddeb 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: 2023-04-18 +date: 2023-04-19 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_url_state.mdx b/api_docs/kbn_ml_url_state.mdx index 473f114a50709..0b1bf48cd584b 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: 2023-04-18 +date: 2023-04-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-url-state'] --- import kbnMlUrlStateObj from './kbn_ml_url_state.devdocs.json'; diff --git a/api_docs/kbn_monaco.mdx b/api_docs/kbn_monaco.mdx index dbd1322725e51..f753592a95739 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: 2023-04-18 +date: 2023-04-19 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 bdf70880504f7..a70891f0e72f4 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: 2023-04-18 +date: 2023-04-19 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 ef2cadc4516c3..6d25386de3158 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: 2023-04-18 +date: 2023-04-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/observability-alert-details'] --- import kbnObservabilityAlertDetailsObj from './kbn_observability_alert_details.devdocs.json'; diff --git a/api_docs/kbn_optimizer.mdx b/api_docs/kbn_optimizer.mdx index 0897ce442f287..2a345e6b1aabd 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: 2023-04-18 +date: 2023-04-19 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 93899a1c199f4..1255de26b275f 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: 2023-04-18 +date: 2023-04-19 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 8d78be6e10ad9..e8886fb80a281 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: 2023-04-18 +date: 2023-04-19 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_performance_testing_dataset_extractor.mdx b/api_docs/kbn_performance_testing_dataset_extractor.mdx index 9f597884b2955..be186b8aefbf7 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: 2023-04-18 +date: 2023-04-19 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_generator.mdx b/api_docs/kbn_plugin_generator.mdx index ef15cdfe2059a..1c2bd87c3a2fb 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: 2023-04-18 +date: 2023-04-19 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 c67a14a04373e..6957d39392ab0 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: 2023-04-18 +date: 2023-04-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/plugin-helpers'] --- import kbnPluginHelpersObj from './kbn_plugin_helpers.devdocs.json'; diff --git a/api_docs/kbn_react_field.mdx b/api_docs/kbn_react_field.mdx index 2a0a1a155a82c..3f9c876bec28d 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: 2023-04-18 +date: 2023-04-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/react-field'] --- import kbnReactFieldObj from './kbn_react_field.devdocs.json'; diff --git a/api_docs/kbn_repo_file_maps.mdx b/api_docs/kbn_repo_file_maps.mdx index e1f6c59096a77..50ffb132d5ee4 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: 2023-04-18 +date: 2023-04-19 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 c60888f9af90a..64d76681a94c2 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: 2023-04-18 +date: 2023-04-19 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 af11a10209f93..726d4775a7541 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: 2023-04-18 +date: 2023-04-19 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 d7fc93cb103ac..ebc905fe80095 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: 2023-04-18 +date: 2023-04-19 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 335b4b0cbc84a..f16d5d346aa8f 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: 2023-04-18 +date: 2023-04-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/reporting-common'] --- import kbnReportingCommonObj from './kbn_reporting_common.devdocs.json'; diff --git a/api_docs/kbn_rison.mdx b/api_docs/kbn_rison.mdx index 06232d88b030c..ffabeef0c0662 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: 2023-04-18 +date: 2023-04-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/rison'] --- import kbnRisonObj from './kbn_rison.devdocs.json'; diff --git a/api_docs/kbn_rule_data_utils.mdx b/api_docs/kbn_rule_data_utils.mdx index ac693b0eb23e7..d9d1a037f64b1 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: 2023-04-18 +date: 2023-04-19 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 27b29d34528e0..6a8a7b7b45d2f 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: 2023-04-18 +date: 2023-04-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/saved-objects-settings'] --- import kbnSavedObjectsSettingsObj from './kbn_saved_objects_settings.devdocs.json'; diff --git a/api_docs/kbn_security_solution_side_nav.mdx b/api_docs/kbn_security_solution_side_nav.mdx index 8edfc25a8f14b..6c4e47f1a4351 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: 2023-04-18 +date: 2023-04-19 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 bec820301d81d..0003d9f974b86 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: 2023-04-18 +date: 2023-04-19 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 a2b697366ceb2..30b6726407b20 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: 2023-04-18 +date: 2023-04-19 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 3d942659e507e..93f74959bb28e 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: 2023-04-18 +date: 2023-04-19 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 23f26f13400c5..c6864d53f5987 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: 2023-04-18 +date: 2023-04-19 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 ec272c689d3b4..0683ce18f1779 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: 2023-04-18 +date: 2023-04-19 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 42a51ba60af2c..fa2c635cb5987 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: 2023-04-18 +date: 2023-04-19 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 4f0072fd817a8..6f5cade1143a6 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: 2023-04-18 +date: 2023-04-19 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 38f6f58e2a7b9..144d4664cd8d2 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: 2023-04-18 +date: 2023-04-19 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 8eda87547a660..2fb1367fc08ad 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: 2023-04-18 +date: 2023-04-19 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 a99d546082489..b5109765d9b12 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: 2023-04-18 +date: 2023-04-19 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 c206469d9c81e..24a04a2bd93b6 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: 2023-04-18 +date: 2023-04-19 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 2c92875e75bd7..47c3625cc4e59 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: 2023-04-18 +date: 2023-04-19 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 ae136149d07d6..04b3393bdfa7f 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: 2023-04-18 +date: 2023-04-19 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 d60684245c382..9e574e3409680 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: 2023-04-18 +date: 2023-04-19 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 4b33f8a6abbd3..b674110dcacb4 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: 2023-04-18 +date: 2023-04-19 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 91b702277c921..c2aa79d1226d9 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: 2023-04-18 +date: 2023-04-19 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 27ce921681830..0477dce73217b 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: 2023-04-18 +date: 2023-04-19 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 a474045f5c74e..2f9f9263ae18e 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: 2023-04-18 +date: 2023-04-19 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 d2603e518f764..bf30718757312 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: 2023-04-18 +date: 2023-04-19 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 8094dc6ed8a35..f05a9f838751d 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: 2023-04-18 +date: 2023-04-19 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 8f06616ffeafa..33001595efbb3 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: 2023-04-18 +date: 2023-04-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/server-route-repository'] --- import kbnServerRouteRepositoryObj from './kbn_server_route_repository.devdocs.json'; diff --git a/api_docs/kbn_shared_svg.mdx b/api_docs/kbn_shared_svg.mdx index 625af47e20b91..b2aae28dfd413 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: 2023-04-18 +date: 2023-04-19 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 f3c543936022f..8d727e343ce46 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: 2023-04-18 +date: 2023-04-19 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_avatar_user_profile_components.mdx b/api_docs/kbn_shared_ux_avatar_user_profile_components.mdx index c7f660fc5d422..1d96f50090471 100644 --- a/api_docs/kbn_shared_ux_avatar_user_profile_components.mdx +++ b/api_docs/kbn_shared_ux_avatar_user_profile_components.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-avatar-user-profile-components title: "@kbn/shared-ux-avatar-user-profile-components" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-avatar-user-profile-components plugin -date: 2023-04-18 +date: 2023-04-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-avatar-user-profile-components'] --- import kbnSharedUxAvatarUserProfileComponentsObj from './kbn_shared_ux_avatar_user_profile_components.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 3722ab19af545..c238eebf30ce3 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: 2023-04-18 +date: 2023-04-19 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_exit_full_screen_mocks.mdx b/api_docs/kbn_shared_ux_button_exit_full_screen_mocks.mdx index 23d88b9bb7bc2..e571e864814a7 100644 --- a/api_docs/kbn_shared_ux_button_exit_full_screen_mocks.mdx +++ b/api_docs/kbn_shared_ux_button_exit_full_screen_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-button-exit-full-screen-mocks title: "@kbn/shared-ux-button-exit-full-screen-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-button-exit-full-screen-mocks plugin -date: 2023-04-18 +date: 2023-04-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-button-exit-full-screen-mocks'] --- import kbnSharedUxButtonExitFullScreenMocksObj from './kbn_shared_ux_button_exit_full_screen_mocks.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_button_toolbar.mdx b/api_docs/kbn_shared_ux_button_toolbar.mdx index 65a874e30c4ba..17d863ad15b80 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: 2023-04-18 +date: 2023-04-19 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 6c6bd673810c8..c7560cd21c835 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: 2023-04-18 +date: 2023-04-19 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 e223fdfdebcb9..7c3cbca6bfa65 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: 2023-04-18 +date: 2023-04-19 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_file_context.mdx b/api_docs/kbn_shared_ux_file_context.mdx index 76f3f5836a837..3458dd32b4b5c 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: 2023-04-18 +date: 2023-04-19 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 2309a89ee51ae..c5b10b3e93361 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: 2023-04-18 +date: 2023-04-19 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 7d504a9b90abe..03765102bad1c 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: 2023-04-18 +date: 2023-04-19 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 c1c7b2bd635ef..c2a6b2898f267 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: 2023-04-18 +date: 2023-04-19 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 bc72a83b4631a..a3072335000dd 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: 2023-04-18 +date: 2023-04-19 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 7bfade800a5b0..73dffa9666505 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: 2023-04-18 +date: 2023-04-19 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 d789138a1f3c4..b44a3ebd0cf57 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: 2023-04-18 +date: 2023-04-19 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 cd2005cbe2f6c..29f8177f6a683 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: 2023-04-18 +date: 2023-04-19 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 453e3d551b616..bfeb8a5123374 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: 2023-04-18 +date: 2023-04-19 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 840098b088fb6..124de09fbb6b2 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: 2023-04-18 +date: 2023-04-19 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 6168d5ff59b0c..ffef3842a8f49 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: 2023-04-18 +date: 2023-04-19 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 0b2c65a94ccc3..12253c541138d 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: 2023-04-18 +date: 2023-04-19 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 733df6b02ef93..521e7afeffc28 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: 2023-04-18 +date: 2023-04-19 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 0826251e7e69b..d94f87f474a06 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: 2023-04-18 +date: 2023-04-19 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 8d57ca813a7bb..9256b249ddec3 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: 2023-04-18 +date: 2023-04-19 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 4796a4dc64d56..98c666dc7ac30 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: 2023-04-18 +date: 2023-04-19 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 e41913e16925f..c7e445662d405 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: 2023-04-18 +date: 2023-04-19 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 3104c250cd274..16e2e693823a6 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: 2023-04-18 +date: 2023-04-19 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 c4f55f5c20806..dd303a55b92d1 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: 2023-04-18 +date: 2023-04-19 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 d167b1e958751..730c8b3b4a670 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: 2023-04-18 +date: 2023-04-19 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 f5de870c5b104..b9a5faef6e24d 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: 2023-04-18 +date: 2023-04-19 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 7ec62a659a858..eb3cdb4b62c34 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: 2023-04-18 +date: 2023-04-19 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 64c532fffed65..50643295597d3 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: 2023-04-18 +date: 2023-04-19 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 71813a99b74dc..a3b0893d962cc 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: 2023-04-18 +date: 2023-04-19 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 52ba22e84684f..0419eea5c2561 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: 2023-04-18 +date: 2023-04-19 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 9f237bb6371dc..8d93e65e58a7a 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: 2023-04-18 +date: 2023-04-19 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 ea4b2719da63f..7b06d8fedf3bc 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: 2023-04-18 +date: 2023-04-19 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 29ff909748e4b..ba35535d9c82d 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: 2023-04-18 +date: 2023-04-19 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 2827c6086dc04..b80a811065383 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: 2023-04-18 +date: 2023-04-19 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 cca631a87cd4d..fdd01db6e7893 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: 2023-04-18 +date: 2023-04-19 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 fd49eeb3bece1..002e032035f2d 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: 2023-04-18 +date: 2023-04-19 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 354903e690a21..909cbc86dbb9a 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: 2023-04-18 +date: 2023-04-19 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 d66dea91b8a6f..32a0175b0ae11 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: 2023-04-18 +date: 2023-04-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/some-dev-log'] --- import kbnSomeDevLogObj from './kbn_some_dev_log.devdocs.json'; diff --git a/api_docs/kbn_std.mdx b/api_docs/kbn_std.mdx index 4fccf81370810..760fb637dca1f 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: 2023-04-18 +date: 2023-04-19 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 fb3bbf806a401..c7c0f0be1c9a8 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: 2023-04-18 +date: 2023-04-19 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 3de038e136336..a8aaf772dfbeb 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: 2023-04-18 +date: 2023-04-19 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 cfcafe77f4a48..e6fb061b3c4e2 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: 2023-04-18 +date: 2023-04-19 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 a7e70f981fba1..9bc5f62ac0fb9 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: 2023-04-18 +date: 2023-04-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/test'] --- import kbnTestObj from './kbn_test.devdocs.json'; diff --git a/api_docs/kbn_test_jest_helpers.mdx b/api_docs/kbn_test_jest_helpers.mdx index e90b770c49a23..7cdb7653f1365 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: 2023-04-18 +date: 2023-04-19 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 ea78c22a4920e..fa0e562873fca 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: 2023-04-18 +date: 2023-04-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/test-subj-selector'] --- import kbnTestSubjSelectorObj from './kbn_test_subj_selector.devdocs.json'; diff --git a/api_docs/kbn_tooling_log.mdx b/api_docs/kbn_tooling_log.mdx index 33e8ca0f4e73e..06c7351858830 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: 2023-04-18 +date: 2023-04-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/tooling-log'] --- import kbnToolingLogObj from './kbn_tooling_log.devdocs.json'; diff --git a/api_docs/kbn_ts_projects.mdx b/api_docs/kbn_ts_projects.mdx index 4ef9e429af459..2b501f8c55bdb 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: 2023-04-18 +date: 2023-04-19 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 3631c441c34ee..1d9e624fb0ea9 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: 2023-04-18 +date: 2023-04-19 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 2fae9c1d82668..2b2bdd60e4ce7 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: 2023-04-18 +date: 2023-04-19 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 8191484ba5834..2461a2ae517a5 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: 2023-04-18 +date: 2023-04-19 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 afa5621c24dd0..889b19937ba67 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: 2023-04-18 +date: 2023-04-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ui-theme'] --- import kbnUiThemeObj from './kbn_ui_theme.devdocs.json'; diff --git a/api_docs/kbn_user_profile_components.mdx b/api_docs/kbn_user_profile_components.mdx index 186a8f826428d..c68316050f7c4 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: 2023-04-18 +date: 2023-04-19 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 d1fc0f672252e..27d8eed321f38 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: 2023-04-18 +date: 2023-04-19 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 a78c9ff36b9c8..23117d51a44f0 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: 2023-04-18 +date: 2023-04-19 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 ffdbc677b4d20..dd420e4509c04 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: 2023-04-18 +date: 2023-04-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/utils'] --- import kbnUtilsObj from './kbn_utils.devdocs.json'; diff --git a/api_docs/kbn_yarn_lock_validator.mdx b/api_docs/kbn_yarn_lock_validator.mdx index afbc6e1514ece..eb3b59e92004e 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: 2023-04-18 +date: 2023-04-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/yarn-lock-validator'] --- import kbnYarnLockValidatorObj from './kbn_yarn_lock_validator.devdocs.json'; diff --git a/api_docs/kibana_overview.mdx b/api_docs/kibana_overview.mdx index 4a14e097b5a6e..92633990126eb 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: 2023-04-18 +date: 2023-04-19 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 30afa8507031f..1dbaea8a030f5 100644 --- a/api_docs/kibana_react.devdocs.json +++ b/api_docs/kibana_react.devdocs.json @@ -1268,18 +1268,6 @@ "plugin": "savedObjectsManagement", "path": "src/plugins/saved_objects_management/public/management_section/objects_table/saved_objects_table.tsx" }, - { - "plugin": "observability", - "path": "x-pack/plugins/observability/public/application/index.tsx" - }, - { - "plugin": "observability", - "path": "x-pack/plugins/observability/public/application/index.tsx" - }, - { - "plugin": "observability", - "path": "x-pack/plugins/observability/public/application/index.tsx" - }, { "plugin": "exploratoryView", "path": "x-pack/plugins/exploratory_view/public/application/index.tsx" @@ -1316,6 +1304,18 @@ "plugin": "fleet", "path": "x-pack/plugins/fleet/public/components/custom_assets_accordion.tsx" }, + { + "plugin": "observability", + "path": "x-pack/plugins/observability/public/application/index.tsx" + }, + { + "plugin": "observability", + "path": "x-pack/plugins/observability/public/application/index.tsx" + }, + { + "plugin": "observability", + "path": "x-pack/plugins/observability/public/application/index.tsx" + }, { "plugin": "ml", "path": "x-pack/plugins/ml/public/application/management/jobs_list/components/jobs_list_page/jobs_list_page.tsx" diff --git a/api_docs/kibana_react.mdx b/api_docs/kibana_react.mdx index 5a580fea57c7c..b521c0367a597 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: 2023-04-18 +date: 2023-04-19 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 9694f7d515bb3..8dd98ee82cd67 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: 2023-04-18 +date: 2023-04-19 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 76c162d42d53a..edea19a333163 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: 2023-04-18 +date: 2023-04-19 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 4e7a2c648a02e..802b5f80050e6 100644 --- a/api_docs/lens.devdocs.json +++ b/api_docs/lens.devdocs.json @@ -6585,7 +6585,7 @@ "signature": [ "((props: ", "VisualizationConfigProps", - ") => boolean) | undefined" + ") => Record<\"data\" | \"appearance\", boolean>) | undefined" ], "path": "x-pack/plugins/lens/public/types.ts", "deprecated": false, @@ -6619,8 +6619,8 @@ "description": [], "signature": [ "((domElement: Element, props: ", - "VisualizationLayerSettingsProps", - ") => void | ((cleanupElement: Element) => void)) | undefined" + "VisualizationConfigProps", + " & { setState(newState: T | ((currState: T) => T)): void; panelRef: React.MutableRefObject; } & { section: \"data\" | \"appearance\"; }) => void | ((cleanupElement: Element) => void)) | undefined" ], "path": "x-pack/plugins/lens/public/types.ts", "deprecated": false, @@ -6649,8 +6649,8 @@ "label": "props", "description": [], "signature": [ - "VisualizationLayerSettingsProps", - "" + "VisualizationConfigProps", + " & { setState(newState: T | ((currState: T) => T)): void; panelRef: React.MutableRefObject; } & { section: \"data\" | \"appearance\"; }" ], "path": "x-pack/plugins/lens/public/types.ts", "deprecated": false, @@ -7602,6 +7602,40 @@ } ], "returnComment": [] + }, + { + "parentPluginId": "lens", + "id": "def-public.Visualization.getReportingLayout", + "type": "Function", + "tags": [], + "label": "getReportingLayout", + "description": [ + "\nA visualization can return custom dimensions for the reporting tool" + ], + "signature": [ + "((state: T) => { height: number; width: number; }) | undefined" + ], + "path": "x-pack/plugins/lens/public/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "lens", + "id": "def-public.Visualization.getReportingLayout.$1", + "type": "Uncategorized", + "tags": [], + "label": "state", + "description": [], + "signature": [ + "T" + ], + "path": "x-pack/plugins/lens/public/types.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] } ], "initialIsOpen": false diff --git a/api_docs/lens.mdx b/api_docs/lens.mdx index 5377268b2dc32..67003127c5a66 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: 2023-04-18 +date: 2023-04-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'lens'] --- import lensObj from './lens.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 | |-------------------|-----------|------------------------|-----------------| -| 608 | 0 | 513 | 53 | +| 610 | 0 | 514 | 52 | ## Client diff --git a/api_docs/license_api_guard.mdx b/api_docs/license_api_guard.mdx index 7fe813ac1ed82..523a22146062a 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: 2023-04-18 +date: 2023-04-19 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 5b6307ca22c86..f8f04c8ea317a 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: 2023-04-18 +date: 2023-04-19 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 98bf9c1fa8ae2..5d218b97477d5 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: 2023-04-18 +date: 2023-04-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'licensing'] --- import licensingObj from './licensing.devdocs.json'; diff --git a/api_docs/lists.mdx b/api_docs/lists.mdx index 017be96350ac0..3fa35bd4fee29 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: 2023-04-18 +date: 2023-04-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'lists'] --- import listsObj from './lists.devdocs.json'; diff --git a/api_docs/management.mdx b/api_docs/management.mdx index f95d5c3108003..869b213005de0 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: 2023-04-18 +date: 2023-04-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'management'] --- import managementObj from './management.devdocs.json'; diff --git a/api_docs/maps.devdocs.json b/api_docs/maps.devdocs.json index 327c896765d86..5b7625d514738 100644 --- a/api_docs/maps.devdocs.json +++ b/api_docs/maps.devdocs.json @@ -3976,7 +3976,7 @@ "signature": [ "\"__kbn__feature_id__\"" ], - "path": "x-pack/plugins/maps/public/classes/layers/vector_layer/geojson_vector_layer/assign_feature_ids.ts", + "path": "x-pack/plugins/maps/common/constants.ts", "deprecated": false, "trackAdoption": false, "initialIsOpen": false @@ -4101,7 +4101,7 @@ "signature": [ "\"MAPS_APP_LOCATOR\"" ], - "path": "x-pack/plugins/maps/public/locators.ts", + "path": "x-pack/plugins/maps/public/locators/map_locator/locator_definition.ts", "deprecated": false, "trackAdoption": false, "initialIsOpen": false diff --git a/api_docs/maps.mdx b/api_docs/maps.mdx index 4162686593d2e..c61e46893d581 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: 2023-04-18 +date: 2023-04-19 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 058893cf79f57..880d15fc8021b 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: 2023-04-18 +date: 2023-04-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'mapsEms'] --- import mapsEmsObj from './maps_ems.devdocs.json'; diff --git a/api_docs/ml.mdx b/api_docs/ml.mdx index f87cd86d7c650..3f870e60e045f 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: 2023-04-18 +date: 2023-04-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'ml'] --- import mlObj from './ml.devdocs.json'; diff --git a/api_docs/monitoring.mdx b/api_docs/monitoring.mdx index 66f5f745e831d..52cfc7d67cff5 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: 2023-04-18 +date: 2023-04-19 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 099410cb470cd..0dd111f72ef78 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: 2023-04-18 +date: 2023-04-19 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 bbabd06919df1..3121830a9f2a7 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: 2023-04-18 +date: 2023-04-19 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 16aea8ad4d480..37887d4843b4c 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: 2023-04-18 +date: 2023-04-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'newsfeed'] --- import newsfeedObj from './newsfeed.devdocs.json'; diff --git a/api_docs/notifications.mdx b/api_docs/notifications.mdx index 2b804922debe0..f9bde1b340e02 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: 2023-04-18 +date: 2023-04-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'notifications'] --- import notificationsObj from './notifications.devdocs.json'; diff --git a/api_docs/observability.devdocs.json b/api_docs/observability.devdocs.json index 44e312e71c6d6..7a97a26b790af 100644 --- a/api_docs/observability.devdocs.json +++ b/api_docs/observability.devdocs.json @@ -4242,6 +4242,24 @@ "deprecated": false, "trackAdoption": false }, + { + "parentPluginId": "observability", + "id": "def-public.ObservabilityPublicPluginsSetup.observabilityShared", + "type": "Object", + "tags": [], + "label": "observabilityShared", + "description": [], + "signature": [ + "{ navigation: { registerSections: (sections$: ", + "Observable", + "<", + "NavigationSection", + "[]>) => void; }; }" + ], + "path": "x-pack/plugins/observability/public/plugin.ts", + "deprecated": false, + "trackAdoption": false + }, { "parentPluginId": "observability", "id": "def-public.ObservabilityPublicPluginsSetup.share", @@ -4526,6 +4544,26 @@ "deprecated": false, "trackAdoption": false }, + { + "parentPluginId": "observability", + "id": "def-public.ObservabilityPublicPluginsStart.exploratoryView", + "type": "Object", + "tags": [], + "label": "exploratoryView", + "description": [], + "signature": [ + { + "pluginId": "exploratoryView", + "scope": "public", + "docId": "kibExploratoryViewPluginApi", + "section": "def-public.ExploratoryViewPublicPluginsStart", + "text": "ExploratoryViewPublicPluginsStart" + } + ], + "path": "x-pack/plugins/observability/public/plugin.ts", + "deprecated": false, + "trackAdoption": false + }, { "parentPluginId": "observability", "id": "def-public.ObservabilityPublicPluginsStart.guidedOnboarding", @@ -4586,6 +4624,40 @@ "deprecated": false, "trackAdoption": false }, + { + "parentPluginId": "observability", + "id": "def-public.ObservabilityPublicPluginsStart.observabilityShared", + "type": "Object", + "tags": [], + "label": "observabilityShared", + "description": [], + "signature": [ + "{ navigation: { PageTemplate: (pageTemplateProps: ", + "WrappedPageTemplateProps", + ") => JSX.Element; }; updateGlobalNavigation: ({ capabilities, deepLinks, updater$, }: { capabilities: Readonly<{ [x: string]: Readonly<{ [x: string]: boolean | Readonly<{ [x: string]: boolean; }>; }>; navLinks: Readonly<{ [x: string]: boolean; }>; management: Readonly<{ [x: string]: Readonly<{ [x: string]: boolean; }>; }>; catalogue: Readonly<{ [x: string]: boolean; }>; }>; deepLinks: ", + { + "pluginId": "@kbn/core-application-browser", + "scope": "common", + "docId": "kibKbnCoreApplicationBrowserPluginApi", + "section": "def-common.AppDeepLink", + "text": "AppDeepLink" + }, + "[]; updater$: ", + "Subject", + "<", + { + "pluginId": "@kbn/core-application-browser", + "scope": "common", + "docId": "kibKbnCoreApplicationBrowserPluginApi", + "section": "def-common.AppUpdater", + "text": "AppUpdater" + }, + ">; }) => void; }" + ], + "path": "x-pack/plugins/observability/public/plugin.ts", + "deprecated": false, + "trackAdoption": false + }, { "parentPluginId": "observability", "id": "def-public.ObservabilityPublicPluginsStart.ruleTypeRegistry", @@ -6845,17 +6917,7 @@ "section": "def-public.ObservabilityRuleTypeFormatter", "text": "ObservabilityRuleTypeFormatter" }, - " | undefined; list: () => string[]; }; navigation: { registerSections: (sections$: ", - "Observable", - "<", - { - "pluginId": "observability", - "scope": "public", - "docId": "kibObservabilityPluginApi", - "section": "def-public.NavigationSection", - "text": "NavigationSection" - }, - "[]>) => void; }; useRulesLink: (options?: ", + " | undefined; list: () => string[]; }; useRulesLink: (options?: ", "Options", ") => ", "LinkProps", @@ -6891,37 +6953,7 @@ "section": "def-public.ObservabilityRuleTypeFormatter", "text": "ObservabilityRuleTypeFormatter" }, - " | undefined; list: () => string[]; }; navigation: { PageTemplate: (pageTemplateProps: ", - "WrappedPageTemplateProps", - ") => JSX.Element; }; createExploratoryViewUrl: ({ reportType, allSeries }: { reportType: ", - "ReportViewType", - "; allSeries: ", - { - "pluginId": "observability", - "scope": "public", - "docId": "kibObservabilityPluginApi", - "section": "def-public.AllSeries", - "text": "AllSeries" - }, - "; }, baseHref?: string, appId?: string) => string; getAppDataView: (appId: ", - "AppDataType", - ", indexPattern?: string | undefined) => Promise<", - { - "pluginId": "dataViews", - "scope": "common", - "docId": "kibDataViewsPluginApi", - "section": "def-common.DataView", - "text": "DataView" - }, - " | null | undefined>; ExploratoryViewEmbeddable: (props: ", - { - "pluginId": "observability", - "scope": "public", - "docId": "kibObservabilityPluginApi", - "section": "def-public.ExploratoryEmbeddableProps", - "text": "ExploratoryEmbeddableProps" - }, - ") => JSX.Element | null; useRulesLink: (options?: ", + " | undefined; list: () => string[]; }; useRulesLink: (options?: ", "Options", ") => ", "LinkProps", diff --git a/api_docs/observability.mdx b/api_docs/observability.mdx index f9e097a2564a2..3e7b223414a79 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: 2023-04-18 +date: 2023-04-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'observability'] --- import observabilityObj from './observability.devdocs.json'; @@ -21,7 +21,7 @@ Contact [@elastic/actionable-observability](https://github.com/orgs/elastic/team | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 653 | 44 | 647 | 34 | +| 656 | 44 | 650 | 33 | ## Client diff --git a/api_docs/observability_shared.devdocs.json b/api_docs/observability_shared.devdocs.json index cc911b709261d..703f3db5c4d04 100644 --- a/api_docs/observability_shared.devdocs.json +++ b/api_docs/observability_shared.devdocs.json @@ -154,7 +154,26 @@ "initialIsOpen": false } ], - "functions": [], + "functions": [ + { + "parentPluginId": "observabilityShared", + "id": "def-public.useObservabilityTourContext", + "type": "Function", + "tags": [], + "label": "useObservabilityTourContext", + "description": [], + "signature": [ + "() => ", + "ObservabilityTourContextValue" + ], + "path": "x-pack/plugins/observability_shared/public/components/tour/tour.tsx", + "deprecated": false, + "trackAdoption": false, + "children": [], + "returnComment": [], + "initialIsOpen": false + } + ], "interfaces": [ { "parentPluginId": "observabilityShared", @@ -376,6 +395,21 @@ "trackAdoption": false, "initialIsOpen": false }, + { + "parentPluginId": "observabilityShared", + "id": "def-public.observabilityAppId", + "type": "string", + "tags": [], + "label": "observabilityAppId", + "description": [], + "signature": [ + "\"observability-overview\"" + ], + "path": "x-pack/plugins/observability_shared/common/index.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, { "parentPluginId": "observabilityShared", "id": "def-public.observabilityFeatureId", diff --git a/api_docs/observability_shared.mdx b/api_docs/observability_shared.mdx index d5d70399bfd0f..4056665de1ed6 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: 2023-04-18 +date: 2023-04-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'observabilityShared'] --- import observabilitySharedObj from './observability_shared.devdocs.json'; @@ -21,10 +21,13 @@ Contact [@elastic/actionable-observability](https://github.com/orgs/elastic/team | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 30 | 0 | 30 | 4 | +| 32 | 0 | 32 | 5 | ## Client +### Functions + + ### Classes diff --git a/api_docs/osquery.mdx b/api_docs/osquery.mdx index 11f7de7c24d37..4663fb00ad3f8 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: 2023-04-18 +date: 2023-04-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'osquery'] --- import osqueryObj from './osquery.devdocs.json'; diff --git a/api_docs/plugin_directory.mdx b/api_docs/plugin_directory.mdx index c2992ea7066af..9c0fa8d2d15ae 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: 2023-04-18 +date: 2023-04-19 tags: ['contributor', 'dev', 'apidocs', 'kibana'] --- @@ -21,7 +21,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | API Count | Any Count | Missing comments | Missing exports | |--------------|----------|-----------------|--------| -| 68983 | 526 | 59579 | 1338 | +| 68995 | 526 | 59590 | 1337 | ## Plugin Directory @@ -72,7 +72,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [@elastic/platform-deployment-management](https://github.com/orgs/elastic/teams/platform-deployment-management) | - | 114 | 3 | 110 | 5 | | | [@elastic/kibana-visualizations](https://github.com/orgs/elastic/teams/kibana-visualizations) | The Event Annotation service contains expressions for event annotations | 172 | 30 | 172 | 3 | | | [@elastic/response-ops](https://github.com/orgs/elastic/teams/response-ops) | - | 116 | 0 | 116 | 11 | -| | [@elastic/uptime](https://github.com/orgs/elastic/teams/uptime) | - | 352 | 4 | 349 | 22 | +| | [@elastic/uptime](https://github.com/orgs/elastic/teams/uptime) | - | 351 | 4 | 348 | 22 | | | [@elastic/kibana-presentation](https://github.com/orgs/elastic/teams/kibana-presentation) | Adds 'error' renderer to expressions | 17 | 0 | 15 | 2 | | | [@elastic/kibana-visualizations](https://github.com/orgs/elastic/teams/kibana-visualizations) | Expression Gauge plugin adds a `gauge` renderer and function to the expression plugin. The renderer will display the `gauge` chart. | 59 | 0 | 59 | 2 | | | [@elastic/kibana-visualizations](https://github.com/orgs/elastic/teams/kibana-visualizations) | Expression Heatmap plugin adds a `heatmap` renderer and function to the expression plugin. The renderer will display the `heatmap` chart. | 112 | 14 | 108 | 2 | @@ -92,7 +92,7 @@ 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. | 62 | 0 | 62 | 2 | | | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | File upload, download, sharing, and serving over HTTP implementation in Kibana. | 215 | 0 | 10 | 5 | | | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | Simple UI for managing files in Kibana | 2 | 1 | 2 | 0 | -| | [@elastic/fleet](https://github.com/orgs/elastic/teams/fleet) | - | 1098 | 3 | 993 | 27 | +| | [@elastic/fleet](https://github.com/orgs/elastic/teams/fleet) | - | 1101 | 3 | 996 | 27 | | 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 | | globalSearchBar | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | - | 0 | 0 | 0 | 0 | @@ -114,7 +114,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | kibanaUsageCollection | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 0 | 0 | 0 | 0 | | | [@elastic/kibana-app-services](https://github.com/orgs/elastic/teams/kibana-app-services) | - | 609 | 3 | 416 | 9 | | | [@elastic/sec-cloudnative-integrations](https://github.com/orgs/elastic/teams/sec-cloudnative-integrations) | - | 3 | 0 | 3 | 1 | -| | [@elastic/kibana-visualizations](https://github.com/orgs/elastic/teams/kibana-visualizations) | Visualization editor allowing to quickly and easily configure compelling visualizations to use on dashboards and canvas workpads. Exposes components to embed visualizations and link into the Lens editor from within other apps in Kibana. | 608 | 0 | 513 | 53 | +| | [@elastic/kibana-visualizations](https://github.com/orgs/elastic/teams/kibana-visualizations) | Visualization editor allowing to quickly and easily configure compelling visualizations to use on dashboards and canvas workpads. Exposes components to embed visualizations and link into the Lens editor from within other apps in Kibana. | 610 | 0 | 514 | 52 | | | [@elastic/platform-deployment-management](https://github.com/orgs/elastic/teams/platform-deployment-management) | - | 8 | 0 | 8 | 0 | | | [@elastic/platform-deployment-management](https://github.com/orgs/elastic/teams/platform-deployment-management) | - | 4 | 0 | 4 | 1 | | | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 117 | 0 | 42 | 10 | @@ -129,8 +129,8 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | - | 34 | 0 | 34 | 2 | | | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 17 | 0 | 17 | 0 | | | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | - | 2 | 0 | 2 | 1 | -| | [@elastic/actionable-observability](https://github.com/orgs/elastic/teams/actionable-observability) | - | 653 | 44 | 647 | 34 | -| | [@elastic/actionable-observability](https://github.com/orgs/elastic/teams/actionable-observability) | - | 30 | 0 | 30 | 4 | +| | [@elastic/actionable-observability](https://github.com/orgs/elastic/teams/actionable-observability) | - | 656 | 44 | 650 | 33 | +| | [@elastic/actionable-observability](https://github.com/orgs/elastic/teams/actionable-observability) | - | 32 | 0 | 32 | 5 | | | [@elastic/security-defend-workflows](https://github.com/orgs/elastic/teams/security-defend-workflows) | - | 24 | 0 | 24 | 6 | | painlessLab | [@elastic/platform-deployment-management](https://github.com/orgs/elastic/teams/platform-deployment-management) | - | 0 | 0 | 0 | 0 | | | [@elastic/kibana-presentation](https://github.com/orgs/elastic/teams/kibana-presentation) | The Presentation Utility Plugin is a set of common, shared components and toolkits for solutions within the Presentation space, (e.g. Dashboards, Canvas). | 207 | 8 | 151 | 12 | @@ -138,7 +138,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [@elastic/platform-deployment-management](https://github.com/orgs/elastic/teams/platform-deployment-management) | - | 4 | 0 | 4 | 0 | | | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | Reporting Services enables applications to feature reports that the user can automate with Watcher and download later. | 36 | 0 | 16 | 0 | | | [@elastic/platform-deployment-management](https://github.com/orgs/elastic/teams/platform-deployment-management) | - | 21 | 0 | 21 | 0 | -| | [@elastic/response-ops](https://github.com/orgs/elastic/teams/response-ops) | - | 260 | 0 | 231 | 14 | +| | [@elastic/response-ops](https://github.com/orgs/elastic/teams/response-ops) | - | 263 | 0 | 234 | 14 | | | [@elastic/platform-deployment-management](https://github.com/orgs/elastic/teams/platform-deployment-management) | - | 24 | 0 | 19 | 2 | | | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 127 | 2 | 116 | 4 | | | [@elastic/kibana-data-discovery](https://github.com/orgs/elastic/teams/kibana-data-discovery) | - | 44 | 0 | 44 | 0 | diff --git a/api_docs/presentation_util.mdx b/api_docs/presentation_util.mdx index a5e850da8b2cb..63d78b82deb52 100644 --- a/api_docs/presentation_util.mdx +++ b/api_docs/presentation_util.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/presentationUtil title: "presentationUtil" image: https://source.unsplash.com/400x175/?github description: API docs for the presentationUtil plugin -date: 2023-04-18 +date: 2023-04-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'presentationUtil'] --- import presentationUtilObj from './presentation_util.devdocs.json'; diff --git a/api_docs/profiling.mdx b/api_docs/profiling.mdx index 532a14b3be1cb..3c12612495706 100644 --- a/api_docs/profiling.mdx +++ b/api_docs/profiling.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/profiling title: "profiling" image: https://source.unsplash.com/400x175/?github description: API docs for the profiling plugin -date: 2023-04-18 +date: 2023-04-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'profiling'] --- import profilingObj from './profiling.devdocs.json'; diff --git a/api_docs/remote_clusters.mdx b/api_docs/remote_clusters.mdx index 7afb72b454a44..a33d94e833ade 100644 --- a/api_docs/remote_clusters.mdx +++ b/api_docs/remote_clusters.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/remoteClusters title: "remoteClusters" image: https://source.unsplash.com/400x175/?github description: API docs for the remoteClusters plugin -date: 2023-04-18 +date: 2023-04-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'remoteClusters'] --- import remoteClustersObj from './remote_clusters.devdocs.json'; diff --git a/api_docs/reporting.mdx b/api_docs/reporting.mdx index cb79cce43c521..f38277af4ee88 100644 --- a/api_docs/reporting.mdx +++ b/api_docs/reporting.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/reporting title: "reporting" image: https://source.unsplash.com/400x175/?github description: API docs for the reporting plugin -date: 2023-04-18 +date: 2023-04-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'reporting'] --- import reportingObj from './reporting.devdocs.json'; diff --git a/api_docs/rollup.mdx b/api_docs/rollup.mdx index 465dff3eeb282..90c95d189b35b 100644 --- a/api_docs/rollup.mdx +++ b/api_docs/rollup.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/rollup title: "rollup" image: https://source.unsplash.com/400x175/?github description: API docs for the rollup plugin -date: 2023-04-18 +date: 2023-04-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'rollup'] --- import rollupObj from './rollup.devdocs.json'; diff --git a/api_docs/rule_registry.devdocs.json b/api_docs/rule_registry.devdocs.json index 6b966ac060602..a7c14ef74b66d 100644 --- a/api_docs/rule_registry.devdocs.json +++ b/api_docs/rule_registry.devdocs.json @@ -314,7 +314,7 @@ "description": [], "signature": [ "({ caseId, alerts }: ", - "RemoveAlertsFromCaseOptions", + "RemoveCaseIdFromAlertsOptions", ") => Promise" ], "path": "x-pack/plugins/rule_registry/server/alert_data_client/alerts_client.ts", @@ -329,7 +329,7 @@ "label": "{ caseId, alerts }", "description": [], "signature": [ - "RemoveAlertsFromCaseOptions" + "RemoveCaseIdFromAlertsOptions" ], "path": "x-pack/plugins/rule_registry/server/alert_data_client/alerts_client.ts", "deprecated": false, @@ -339,6 +339,50 @@ ], "returnComment": [] }, + { + "parentPluginId": "ruleRegistry", + "id": "def-server.AlertsClient.removeCaseIdsFromAllAlerts", + "type": "Function", + "tags": [], + "label": "removeCaseIdsFromAllAlerts", + "description": [], + "signature": [ + "({ caseIds }: { caseIds: string[]; }) => Promise" + ], + "path": "x-pack/plugins/rule_registry/server/alert_data_client/alerts_client.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "ruleRegistry", + "id": "def-server.AlertsClient.removeCaseIdsFromAllAlerts.$1", + "type": "Object", + "tags": [], + "label": "{ caseIds }", + "description": [], + "path": "x-pack/plugins/rule_registry/server/alert_data_client/alerts_client.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "ruleRegistry", + "id": "def-server.AlertsClient.removeCaseIdsFromAllAlerts.$1.caseIds", + "type": "Array", + "tags": [], + "label": "caseIds", + "description": [], + "signature": [ + "string[]" + ], + "path": "x-pack/plugins/rule_registry/server/alert_data_client/alerts_client.ts", + "deprecated": false, + "trackAdoption": false + } + ] + } + ], + "returnComment": [] + }, { "parentPluginId": "ruleRegistry", "id": "def-server.AlertsClient.find", diff --git a/api_docs/rule_registry.mdx b/api_docs/rule_registry.mdx index 0c3666ca0988c..03e511b5dd320 100644 --- a/api_docs/rule_registry.mdx +++ b/api_docs/rule_registry.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/ruleRegistry title: "ruleRegistry" image: https://source.unsplash.com/400x175/?github description: API docs for the ruleRegistry plugin -date: 2023-04-18 +date: 2023-04-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'ruleRegistry'] --- import ruleRegistryObj from './rule_registry.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 | |-------------------|-----------|------------------------|-----------------| -| 260 | 0 | 231 | 14 | +| 263 | 0 | 234 | 14 | ## Server diff --git a/api_docs/runtime_fields.mdx b/api_docs/runtime_fields.mdx index d8d18ebab68ce..abc334737e1d2 100644 --- a/api_docs/runtime_fields.mdx +++ b/api_docs/runtime_fields.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/runtimeFields title: "runtimeFields" image: https://source.unsplash.com/400x175/?github description: API docs for the runtimeFields plugin -date: 2023-04-18 +date: 2023-04-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'runtimeFields'] --- import runtimeFieldsObj from './runtime_fields.devdocs.json'; diff --git a/api_docs/saved_objects.mdx b/api_docs/saved_objects.mdx index 75f04d6d2a9ab..1f7640d60a3e5 100644 --- a/api_docs/saved_objects.mdx +++ b/api_docs/saved_objects.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/savedObjects title: "savedObjects" image: https://source.unsplash.com/400x175/?github description: API docs for the savedObjects plugin -date: 2023-04-18 +date: 2023-04-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'savedObjects'] --- import savedObjectsObj from './saved_objects.devdocs.json'; diff --git a/api_docs/saved_objects_finder.mdx b/api_docs/saved_objects_finder.mdx index 944092f2f1730..9f9007d746036 100644 --- a/api_docs/saved_objects_finder.mdx +++ b/api_docs/saved_objects_finder.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/savedObjectsFinder title: "savedObjectsFinder" image: https://source.unsplash.com/400x175/?github description: API docs for the savedObjectsFinder plugin -date: 2023-04-18 +date: 2023-04-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'savedObjectsFinder'] --- import savedObjectsFinderObj from './saved_objects_finder.devdocs.json'; diff --git a/api_docs/saved_objects_management.mdx b/api_docs/saved_objects_management.mdx index 46da841238f33..cafcf387064c8 100644 --- a/api_docs/saved_objects_management.mdx +++ b/api_docs/saved_objects_management.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/savedObjectsManagement title: "savedObjectsManagement" image: https://source.unsplash.com/400x175/?github description: API docs for the savedObjectsManagement plugin -date: 2023-04-18 +date: 2023-04-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'savedObjectsManagement'] --- import savedObjectsManagementObj from './saved_objects_management.devdocs.json'; diff --git a/api_docs/saved_objects_tagging.mdx b/api_docs/saved_objects_tagging.mdx index 97ffc99a1d799..73795b54fde91 100644 --- a/api_docs/saved_objects_tagging.mdx +++ b/api_docs/saved_objects_tagging.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/savedObjectsTagging title: "savedObjectsTagging" image: https://source.unsplash.com/400x175/?github description: API docs for the savedObjectsTagging plugin -date: 2023-04-18 +date: 2023-04-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'savedObjectsTagging'] --- import savedObjectsTaggingObj from './saved_objects_tagging.devdocs.json'; diff --git a/api_docs/saved_objects_tagging_oss.mdx b/api_docs/saved_objects_tagging_oss.mdx index a8b1ad9fdf80a..8fe115ad61b6b 100644 --- a/api_docs/saved_objects_tagging_oss.mdx +++ b/api_docs/saved_objects_tagging_oss.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/savedObjectsTaggingOss title: "savedObjectsTaggingOss" image: https://source.unsplash.com/400x175/?github description: API docs for the savedObjectsTaggingOss plugin -date: 2023-04-18 +date: 2023-04-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'savedObjectsTaggingOss'] --- import savedObjectsTaggingOssObj from './saved_objects_tagging_oss.devdocs.json'; diff --git a/api_docs/saved_search.mdx b/api_docs/saved_search.mdx index 64dce1aa69699..b20decae874f6 100644 --- a/api_docs/saved_search.mdx +++ b/api_docs/saved_search.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/savedSearch title: "savedSearch" image: https://source.unsplash.com/400x175/?github description: API docs for the savedSearch plugin -date: 2023-04-18 +date: 2023-04-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'savedSearch'] --- import savedSearchObj from './saved_search.devdocs.json'; diff --git a/api_docs/screenshot_mode.mdx b/api_docs/screenshot_mode.mdx index 2e34968c8916b..5d6890995a537 100644 --- a/api_docs/screenshot_mode.mdx +++ b/api_docs/screenshot_mode.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/screenshotMode title: "screenshotMode" image: https://source.unsplash.com/400x175/?github description: API docs for the screenshotMode plugin -date: 2023-04-18 +date: 2023-04-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'screenshotMode'] --- import screenshotModeObj from './screenshot_mode.devdocs.json'; diff --git a/api_docs/screenshotting.mdx b/api_docs/screenshotting.mdx index ec45ea848d125..1a81510cb27e3 100644 --- a/api_docs/screenshotting.mdx +++ b/api_docs/screenshotting.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/screenshotting title: "screenshotting" image: https://source.unsplash.com/400x175/?github description: API docs for the screenshotting plugin -date: 2023-04-18 +date: 2023-04-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'screenshotting'] --- import screenshottingObj from './screenshotting.devdocs.json'; diff --git a/api_docs/security.mdx b/api_docs/security.mdx index 1698f95c19367..dad65436a8394 100644 --- a/api_docs/security.mdx +++ b/api_docs/security.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/security title: "security" image: https://source.unsplash.com/400x175/?github description: API docs for the security plugin -date: 2023-04-18 +date: 2023-04-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'security'] --- import securityObj from './security.devdocs.json'; diff --git a/api_docs/security_solution.devdocs.json b/api_docs/security_solution.devdocs.json index 3c468e74d4b85..7ceeedb3fb56d 100644 --- a/api_docs/security_solution.devdocs.json +++ b/api_docs/security_solution.devdocs.json @@ -95,7 +95,7 @@ "label": "experimentalFeatures", "description": [], "signature": [ - "{ readonly tGridEnabled: boolean; readonly tGridEventRenderedViewEnabled: boolean; readonly excludePoliciesInFilterEnabled: boolean; readonly kubernetesEnabled: boolean; readonly disableIsolationUIPendingStatuses: boolean; readonly pendingActionResponsesWithAck: boolean; readonly policyListEnabled: boolean; readonly policyResponseInFleetEnabled: boolean; readonly chartEmbeddablesEnabled: boolean; readonly donutChartEmbeddablesEnabled: boolean; readonly alertsPreviewChartEmbeddablesEnabled: boolean; readonly previewTelemetryUrlEnabled: boolean; readonly responseActionsConsoleEnabled: boolean; readonly insightsRelatedAlertsByProcessAncestry: boolean; readonly extendedRuleExecutionLoggingEnabled: boolean; readonly prebuiltRulesNewUpgradeAndInstallationWorkflowsEnabled: boolean; readonly socTrendsEnabled: boolean; readonly responseActionsEnabled: boolean; readonly endpointResponseActionsEnabled: boolean; readonly endpointRbacEnabled: boolean; readonly endpointRbacV1Enabled: boolean; readonly alertDetailsPageEnabled: boolean; readonly responseActionGetFileEnabled: boolean; readonly responseActionExecuteEnabled: boolean; readonly alertsPageChartsEnabled: boolean; readonly alertTypeEnabled: boolean; readonly securityFlyoutEnabled: boolean; readonly riskyHostsEnabled: boolean; readonly riskyUsersEnabled: boolean; readonly alertsPageFiltersEnabled: boolean; }" + "{ readonly tGridEnabled: boolean; readonly tGridEventRenderedViewEnabled: boolean; readonly excludePoliciesInFilterEnabled: boolean; readonly kubernetesEnabled: boolean; readonly disableIsolationUIPendingStatuses: boolean; readonly pendingActionResponsesWithAck: boolean; readonly policyListEnabled: boolean; readonly policyResponseInFleetEnabled: boolean; readonly chartEmbeddablesEnabled: boolean; readonly donutChartEmbeddablesEnabled: boolean; readonly alertsPreviewChartEmbeddablesEnabled: boolean; readonly previewTelemetryUrlEnabled: boolean; readonly responseActionsConsoleEnabled: boolean; readonly insightsRelatedAlertsByProcessAncestry: boolean; readonly extendedRuleExecutionLoggingEnabled: boolean; readonly prebuiltRulesNewUpgradeAndInstallationWorkflowsEnabled: boolean; readonly socTrendsEnabled: boolean; readonly responseActionsEnabled: boolean; readonly endpointResponseActionsEnabled: boolean; readonly endpointRbacEnabled: boolean; readonly endpointRbacV1Enabled: boolean; readonly alertDetailsPageEnabled: boolean; readonly responseActionGetFileEnabled: boolean; readonly responseActionExecuteEnabled: boolean; readonly alertsPageChartsEnabled: boolean; readonly alertTypeEnabled: boolean; readonly securityFlyoutEnabled: boolean; readonly riskyHostsEnabled: boolean; readonly riskyUsersEnabled: boolean; readonly alertsPageFiltersEnabled: boolean; readonly newUserDetailsFlyout: boolean; }" ], "path": "x-pack/plugins/security_solution/public/plugin.tsx", "deprecated": false, diff --git a/api_docs/security_solution.mdx b/api_docs/security_solution.mdx index af52d6774356e..8ad48aa527f9a 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: 2023-04-18 +date: 2023-04-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'securitySolution'] --- import securitySolutionObj from './security_solution.devdocs.json'; diff --git a/api_docs/session_view.mdx b/api_docs/session_view.mdx index 4214cc36d11c5..a9735544749f3 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: 2023-04-18 +date: 2023-04-19 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 0c0fe95e8eb93..b747319f1e8dc 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: 2023-04-18 +date: 2023-04-19 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 89d8fd224eaaa..6db34a3fb6340 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: 2023-04-18 +date: 2023-04-19 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 0ae0d52259d7d..a0abb5235fc22 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: 2023-04-18 +date: 2023-04-19 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 5c08c52b7f8d1..54ceca2baa1ac 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: 2023-04-18 +date: 2023-04-19 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 4c7ac9b47cfbe..f253101036027 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: 2023-04-18 +date: 2023-04-19 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 94c77afcffa2a..fa4754530cfe1 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: 2023-04-18 +date: 2023-04-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'taskManager'] --- import taskManagerObj from './task_manager.devdocs.json'; diff --git a/api_docs/telemetry.mdx b/api_docs/telemetry.mdx index f4b92ece0e010..1129c23641775 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: 2023-04-18 +date: 2023-04-19 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 2af3fecb0bdbb..2bd33bd8db740 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: 2023-04-18 +date: 2023-04-19 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 310868095e02b..3536a151b1b87 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: 2023-04-18 +date: 2023-04-19 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 bde4e1fa26213..ffa3e18a9de78 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: 2023-04-18 +date: 2023-04-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'telemetryManagementSection'] --- import telemetryManagementSectionObj from './telemetry_management_section.devdocs.json'; diff --git a/api_docs/threat_intelligence.mdx b/api_docs/threat_intelligence.mdx index 6649db6849400..5dc2189325209 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: 2023-04-18 +date: 2023-04-19 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 4419699db323b..f139696fae67d 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: 2023-04-18 +date: 2023-04-19 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 628c255d1b188..3a23c501e9939 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: 2023-04-18 +date: 2023-04-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'transform'] --- import transformObj from './transform.devdocs.json'; diff --git a/api_docs/triggers_actions_ui.mdx b/api_docs/triggers_actions_ui.mdx index c6ad3886c12b0..8d2457325cc8f 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: 2023-04-18 +date: 2023-04-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'triggersActionsUi'] --- import triggersActionsUiObj from './triggers_actions_ui.devdocs.json'; diff --git a/api_docs/ui_actions.mdx b/api_docs/ui_actions.mdx index a2cfa74cfd650..d5cc70c49e339 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: 2023-04-18 +date: 2023-04-19 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 bb9a992413684..205ef372f2cf8 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: 2023-04-18 +date: 2023-04-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'uiActionsEnhanced'] --- import uiActionsEnhancedObj from './ui_actions_enhanced.devdocs.json'; diff --git a/api_docs/unified_field_list.mdx b/api_docs/unified_field_list.mdx index 0e8df430367a3..b87c428bf726f 100644 --- a/api_docs/unified_field_list.mdx +++ b/api_docs/unified_field_list.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/unifiedFieldList title: "unifiedFieldList" image: https://source.unsplash.com/400x175/?github description: API docs for the unifiedFieldList plugin -date: 2023-04-18 +date: 2023-04-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'unifiedFieldList'] --- import unifiedFieldListObj from './unified_field_list.devdocs.json'; diff --git a/api_docs/unified_histogram.mdx b/api_docs/unified_histogram.mdx index 9ab0773517528..78d219b601783 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: 2023-04-18 +date: 2023-04-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'unifiedHistogram'] --- import unifiedHistogramObj from './unified_histogram.devdocs.json'; diff --git a/api_docs/unified_search.mdx b/api_docs/unified_search.mdx index 6d31cdbf6b69e..decb9376b9b01 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: 2023-04-18 +date: 2023-04-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'unifiedSearch'] --- import unifiedSearchObj from './unified_search.devdocs.json'; diff --git a/api_docs/unified_search_autocomplete.mdx b/api_docs/unified_search_autocomplete.mdx index 6b324846fbb51..23dac2d6d06ae 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: 2023-04-18 +date: 2023-04-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'unifiedSearch.autocomplete'] --- import unifiedSearchAutocompleteObj from './unified_search_autocomplete.devdocs.json'; diff --git a/api_docs/url_forwarding.mdx b/api_docs/url_forwarding.mdx index f0438b8d54d73..a131918660baf 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: 2023-04-18 +date: 2023-04-19 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 d4c77908e9162..5323eb431a1db 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: 2023-04-18 +date: 2023-04-19 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 bba75c953766b..55579684c8294 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: 2023-04-18 +date: 2023-04-19 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 d5785fa647c1d..19e4daeb84ec2 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: 2023-04-18 +date: 2023-04-19 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 301bc3ea87a6a..5cbc1fabc9bdf 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: 2023-04-18 +date: 2023-04-19 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 274f2b0cdd702..ede12c188e2e3 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: 2023-04-18 +date: 2023-04-19 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 303568940f534..d9697ae33c35d 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: 2023-04-18 +date: 2023-04-19 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 0a9b787d4a65e..9177c2197fbb7 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: 2023-04-18 +date: 2023-04-19 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 7bdb30db5dbe4..502100b2541ef 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: 2023-04-18 +date: 2023-04-19 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 4df03c3b1d319..01ec3973ced2f 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: 2023-04-18 +date: 2023-04-19 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 2ae8799970648..2a75cae2ee36d 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: 2023-04-18 +date: 2023-04-19 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 56a01202a9b99..88c97e8638f13 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: 2023-04-18 +date: 2023-04-19 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 894c6fe49c6a7..b3f1e332f5e8a 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: 2023-04-18 +date: 2023-04-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'visTypeXy'] --- import visTypeXyObj from './vis_type_xy.devdocs.json'; diff --git a/api_docs/visualizations.mdx b/api_docs/visualizations.mdx index d551e436f7742..1d18208d4c2fd 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: 2023-04-18 +date: 2023-04-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'visualizations'] --- import visualizationsObj from './visualizations.devdocs.json'; From ff11323011882dd8b828a359b1da9677faa471a7 Mon Sep 17 00:00:00 2001 From: Julia Rechkunova Date: Wed, 19 Apr 2023 08:45:26 +0200 Subject: [PATCH 20/78] [Discover] Unskip context filters tests (#154562) Closes https://github.com/elastic/kibana/issues/154387 250x https://buildkite.com/elastic/kibana-flaky-test-suite-runner/builds/2147 --- test/functional/apps/context/_filters.ts | 10 ++++++++-- test/functional/services/data_grid.ts | 2 ++ 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/test/functional/apps/context/_filters.ts b/test/functional/apps/context/_filters.ts index b3b8cc0a1fd62..9aff3f6c805fc 100644 --- a/test/functional/apps/context/_filters.ts +++ b/test/functional/apps/context/_filters.ts @@ -20,12 +20,18 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const filterBar = getService('filterBar'); const retry = getService('retry'); const browser = getService('browser'); + const kibanaServer = getService('kibanaServer'); const PageObjects = getPageObjects(['common', 'context']); const testSubjects = getService('testSubjects'); - // FLAKY: https://github.com/elastic/kibana/issues/154387 - describe.skip('context filters', function contextSize() { + describe('context filters', function contextSize() { + before(async function () { + await kibanaServer.uiSettings.update({ + 'discover:rowHeightOption': 0, // to have more grid rows visible at once + }); + }); + beforeEach(async function () { await PageObjects.context.navigateTo(TEST_INDEX_PATTERN, TEST_ANCHOR_ID, { columns: TEST_COLUMN_NAMES, diff --git a/test/functional/services/data_grid.ts b/test/functional/services/data_grid.ts index 7fde46031e737..1f022c890b724 100644 --- a/test/functional/services/data_grid.ts +++ b/test/functional/services/data_grid.ts @@ -207,6 +207,8 @@ export class DataGridService extends FtrService { ? '~docTableExpandToggleColumnAnchor' : '~docTableExpandToggleColumn'; const toggle = await row[0].findByTestSubject(testSubj); + + await toggle.scrollIntoViewIfNecessary(); await toggle.click(); } From 0a120ab8ffb218a7c177a399ad456190aada49cf Mon Sep 17 00:00:00 2001 From: Shahzad Date: Wed, 19 Apr 2023 08:57:46 +0200 Subject: [PATCH 21/78] [Synthetics] Fix pending count in case of location filtering (#155200) --- .../monitors_page/overview/overview_page.tsx | 2 +- .../server/routes/overview_status/overview_status.ts | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/overview/overview_page.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/overview/overview_page.tsx index 14b5bbf0cfb57..a98f78249adbe 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/overview/overview_page.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/overview/overview_page.tsx @@ -118,7 +118,7 @@ export const OverviewPage: React.FC = () => { {!noMonitorFound ? ( <> - + diff --git a/x-pack/plugins/synthetics/server/routes/overview_status/overview_status.ts b/x-pack/plugins/synthetics/server/routes/overview_status/overview_status.ts index 11ae9ff793e21..f8c55ba4fae39 100644 --- a/x-pack/plugins/synthetics/server/routes/overview_status/overview_status.ts +++ b/x-pack/plugins/synthetics/server/routes/overview_status/overview_status.ts @@ -39,7 +39,9 @@ export function periodToMs(schedule: { number: string; unit: Unit }) { export async function getStatus(context: RouteContext, params: OverviewStatusQuery) { const { uptimeEsClient, syntheticsMonitorClient, savedObjectsClient, server } = context; - const { query, locations: queryLocations, scopeStatusByLocation = true } = params; + const { query, locations: qLocations, scopeStatusByLocation = true } = params; + + const queryLocations = qLocations && !Array.isArray(qLocations) ? [qLocations] : qLocations; /** * Walk through all monitor saved objects, bucket IDs by disabled/enabled status. * @@ -85,11 +87,9 @@ export async function getStatus(context: RouteContext, params: OverviewStatusQue ); // Account for locations filter - const queryLocationsArray = - queryLocations && !Array.isArray(queryLocations) ? [queryLocations] : queryLocations; const listOfLocationAfterFilter = - queryLocationsArray && scopeStatusByLocation - ? intersection(listOfLocations, queryLocationsArray) + queryLocations && scopeStatusByLocation + ? intersection(listOfLocations, queryLocations) : listOfLocations; const range = { From 8b6146d1836696ac894dfb017a4fdd777b79ee35 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Efe=20G=C3=BCrkan=20YALAMAN?= Date: Wed, 19 Apr 2023 10:04:42 +0200 Subject: [PATCH 22/78] [Enterprise Search] Fix Connector scheduling show week information correctly (#155191) ## Summary https://user-images.githubusercontent.com/1410658/232858008-ee843f6e-dfb4-40f1-b33f-66fd90bc8d2e.mov When weekly scheduling selected we were showing the wrong information. This fixes cron string parsing and shows correct information. ## Release note Fixes connector scheduling now showing correct setting when weekly intervals selected. ### Checklist Delete any items that are not applicable to this PR. - [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 --- .../shared/cron_editor/enterprise_search_cron_editor.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/cron_editor/enterprise_search_cron_editor.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/cron_editor/enterprise_search_cron_editor.tsx index f0e2d371dc081..48b402436f69a 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/cron_editor/enterprise_search_cron_editor.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/shared/cron_editor/enterprise_search_cron_editor.tsx @@ -66,10 +66,10 @@ function cronToFrequency(cron: string): Frequency { if (fields[3] === '*') { return 'DAY'; } - if (fields[4] === '?') { + if (fields[4] === '*') { return 'WEEK'; } - if (fields[4] === '*') { + if (fields[4] === '?') { return 'MONTH'; } return 'YEAR'; From a1ffdb3414ec950e44f82685ff2b8544d231f1ce Mon Sep 17 00:00:00 2001 From: Sander Philipse <94373878+sphilipse@users.noreply.github.com> Date: Wed, 19 Apr 2023 10:30:12 +0200 Subject: [PATCH 23/78] [Enterprise Search] Clarify timezone use for cron schedules (#155181) ## Summary This updates the scheduling for the Elastic Web Crawler and Connectors to be more specific about timezones. --- .../components/search_index/connector/connector_scheduling.tsx | 2 +- .../automatic_crawl_scheduler/automatic_crawl_scheduler.tsx | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/connector_scheduling.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/connector_scheduling.tsx index 1d93e5c067e11..2feea44c703fb 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/connector_scheduling.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/connector_scheduling.tsx @@ -156,7 +156,7 @@ export const ConnectorSchedulingComponent: React.FC = () => { 'xpack.enterpriseSearch.content.indices.connectorScheduling.configured.description', { defaultMessage: - 'Your connector is configured and deployed. Configure a one-time sync by clicking the Sync button, or enable a recurring sync schedule. ', + 'Your connector is configured and deployed. Configure a one-time sync by clicking the Sync button, or enable a recurring sync schedule. The connector uses UTC as its timezone. ', } )} diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/crawler/automatic_crawl_scheduler/automatic_crawl_scheduler.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/crawler/automatic_crawl_scheduler/automatic_crawl_scheduler.tsx index de6d247bd5bb6..3a4018776a9f7 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/crawler/automatic_crawl_scheduler/automatic_crawl_scheduler.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/crawler/automatic_crawl_scheduler/automatic_crawl_scheduler.tsx @@ -108,7 +108,8 @@ export const AutomaticCrawlScheduler: React.FC = () => { {i18n.translate( 'xpack.enterpriseSearch.crawler.automaticCrawlSchedule.cronSchedulingDescription', { - defaultMessage: 'Define the frequency and time for scheduled crawls', + defaultMessage: + 'Define the frequency and time for scheduled crawls. The crawler uses UTC as its timezone.', } )} From 4b0b8e469fa39ba6760d6b2cdbbfc15df3488a6e Mon Sep 17 00:00:00 2001 From: Sean Story Date: Wed, 19 Apr 2023 03:31:40 -0500 Subject: [PATCH 24/78] Revert "Use unsigned_long for field indexed_document_volume" (#155199) Reverts elastic/kibana#155014 See: https://github.com/elastic/connectors-python/issues/735#issuecomment-1513725750 --- .../server/index_management/setup_indices.test.ts | 2 +- .../enterprise_search/server/index_management/setup_indices.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/x-pack/plugins/enterprise_search/server/index_management/setup_indices.test.ts b/x-pack/plugins/enterprise_search/server/index_management/setup_indices.test.ts index a62333a9abd5a..525434e326a3b 100644 --- a/x-pack/plugins/enterprise_search/server/index_management/setup_indices.test.ts +++ b/x-pack/plugins/enterprise_search/server/index_management/setup_indices.test.ts @@ -214,7 +214,7 @@ describe('Setup Indices', () => { deleted_document_count: { type: 'integer' }, error: { type: 'keyword' }, indexed_document_count: { type: 'integer' }, - indexed_document_volume: { type: 'unsigned_long' }, + indexed_document_volume: { type: 'integer' }, last_seen: { type: 'date' }, metadata: { type: 'object' }, started_at: { type: 'date' }, diff --git a/x-pack/plugins/enterprise_search/server/index_management/setup_indices.ts b/x-pack/plugins/enterprise_search/server/index_management/setup_indices.ts index c660ac2aa0e5b..050b85a872c01 100644 --- a/x-pack/plugins/enterprise_search/server/index_management/setup_indices.ts +++ b/x-pack/plugins/enterprise_search/server/index_management/setup_indices.ts @@ -238,7 +238,7 @@ const indices: IndexDefinition[] = [ deleted_document_count: { type: 'integer' }, error: { type: 'keyword' }, indexed_document_count: { type: 'integer' }, - indexed_document_volume: { type: 'unsigned_long' }, + indexed_document_volume: { type: 'integer' }, last_seen: { type: 'date' }, metadata: { type: 'object' }, started_at: { type: 'date' }, From 533381eab7d5329c3583ddd045e61414f588e446 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20R=C3=BChsen?= Date: Wed, 19 Apr 2023 10:38:49 +0200 Subject: [PATCH 25/78] [Fleet] Support for Profiling symbolization (#155115) This fixes Universal Profiling data stream and index pattern from `profiling-*-*` to `profiling-*` in the fleet-server and logstash key generator. Additionally, this adds the `.profiling-*` index pattern. We use this for ILM / roll-over of the k/v indices, but want to hide these type of indices from the user. --- .../fleet/server/services/api_keys/logstash_api_keys.ts | 6 ++++-- x-pack/plugins/fleet/server/services/data_streams.ts | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/x-pack/plugins/fleet/server/services/api_keys/logstash_api_keys.ts b/x-pack/plugins/fleet/server/services/api_keys/logstash_api_keys.ts index f072bf0777566..66888223b02d1 100644 --- a/x-pack/plugins/fleet/server/services/api_keys/logstash_api_keys.ts +++ b/x-pack/plugins/fleet/server/services/api_keys/logstash_api_keys.ts @@ -24,7 +24,8 @@ export async function canCreateLogstashApiKey(esClient: ElasticsearchClient) { 'synthetics-*-*', '.logs-endpoint.diagnostic.collection-*', '.logs-endpoint.action.responses-*', - 'profiling-*-*', + 'profiling-*', + '.profiling-*', ], privileges: ['auto_configure', 'create_doc'], }, @@ -59,7 +60,8 @@ export async function generateLogstashApiKey(esClient: ElasticsearchClient) { 'synthetics-*-*', '.logs-endpoint.diagnostic.collection-*', '.logs-endpoint.action.responses-*', - 'profiling-*-*', + 'profiling-*', + '.profiling-*', ], privileges: ['auto_configure', 'create_doc'], }, diff --git a/x-pack/plugins/fleet/server/services/data_streams.ts b/x-pack/plugins/fleet/server/services/data_streams.ts index 0cf7568c94772..6dd60a4e0be1e 100644 --- a/x-pack/plugins/fleet/server/services/data_streams.ts +++ b/x-pack/plugins/fleet/server/services/data_streams.ts @@ -8,7 +8,7 @@ import type { IndicesDataStream, IndicesIndexTemplate } from '@elastic/elasticsearch/lib/api/types'; import type { ElasticsearchClient } from '@kbn/core/server'; -const DATA_STREAM_INDEX_PATTERN = 'logs-*-*,metrics-*-*,traces-*-*,synthetics-*-*,profiling-*-*'; +const DATA_STREAM_INDEX_PATTERN = 'logs-*-*,metrics-*-*,traces-*-*,synthetics-*-*,profiling-*'; class DataStreamService { public async getAllFleetDataStreams(esClient: ElasticsearchClient) { From 153e1fcf45e320ac4dd47ca026ea90bcaacb08ed Mon Sep 17 00:00:00 2001 From: Yngrid Coello Date: Wed, 19 Apr 2023 10:49:10 +0200 Subject: [PATCH 26/78] [APM] plugin description (#155041) While I was checking [some documentation](https://www.elastic.co/guide/en/kibana/current/plugin-list.html) I discovered APM plugin description was `undefined` image --- docs/developer/plugin-list.asciidoc | 2 +- x-pack/plugins/apm/readme.md | 14 +++++++++----- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/docs/developer/plugin-list.asciidoc b/docs/developer/plugin-list.asciidoc index 572452cec368c..1755f65193276 100644 --- a/docs/developer/plugin-list.asciidoc +++ b/docs/developer/plugin-list.asciidoc @@ -431,7 +431,7 @@ The plugin exposes the static DefaultEditorController class to consume. |{kib-repo}blob/{branch}/x-pack/plugins/apm/readme.md[apm] -|undefined +|This plugin provides access to App Monitoring features provided by Elastic. It allows you to monitor your software services and applications in real-time; visualize detailed performance information on your services, identify and analyze errors, and monitor host-level and APM agent-specific metrics like JVM and Go runtime metrics. |{kib-repo}blob/{branch}/x-pack/plugins/asset_manager/README.md[assetManager] diff --git a/x-pack/plugins/apm/readme.md b/x-pack/plugins/apm/readme.md index fb7cf20732bf3..51164460ce8be 100644 --- a/x-pack/plugins/apm/readme.md +++ b/x-pack/plugins/apm/readme.md @@ -1,21 +1,25 @@ -# Documentation for APM UI developers +# APM -## Getting started +This plugin provides access to App Monitoring features provided by Elastic. It allows you to monitor your software services and applications in real-time; visualize detailed performance information on your services, identify and analyze errors, and monitor host-level and APM agent-specific metrics like JVM and Go runtime metrics. + +## Documentation for APM UI developers + +### Getting started - [Local setup](./dev_docs/local_setup.md) - [Testing (unit, api, e2e, storybook)](./dev_docs/testing.md) - [Linting (typescript, eslint, prettier)](./dev_docs/linting.md) -## APM concepts +### APM concepts - [Queries and data model](./dev_docs/apm_queries.md) - [Telemetry](./dev_docs/telemetry.md) - [Routing and Linking](./dev_docs/routing_and_linking.md) -## Tooling +### Tooling - [VSCode setup instructions](./dev_docs/vscode_setup.md) - [Github PR commands](./dev_docs/github_commands.md) - [Synthtrace (data generation)](https://github.com/elastic/kibana/blob/main/packages/kbn-apm-synthtrace/README.md) - [Query debugging in development and production](./dev_docs/query_debugging_in_development_and_production.md) -## Other resources +### Other resources - [Official APM UI settings docs](https://www.elastic.co/guide/en/kibana/current/apm-settings-in-kibana.html) - [Reading Material](./dev_docs/learning_material.md) From a055d51627ebbe13eeb7c6032e3893e67be27c6b Mon Sep 17 00:00:00 2001 From: Jean-Louis Leysens Date: Wed, 19 Apr 2023 10:50:54 +0200 Subject: [PATCH 27/78] [HTTP] Only run versioned router response validation in dev (#154907) ## Summary This PR passes the `dev` environment value to `Router` which passes it to `CoreVersionedRouter` so that we can toggle whether we run response validation based on the environment. - [x] Update and add unit and integration tests --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> --- .../src/router.ts | 11 ++- .../core_versioned_route.test.ts | 4 +- .../versioned_router/core_versioned_route.ts | 2 +- .../versioned_router/core_versioned_router.ts | 24 +++--- .../tsconfig.json | 2 +- .../src/http_service.ts | 7 +- .../http/versioned_router.test.ts | 75 +++++++++++++++++-- 7 files changed, 99 insertions(+), 26 deletions(-) diff --git a/packages/core/http/core-http-router-server-internal/src/router.ts b/packages/core/http/core-http-router-server-internal/src/router.ts index d189b04d332a2..4b2519c6bc80f 100644 --- a/packages/core/http/core-http-router-server-internal/src/router.ts +++ b/packages/core/http/core-http-router-server-internal/src/router.ts @@ -119,6 +119,12 @@ function validOptions( return { ...options, body }; } +/** @internal */ +interface RouterOptions { + /** Whether we are running in development */ + isDev: boolean; +} + /** * @internal */ @@ -135,7 +141,8 @@ export class Router + private readonly enhanceWithContext: ContextEnhancer, + private readonly options: RouterOptions = { isDev: false } ) { const buildMethod = (method: Method) => @@ -209,7 +216,7 @@ export class Router = undefined; public get versioned(): VersionedRouter { if (this.versionedRouter === undefined) { - this.versionedRouter = CoreVersionedRouter.from({ router: this }); + this.versionedRouter = CoreVersionedRouter.from({ router: this, isDev: this.options.isDev }); } return this.versionedRouter; } diff --git a/packages/core/http/core-http-router-server-internal/src/versioned_router/core_versioned_route.test.ts b/packages/core/http/core-http-router-server-internal/src/versioned_router/core_versioned_route.test.ts index 337348e425a8f..e95f4cf82a309 100644 --- a/packages/core/http/core-http-router-server-internal/src/versioned_router/core_versioned_route.test.ts +++ b/packages/core/http/core-http-router-server-internal/src/versioned_router/core_versioned_route.test.ts @@ -149,7 +149,7 @@ describe('Versioned route', () => { let validatedOutputBody = false; (router.post as jest.Mock).mockImplementation((opts: unknown, fn) => (handler = fn)); - const versionedRouter = CoreVersionedRouter.from({ router, validateResponses: true }); + const versionedRouter = CoreVersionedRouter.from({ router, isDev: true }); versionedRouter.post({ path: '/test/{id}', access: 'internal' }).addVersion( { version: '1', @@ -194,7 +194,7 @@ describe('Versioned route', () => { ); const kibanaResponse = await handler!( - {} as any, + { core: { env: { mode: { dev: true } } } } as any, createRequest({ version: '1', body: { foo: 1 }, diff --git a/packages/core/http/core-http-router-server-internal/src/versioned_router/core_versioned_route.ts b/packages/core/http/core-http-router-server-internal/src/versioned_router/core_versioned_route.ts index 70035a05455b4..4d73b09563077 100644 --- a/packages/core/http/core-http-router-server-internal/src/versioned_router/core_versioned_route.ts +++ b/packages/core/http/core-http-router-server-internal/src/versioned_router/core_versioned_route.ts @@ -148,7 +148,7 @@ export class CoreVersionedRoute implements VersionedRoute { const response = await handler.fn(ctx, mutableCoreKibanaRequest, res); - if (this.router.validateResponses && validation?.response?.[response.status]) { + if (this.router.isDev && validation?.response?.[response.status]) { const responseValidation = validation.response[response.status]; try { validate( diff --git a/packages/core/http/core-http-router-server-internal/src/versioned_router/core_versioned_router.ts b/packages/core/http/core-http-router-server-internal/src/versioned_router/core_versioned_router.ts index 2893fd325b712..2d148ab461a6e 100644 --- a/packages/core/http/core-http-router-server-internal/src/versioned_router/core_versioned_router.ts +++ b/packages/core/http/core-http-router-server-internal/src/versioned_router/core_versioned_router.ts @@ -11,23 +11,23 @@ import type { VersionedRouter, VersionedRoute, VersionedRouteConfig } from '@kbn import { CoreVersionedRoute } from './core_versioned_route'; import { HandlerResolutionStrategy, Method, VersionedRouterRoute } from './types'; +/** @internal */ +interface Dependencies { + router: IRouter; + defaultHandlerResolutionStrategy?: HandlerResolutionStrategy; + /** Whether Kibana is running in a dev environment */ + isDev?: boolean; +} + export class CoreVersionedRouter implements VersionedRouter { private readonly routes = new Set(); - public static from({ - router, - validateResponses, - defaultHandlerResolutionStrategy = 'oldest', - }: { - router: IRouter; - validateResponses?: boolean; - defaultHandlerResolutionStrategy?: HandlerResolutionStrategy; - }) { - return new CoreVersionedRouter(router, validateResponses, defaultHandlerResolutionStrategy); + public static from({ router, defaultHandlerResolutionStrategy, isDev }: Dependencies) { + return new CoreVersionedRouter(router, defaultHandlerResolutionStrategy, isDev); } private constructor( public readonly router: IRouter, - public readonly validateResponses: boolean = false, - public readonly defaultHandlerResolutionStrategy: HandlerResolutionStrategy = 'oldest' + public readonly defaultHandlerResolutionStrategy: HandlerResolutionStrategy = 'oldest', + public readonly isDev: boolean = false ) {} private registerVersionedRoute = diff --git a/packages/core/http/core-http-router-server-internal/tsconfig.json b/packages/core/http/core-http-router-server-internal/tsconfig.json index 067fd1a5173f4..e4a70cbcddeec 100644 --- a/packages/core/http/core-http-router-server-internal/tsconfig.json +++ b/packages/core/http/core-http-router-server-internal/tsconfig.json @@ -17,7 +17,7 @@ "@kbn/hapi-mocks", "@kbn/core-logging-server-mocks", "@kbn/logging", - "@kbn/core-http-common", + "@kbn/core-http-common" ], "exclude": [ "target/**/*", diff --git a/packages/core/http/core-http-server-internal/src/http_service.ts b/packages/core/http/core-http-server-internal/src/http_service.ts index 45841b72bdbee..5c1ec864b2141 100644 --- a/packages/core/http/core-http-server-internal/src/http_service.ts +++ b/packages/core/http/core-http-server-internal/src/http_service.ts @@ -128,7 +128,8 @@ export class HttpService const router = new Router( path, this.log, - prebootServerRequestHandlerContext.createHandler.bind(null, this.coreContext.coreId) + prebootServerRequestHandlerContext.createHandler.bind(null, this.coreContext.coreId), + { isDev: this.env.mode.dev } ); registerCallback(router); @@ -172,7 +173,9 @@ export class HttpService pluginId: PluginOpaqueId = this.coreContext.coreId ) => { const enhanceHandler = this.requestHandlerContext!.createHandler.bind(null, pluginId); - const router = new Router(path, this.log, enhanceHandler); + const router = new Router(path, this.log, enhanceHandler, { + isDev: this.env.mode.dev, + }); registerRouter(router); return router; }, diff --git a/src/core/server/integration_tests/http/versioned_router.test.ts b/src/core/server/integration_tests/http/versioned_router.test.ts index 9a0b18adceff1..4206bed642bba 100644 --- a/src/core/server/integration_tests/http/versioned_router.test.ts +++ b/src/core/server/integration_tests/http/versioned_router.test.ts @@ -7,6 +7,7 @@ */ import Supertest from 'supertest'; +import { createTestEnv, getEnvOptions } from '@kbn/config-mocks'; import { schema } from '@kbn/config-schema'; import { loggingSystemMock } from '@kbn/core-logging-server-mocks'; import { executionContextServiceMock } from '@kbn/core-execution-context-server-mocks'; @@ -19,12 +20,11 @@ let server: HttpService; let logger: ReturnType; describe('Routing versioned requests', () => { - const contextSetup = contextServiceMock.createSetupContract(); let router: IRouterWithVersion; let supertest: Supertest.SuperTest; const setupDeps = { - context: contextSetup, + context: contextServiceMock.createSetupContract(), executionContext: executionContextServiceMock.createInternalSetupContract(), }; @@ -114,11 +114,17 @@ describe('Routing versioned requests', () => { ); }); - it('returns the expected output for failed validation', async () => { + it('returns the expected responses for failed validation', async () => { router.versioned - .post({ access: 'internal', path: '/test/{id}' }) + .post({ path: '/my-path', access: 'internal' }) + // Bad request validation .addVersion( - { validate: { request: { body: schema.object({ foo: schema.number() }) } }, version: '1' }, + { + validate: { + request: { body: schema.object({ foo: schema.number() }) }, + }, + version: '1', + }, async (ctx, req, res) => { return res.ok({ body: { v: '1' } }); } @@ -128,7 +134,7 @@ describe('Routing versioned requests', () => { await expect( supertest - .post('/test/1') + .post('/my-path') .send({}) .set('Elastic-Api-Version', '1') .expect(400) @@ -159,6 +165,63 @@ describe('Routing versioned requests', () => { ).resolves.toEqual(expect.objectContaining({ 'elastic-api-version': '2020-02-02' })); }); + it('runs response validation when in dev', async () => { + router.versioned + .get({ path: '/my-path', access: 'internal' }) + .addVersion( + { validate: { response: { 200: { body: schema.number() } } }, version: '1' }, + async (ctx, req, res) => { + return res.ok({ body: { v: '1' } }); + } + ); + + await server.start(); + + await expect( + supertest + .get('/my-path') + .set('Elastic-Api-Version', '1') + .expect(500) + .then(({ body }) => body) + ).resolves.toEqual( + expect.objectContaining({ + message: expect.stringMatching(/Failed output validation/), + }) + ); + }); + + it('does not run response validation in prod', async () => { + logger = loggingSystemMock.create(); + await server.stop(); // stop the already started server + server = createHttpServer({ + logger, + env: createTestEnv({ envOptions: getEnvOptions({ cliArgs: { dev: false } }) }), + }); + await server.preboot({ context: contextServiceMock.createPrebootContract() }); + const { server: innerServer, createRouter } = await server.setup(setupDeps); + + router = createRouter('/'); + supertest = Supertest(innerServer.listener); + router.versioned + .get({ path: '/my-path', access: 'internal' }) + .addVersion( + { validate: { response: { 200: { body: schema.number() } } }, version: '1' }, + async (ctx, req, res) => { + return res.ok({ body: { v: '1' } }); + } + ); + + await server.start(); + + await expect( + supertest + .get('/my-path') + .set('Elastic-Api-Version', '1') + .expect(200) + .then(({ body }) => body.v) + ).resolves.toEqual('1'); + }); + it('errors when no handler could be found', async () => { router.versioned.get({ path: '/my-path', access: 'public' }); From c7f95370dfe0b483e85a200d86ebddf13a3226c9 Mon Sep 17 00:00:00 2001 From: Jill Guyonnet Date: Wed, 19 Apr 2023 11:04:28 +0200 Subject: [PATCH 28/78] [Fleet] Fix package policy validation for select variables (#155180) ## Summary There is a bug in package policy validation when the variable is of type `select`: empty strings are not correctly rejected as invalid values. This PR fixes this. Closes https://github.com/elastic/kibana/issues/154905 ### 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 --- .../services/validate_package_policy.test.ts | 21 +++++++++++++++++++ .../services/validate_package_policy.ts | 2 +- 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/x-pack/plugins/fleet/common/services/validate_package_policy.test.ts b/x-pack/plugins/fleet/common/services/validate_package_policy.test.ts index 35c1e8184c931..abf38ea7db0c5 100644 --- a/x-pack/plugins/fleet/common/services/validate_package_policy.test.ts +++ b/x-pack/plugins/fleet/common/services/validate_package_policy.test.ts @@ -968,6 +968,27 @@ describe('Fleet - validatePackagePolicyConfig', () => { expect(res).toEqual(['Invalid value for select type']); }); + it('should return an error message if the value is an empty string', () => { + const res = validatePackagePolicyConfig( + { + type: 'select', + value: '', + }, + { + name: 'myvariable', + type: 'select', + options: [ + { value: 'a', text: 'A' }, + { value: 'b', text: 'B' }, + ], + }, + 'myvariable', + safeLoad + ); + + expect(res).toEqual(['Invalid value for select type']); + }); + it('should accept a select with a valid value', () => { const res = validatePackagePolicyConfig( { diff --git a/x-pack/plugins/fleet/common/services/validate_package_policy.ts b/x-pack/plugins/fleet/common/services/validate_package_policy.ts index 49a59f1e64eb8..f1aa6f3f19640 100644 --- a/x-pack/plugins/fleet/common/services/validate_package_policy.ts +++ b/x-pack/plugins/fleet/common/services/validate_package_policy.ts @@ -337,7 +337,7 @@ export const validatePackagePolicyConfig = ( } } - if (varDef.type === 'select' && parsedValue) { + if (varDef.type === 'select' && parsedValue !== undefined) { if (!varDef.options?.map((o) => o.value).includes(parsedValue)) { errors.push( i18n.translate('xpack.fleet.packagePolicyValidation.invalidSelectValueErrorMessage', { From 2d04254b9e935245a13a79d153ba7867d0cf8628 Mon Sep 17 00:00:00 2001 From: Candace Park <56409205+parkiino@users.noreply.github.com> Date: Wed, 19 Apr 2023 05:57:33 -0400 Subject: [PATCH 29/78] [Security Solution][Admin][Policy] Add Cloud status to Policy (#155005) ## Summary - [x] Adds cloud status to endpoint integration policy - [x] Update 8.8 migration to include cloud id in meta field - [x] Migration unit test --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: Kevin Logan --- .../security_solution/to_v8_8_0.test.ts | 4 +-- .../migrations/security_solution/to_v8_8_0.ts | 2 +- .../common/endpoint/models/policy_config.ts | 3 ++- .../models/policy_config_helpers.test.ts | 2 +- .../common/endpoint/types/index.ts | 1 + .../policy/store/policy_details/index.test.ts | 2 +- .../plugins/security_solution/public/types.ts | 3 +++ .../endpoint/endpoint_app_context_services.ts | 6 ++++- .../endpoint/lib/policy/license_watch.test.ts | 26 ++++++++++++++++--- .../endpoint/lib/policy/license_watch.ts | 6 +++++ .../server/endpoint/mocks.ts | 2 ++ .../fleet_integration.test.ts | 13 +++++++--- .../fleet_integration/fleet_integration.ts | 10 +++++-- .../handlers/create_default_policy.test.ts | 7 +++-- .../handlers/create_default_policy.ts | 7 +++-- .../security_solution/server/plugin.ts | 2 ++ .../server/plugin_contract.ts | 1 + 17 files changed, 78 insertions(+), 19 deletions(-) diff --git a/x-pack/plugins/fleet/server/saved_objects/migrations/security_solution/to_v8_8_0.test.ts b/x-pack/plugins/fleet/server/saved_objects/migrations/security_solution/to_v8_8_0.test.ts index e360b655f2a61..1e5ede49f2c4b 100644 --- a/x-pack/plugins/fleet/server/saved_objects/migrations/security_solution/to_v8_8_0.test.ts +++ b/x-pack/plugins/fleet/server/saved_objects/migrations/security_solution/to_v8_8_0.test.ts @@ -53,11 +53,11 @@ describe('8.8.0 Endpoint Package Policy migration', () => { }; }; - it('adds license to policy, defaulted to empty string', () => { + it('adds license and cloud status to policy, defaulted to empty string', () => { const initialDoc = policyDoc({}); const migratedDoc = policyDoc({ - meta: { license: '' }, + meta: { license: '', cloud: false }, }); expect(migration(initialDoc, {} as SavedObjectMigrationContext)).toEqual(migratedDoc); diff --git a/x-pack/plugins/fleet/server/saved_objects/migrations/security_solution/to_v8_8_0.ts b/x-pack/plugins/fleet/server/saved_objects/migrations/security_solution/to_v8_8_0.ts index 24b769bcdec9a..954349f89714f 100644 --- a/x-pack/plugins/fleet/server/saved_objects/migrations/security_solution/to_v8_8_0.ts +++ b/x-pack/plugins/fleet/server/saved_objects/migrations/security_solution/to_v8_8_0.ts @@ -24,7 +24,7 @@ export const migratePackagePolicyToV880: SavedObjectMigrationFn { +export const policyFactory = (license = '', cloud = false): PolicyConfig => { return { meta: { license, + cloud, }, windows: { events: { diff --git a/x-pack/plugins/security_solution/common/endpoint/models/policy_config_helpers.test.ts b/x-pack/plugins/security_solution/common/endpoint/models/policy_config_helpers.test.ts index b35bbc190fe43..c59844f70ead9 100644 --- a/x-pack/plugins/security_solution/common/endpoint/models/policy_config_helpers.test.ts +++ b/x-pack/plugins/security_solution/common/endpoint/models/policy_config_helpers.test.ts @@ -119,7 +119,7 @@ describe('Policy Config helpers', () => { // This constant makes sure that if the type `PolicyConfig` is ever modified, // the logic for disabling protections is also modified due to type check. export const eventsOnlyPolicy: PolicyConfig = { - meta: { license: '' }, + meta: { license: '', cloud: false }, windows: { events: { credential_access: true, 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 eb4d5c7421eef..75899b5422039 100644 --- a/x-pack/plugins/security_solution/common/endpoint/types/index.ts +++ b/x-pack/plugins/security_solution/common/endpoint/types/index.ts @@ -924,6 +924,7 @@ type KbnConfigSchemaNonOptionalProps> = Pi export interface PolicyConfig { meta: { license: string; + cloud: boolean; }; windows: { advanced?: { diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_details/index.test.ts b/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_details/index.test.ts index f4035498f81df..d2876f1bcdd24 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_details/index.test.ts +++ b/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_details/index.test.ts @@ -269,7 +269,7 @@ describe('policy details: ', () => { }, policy: { value: { - meta: { license: '' }, + meta: { license: '', cloud: false }, windows: { events: { credential_access: true, diff --git a/x-pack/plugins/security_solution/public/types.ts b/x-pack/plugins/security_solution/public/types.ts index a9566fabc6c1a..e6e10a9c5b3dd 100644 --- a/x-pack/plugins/security_solution/public/types.ts +++ b/x-pack/plugins/security_solution/public/types.ts @@ -33,6 +33,7 @@ import type { LicensingPluginStart, LicensingPluginSetup } from '@kbn/licensing- import type { DashboardStart } from '@kbn/dashboard-plugin/public'; import type { IndexPatternFieldEditorStart } from '@kbn/data-view-field-editor-plugin/public'; import type { UnifiedSearchPublicPluginStart } from '@kbn/unified-search-plugin/public'; +import type { CloudSetup, CloudStart } from '@kbn/cloud-plugin/public'; import type { CloudDefendPluginStart } from '@kbn/cloud-defend-plugin/public'; import type { CspClientPluginStart } from '@kbn/cloud-security-posture-plugin/public'; import type { ApmBase } from '@elastic/apm-rum'; @@ -65,6 +66,7 @@ import type { TelemetryClientStart } from './common/lib/telemetry'; import type { Dashboards } from './dashboards'; export interface SetupPlugins { + cloud?: CloudSetup; home?: HomePublicPluginSetup; licensing: LicensingPluginSetup; security: SecurityPluginSetup; @@ -96,6 +98,7 @@ export interface StartPlugins { dataViewFieldEditor: IndexPatternFieldEditorStart; osquery: OsqueryPluginStart; security: SecurityPluginStart; + cloud?: CloudStart; cloudDefend: CloudDefendPluginStart; cloudSecurityPosture: CspClientPluginStart; threatIntelligence: ThreatIntelligencePluginStart; diff --git a/x-pack/plugins/security_solution/server/endpoint/endpoint_app_context_services.ts b/x-pack/plugins/security_solution/server/endpoint/endpoint_app_context_services.ts index 841a27b2650ac..489953a2d96d2 100644 --- a/x-pack/plugins/security_solution/server/endpoint/endpoint_app_context_services.ts +++ b/x-pack/plugins/security_solution/server/endpoint/endpoint_app_context_services.ts @@ -12,6 +12,7 @@ import type { SecurityPluginStart } from '@kbn/security-plugin/server'; import type { FleetStartContract, MessageSigningServiceInterface } from '@kbn/fleet-plugin/server'; import type { PluginStartContract as AlertsPluginStartContract } from '@kbn/alerting-plugin/server'; import { ENDPOINT_HOST_ISOLATION_EXCEPTIONS_LIST_ID } from '@kbn/securitysolution-list-constants'; +import type { CloudSetup } from '@kbn/cloud-plugin/server'; import { getPackagePolicyCreateCallback, getPackagePolicyUpdateCallback, @@ -61,6 +62,7 @@ export interface EndpointAppContextServiceStartContract { experimentalFeatures: ExperimentalFeatures; messageSigningService: MessageSigningServiceInterface | undefined; actionCreateService: ActionCreateService | undefined; + cloud: CloudSetup; } /** @@ -92,6 +94,7 @@ export class EndpointAppContextService { logger, manifestManager, alerting, + cloud, licenseService, exceptionListsClient, featureUsageService, @@ -106,7 +109,8 @@ export class EndpointAppContextService { this.setupDependencies.securitySolutionRequestContextFactory, alerting, licenseService, - exceptionListsClient + exceptionListsClient, + cloud ) ); diff --git a/x-pack/plugins/security_solution/server/endpoint/lib/policy/license_watch.test.ts b/x-pack/plugins/security_solution/server/endpoint/lib/policy/license_watch.test.ts index 9d962bc0e64ce..9c57b8156e3e3 100644 --- a/x-pack/plugins/security_solution/server/endpoint/lib/policy/license_watch.test.ts +++ b/x-pack/plugins/security_solution/server/endpoint/lib/policy/license_watch.test.ts @@ -16,6 +16,7 @@ import { createPackagePolicyServiceMock } from '@kbn/fleet-plugin/server/mocks'; import { PolicyWatcher } from './license_watch'; import type { ILicense } from '@kbn/licensing-plugin/common/types'; import { licenseMock } from '@kbn/licensing-plugin/common/licensing.mock'; +import { cloudMock } from '@kbn/cloud-plugin/server/mocks'; import type { PackagePolicyClient } from '@kbn/fleet-plugin/server'; import type { PackagePolicy } from '@kbn/fleet-plugin/common'; import { createPackagePolicyMock } from '@kbn/fleet-plugin/common/mocks'; @@ -35,6 +36,7 @@ const MockPPWithEndpointPolicy = (cb?: (p: PolicyConfig) => PolicyConfig): Packa describe('Policy-Changing license watcher', () => { const logger = loggingSystemMock.create().get('license_watch.test'); + const cloudServiceMock = cloudMock.createSetup(); const soStartMock = savedObjectsServiceMock.createStartContract(); const esStartMock = elasticsearchServiceMock.createStart(); let packagePolicySvcMock: jest.Mocked; @@ -51,7 +53,13 @@ describe('Policy-Changing license watcher', () => { // mock a license-changing service to test reactivity const licenseEmitter: Subject = new Subject(); const licenseService = new LicenseService(); - const pw = new PolicyWatcher(packagePolicySvcMock, soStartMock, esStartMock, logger); + const pw = new PolicyWatcher( + packagePolicySvcMock, + soStartMock, + esStartMock, + cloudServiceMock, + logger + ); // swap out watch function, just to ensure it gets called when a license change happens const mockWatch = jest.fn(); @@ -96,7 +104,13 @@ describe('Policy-Changing license watcher', () => { perPage: 100, }); - const pw = new PolicyWatcher(packagePolicySvcMock, soStartMock, esStartMock, logger); + const pw = new PolicyWatcher( + packagePolicySvcMock, + soStartMock, + esStartMock, + cloudServiceMock, + logger + ); await pw.watch(Gold); // just manually trigger with a given license expect(packagePolicySvcMock.list.mock.calls.length).toBe(3); // should have asked for 3 pages of resuts @@ -123,7 +137,13 @@ describe('Policy-Changing license watcher', () => { perPage: 100, }); - const pw = new PolicyWatcher(packagePolicySvcMock, soStartMock, esStartMock, logger); + const pw = new PolicyWatcher( + packagePolicySvcMock, + soStartMock, + esStartMock, + cloudServiceMock, + logger + ); // emulate a license change below paid tier await pw.watch(Basic); diff --git a/x-pack/plugins/security_solution/server/endpoint/lib/policy/license_watch.ts b/x-pack/plugins/security_solution/server/endpoint/lib/policy/license_watch.ts index 28a6eb273bcf3..dded85b559c6d 100644 --- a/x-pack/plugins/security_solution/server/endpoint/lib/policy/license_watch.ts +++ b/x-pack/plugins/security_solution/server/endpoint/lib/policy/license_watch.ts @@ -17,6 +17,7 @@ import type { } from '@kbn/core/server'; import type { PackagePolicy } from '@kbn/fleet-plugin/common'; import { PACKAGE_POLICY_SAVED_OBJECT_TYPE } from '@kbn/fleet-plugin/common'; +import type { CloudSetup } from '@kbn/cloud-plugin/server'; import type { PackagePolicyClient } from '@kbn/fleet-plugin/server'; import type { ILicense } from '@kbn/licensing-plugin/common/types'; import { SECURITY_EXTENSION_ID } from '@kbn/core-saved-objects-server'; @@ -34,16 +35,19 @@ export class PolicyWatcher { private policyService: PackagePolicyClient; private subscription: Subscription | undefined; private soStart: SavedObjectsServiceStart; + private cloud: CloudSetup; constructor( policyService: PackagePolicyClient, soStart: SavedObjectsServiceStart, esStart: ElasticsearchServiceStart, + cloud: CloudSetup, logger: Logger ) { this.policyService = policyService; this.esClient = esStart.client.asInternalUser; this.logger = logger; this.soStart = soStart; + this.cloud = cloud; } /** @@ -102,6 +106,8 @@ export class PolicyWatcher { const updatePolicy = getPolicyDataForUpdate(policy); const policyConfig = updatePolicy.inputs[0].config.policy.value; updatePolicy.inputs[0].config.policy.value.meta.license = license.type || ''; + // add cloud info to policy meta + updatePolicy.inputs[0].config.policy.value.meta.cloud = this.cloud?.isCloudEnabled; try { if (!isEndpointPolicyValidForLicense(policyConfig, license)) { diff --git a/x-pack/plugins/security_solution/server/endpoint/mocks.ts b/x-pack/plugins/security_solution/server/endpoint/mocks.ts index 7177c92c961b3..46ffabfb5de17 100644 --- a/x-pack/plugins/security_solution/server/endpoint/mocks.ts +++ b/x-pack/plugins/security_solution/server/endpoint/mocks.ts @@ -27,6 +27,7 @@ import type { import { listMock } from '@kbn/lists-plugin/server/mocks'; import { securityMock } from '@kbn/security-plugin/server/mocks'; import { alertsMock } from '@kbn/alerting-plugin/server/mocks'; +import { cloudMock } from '@kbn/cloud-plugin/server/mocks'; import type { FleetStartContract } from '@kbn/fleet-plugin/server'; import { createPackagePolicyServiceMock, @@ -170,6 +171,7 @@ export const createMockEndpointAppContextServiceStartContract = >(), exceptionListsClient: listMock.getExceptionListClient(), cases: casesMock, + cloud: cloudMock.createSetup(), featureUsageService: createFeatureUsageServiceMock(), experimentalFeatures: createMockConfig().experimentalFeatures, messageSigningService: createMessageSigningServiceMock(), diff --git a/x-pack/plugins/security_solution/server/fleet_integration/fleet_integration.test.ts b/x-pack/plugins/security_solution/server/fleet_integration/fleet_integration.test.ts index 0ffdc09844c9a..30c7e776e718e 100644 --- a/x-pack/plugins/security_solution/server/fleet_integration/fleet_integration.test.ts +++ b/x-pack/plugins/security_solution/server/fleet_integration/fleet_integration.test.ts @@ -17,6 +17,7 @@ import { createNewPackagePolicyMock, deletePackagePolicyMock, } from '@kbn/fleet-plugin/common/mocks'; +import { cloudMock } from '@kbn/cloud-plugin/server/mocks'; import { policyFactory, policyFactoryWithoutPaidFeatures, @@ -68,6 +69,7 @@ describe('ingest_integration tests ', () => { const Platinum = licenseMock.createLicense({ license: { type: 'platinum', mode: 'platinum' } }); const Gold = licenseMock.createLicense({ license: { type: 'gold', mode: 'gold' } }); const generator = new EndpointDocGenerator(); + const cloudService = cloudMock.createSetup(); beforeEach(() => { endpointAppContextMock = createMockEndpointAppContextServiceStartContract(); @@ -92,13 +94,17 @@ describe('ingest_integration tests ', () => { const soClient = savedObjectsClientMock.create(); const esClient = elasticsearchServiceMock.createClusterClient().asInternalUser; - const createNewEndpointPolicyInput = (manifest: ManifestSchema, license = 'platinum') => ({ + const createNewEndpointPolicyInput = ( + manifest: ManifestSchema, + license = 'platinum', + cloud = cloudService.isCloudEnabled + ) => ({ type: 'endpoint', enabled: true, streams: [], config: { integration_config: {}, - policy: { value: disableProtections(policyFactory(license)) }, + policy: { value: disableProtections(policyFactory(license, cloud)) }, artifact_manifest: { value: manifest }, }, }); @@ -111,7 +117,8 @@ describe('ingest_integration tests ', () => { requestContextFactoryMock.create(), endpointAppContextMock.alerting, licenseService, - exceptionListClient + exceptionListClient, + cloudService ); return callback( diff --git a/x-pack/plugins/security_solution/server/fleet_integration/fleet_integration.ts b/x-pack/plugins/security_solution/server/fleet_integration/fleet_integration.ts index a941b8f8aa4ca..fd6f85af1a045 100644 --- a/x-pack/plugins/security_solution/server/fleet_integration/fleet_integration.ts +++ b/x-pack/plugins/security_solution/server/fleet_integration/fleet_integration.ts @@ -20,6 +20,7 @@ import type { PackagePolicy, UpdatePackagePolicy, } from '@kbn/fleet-plugin/common'; +import type { CloudSetup } from '@kbn/cloud-plugin/server'; import type { NewPolicyData, PolicyConfig } from '../../common/endpoint/types'; import type { LicenseService } from '../../common/license'; import type { ManifestManager } from '../endpoint/services'; @@ -52,7 +53,8 @@ export const getPackagePolicyCreateCallback = ( securitySolutionRequestContextFactory: IRequestContextFactory, alerts: AlertsStartContract, licenseService: LicenseService, - exceptionsClient: ExceptionListClient | undefined + exceptionsClient: ExceptionListClient | undefined, + cloud: CloudSetup ): PostPackagePolicyCreateCallback => { return async ( newPackagePolicy, @@ -114,7 +116,11 @@ export const getPackagePolicyCreateCallback = ( ]); // Add the default endpoint security policy - const defaultPolicyValue = createDefaultPolicy(licenseService, endpointIntegrationConfig); + const defaultPolicyValue = createDefaultPolicy( + licenseService, + endpointIntegrationConfig, + cloud + ); return { // We cast the type here so that any changes to the Endpoint diff --git a/x-pack/plugins/security_solution/server/fleet_integration/handlers/create_default_policy.test.ts b/x-pack/plugins/security_solution/server/fleet_integration/handlers/create_default_policy.test.ts index c0b713b1bfa17..867876d6345a3 100644 --- a/x-pack/plugins/security_solution/server/fleet_integration/handlers/create_default_policy.test.ts +++ b/x-pack/plugins/security_solution/server/fleet_integration/handlers/create_default_policy.test.ts @@ -7,6 +7,7 @@ import { Subject } from 'rxjs'; import type { ILicense } from '@kbn/licensing-plugin/common/types'; import { licenseMock } from '@kbn/licensing-plugin/common/licensing.mock'; +import { cloudMock } from '@kbn/cloud-plugin/server/mocks'; import { LicenseService } from '../../../common/license'; import { createDefaultPolicy } from './create_default_policy'; import { ProtectionModes } from '../../../common/endpoint/types'; @@ -19,13 +20,14 @@ import type { } from '../types'; describe('Create Default Policy tests ', () => { + const cloud = cloudMock.createSetup(); const Platinum = licenseMock.createLicense({ license: { type: 'platinum', mode: 'platinum' } }); const Gold = licenseMock.createLicense({ license: { type: 'gold', mode: 'gold' } }); let licenseEmitter: Subject; let licenseService: LicenseService; const createDefaultPolicyCallback = (config: AnyPolicyCreateConfig | undefined): PolicyConfig => { - return createDefaultPolicy(licenseService, config); + return createDefaultPolicy(licenseService, config, cloud); }; beforeEach(() => { @@ -169,8 +171,9 @@ describe('Create Default Policy tests ', () => { const config = createEndpointConfig({ preset: 'EDRComplete' }); const policy = createDefaultPolicyCallback(config); const defaultPolicy = policyFactory(); - // update defaultPolicy w/ platinum license + // update defaultPolicy w/ platinum license & cloud info defaultPolicy.meta.license = 'platinum'; + defaultPolicy.meta.cloud = true; expect(policy).toMatchObject(defaultPolicy); }); }); diff --git a/x-pack/plugins/security_solution/server/fleet_integration/handlers/create_default_policy.ts b/x-pack/plugins/security_solution/server/fleet_integration/handlers/create_default_policy.ts index 17b1972b743d6..52a686d198701 100644 --- a/x-pack/plugins/security_solution/server/fleet_integration/handlers/create_default_policy.ts +++ b/x-pack/plugins/security_solution/server/fleet_integration/handlers/create_default_policy.ts @@ -5,6 +5,7 @@ * 2.0. */ +import type { CloudSetup } from '@kbn/cloud-plugin/server'; import { policyFactory as policyConfigFactory, policyFactoryWithoutPaidFeatures as policyConfigFactoryWithoutPaidFeatures, @@ -25,12 +26,14 @@ import { disableProtections } from '../../../common/endpoint/models/policy_confi */ export const createDefaultPolicy = ( licenseService: LicenseService, - config: AnyPolicyCreateConfig | undefined + config: AnyPolicyCreateConfig | undefined, + cloud: CloudSetup ): PolicyConfig => { const factoryPolicy = policyConfigFactory(); - // Add license information after policy creation + // Add license and cloud information after policy creation factoryPolicy.meta.license = licenseService.getLicenseType(); + factoryPolicy.meta.cloud = cloud?.isCloudEnabled; const defaultPolicyPerType = config?.type === 'cloud' diff --git a/x-pack/plugins/security_solution/server/plugin.ts b/x-pack/plugins/security_solution/server/plugin.ts index 2b6048c122f27..2ec2db2394068 100644 --- a/x-pack/plugins/security_solution/server/plugin.ts +++ b/x-pack/plugins/security_solution/server/plugin.ts @@ -468,6 +468,7 @@ export class Plugin implements ISecuritySolutionPlugin { plugins.fleet.packagePolicyService, core.savedObjects, core.elasticsearch, + plugins.cloud, logger ); this.policyWatcher.start(licenseService); @@ -498,6 +499,7 @@ export class Plugin implements ISecuritySolutionPlugin { manifestManager, registerIngestCallback, licenseService, + cloud: plugins.cloud, exceptionListsClient: exceptionListClient, registerListsServerExtension: this.lists?.registerExtension, featureUsageService, diff --git a/x-pack/plugins/security_solution/server/plugin_contract.ts b/x-pack/plugins/security_solution/server/plugin_contract.ts index 57c05d2490c05..6a06c67a6cb1b 100644 --- a/x-pack/plugins/security_solution/server/plugin_contract.ts +++ b/x-pack/plugins/security_solution/server/plugin_contract.ts @@ -66,6 +66,7 @@ export interface SecuritySolutionPluginSetupDependencies { export interface SecuritySolutionPluginStartDependencies { alerting: AlertingPluginStart; cases?: CasesStart; + cloud: CloudSetup; cloudExperiments?: CloudExperimentsPluginStart; data: DataPluginStart; dataViews: DataViewsPluginStart; From 54757a0db85c18650a5a699aaf354f2b84bef9b1 Mon Sep 17 00:00:00 2001 From: Navarone Feekery <13634519+navarone-feekery@users.noreply.github.com> Date: Wed, 19 Apr 2023 12:02:12 +0200 Subject: [PATCH 30/78] [Enterprise Search] Add tooltip and default value message (#155176) - Adds tooltip to each configurable field label. If tooltip is empty, the tooltip will not appear. - Adds a message below fields that have a `default_value` --- .../common/connectors/native_connectors.ts | 58 +++-- .../common/types/connectors.ts | 13 +- .../__mocks__/search_indices.mock.ts | 9 +- .../__mocks__/view_index.mock.ts | 9 +- .../connector_configuration_field.tsx | 20 +- .../connector_configuration_form.tsx | 42 +++- .../connector_configuration_logic.test.ts | 94 ++++++-- .../connector_configuration_logic.ts | 35 ++- .../plugins/translations/translations/en.json | 213 ++++++++++++++++++ 9 files changed, 431 insertions(+), 62 deletions(-) create mode 100644 x-pack/plugins/translations/translations/en.json diff --git a/x-pack/plugins/enterprise_search/common/connectors/native_connectors.ts b/x-pack/plugins/enterprise_search/common/connectors/native_connectors.ts index aab72dce47f76..8d962ee206f9e 100644 --- a/x-pack/plugins/enterprise_search/common/connectors/native_connectors.ts +++ b/x-pack/plugins/enterprise_search/common/connectors/native_connectors.ts @@ -7,14 +7,15 @@ import { i18n } from '@kbn/i18n'; -import { FeatureName, NativeConnector } from '../types/connectors'; +import { DisplayType, FeatureName, NativeConnector } from '../types/connectors'; export const NATIVE_CONNECTOR_DEFINITIONS: Record = { mongodb: { configuration: { host: { + default_value: '', depends_on: [], - display: 'textbox', + display: DisplayType.TEXTBOX, label: i18n.translate( 'xpack.enterpriseSearch.nativeConnectors.mongodb.configuration.hostLabel', { @@ -25,11 +26,13 @@ export const NATIVE_CONNECTOR_DEFINITIONS: Record; +export enum DisplayType { + TEXTBOX = 'textbox', + TEXTAREA = 'textarea', + NUMERIC = 'numeric', + TOGGLE = 'toggle', + DROPDOWN = 'dropdown', + CHECKBOX = 'checkbox', +} + export interface ConnectorConfigProperties { + default_value: string | number | boolean | null; depends_on: Dependency[]; - display: string; + display: DisplayType; label: string; options: SelectOption[]; order?: number | null; required: boolean; sensitive: boolean; + tooltip: string; value: string | number | boolean | null; } diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/__mocks__/search_indices.mock.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/__mocks__/search_indices.mock.ts index 0ae7543eadd45..5ae2784b52664 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/__mocks__/search_indices.mock.ts +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/__mocks__/search_indices.mock.ts @@ -9,6 +9,7 @@ import { ENTERPRISE_SEARCH_CONNECTOR_CRAWLER_SERVICE_TYPE } from '../../../../co import { ConnectorStatus, + DisplayType, FilteringPolicy, FilteringRuleRule, FilteringValidationState, @@ -34,14 +35,16 @@ export const indices: ElasticsearchIndexWithIngestion[] = [ api_key_id: null, configuration: { foo: { + default_value: '', depends_on: [], - display: 'textbox', + display: DisplayType.TEXTBOX, key: 'foo', label: 'bar', options: [], order: 1, required: false, sensitive: false, + tooltip: '', value: 'barbar', }, }, @@ -142,14 +145,16 @@ export const indices: ElasticsearchIndexWithIngestion[] = [ api_key_id: null, configuration: { foo: { + default_value: '', depends_on: [], - display: 'textbox', + display: DisplayType.TEXTBOX, key: 'foo', label: 'bar', options: [], order: 1, required: false, sensitive: false, + tooltip: '', value: 'barbar', }, }, diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/__mocks__/view_index.mock.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/__mocks__/view_index.mock.ts index 517bad3746bc6..1674f5ca18675 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/__mocks__/view_index.mock.ts +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/__mocks__/view_index.mock.ts @@ -13,6 +13,7 @@ import { FilteringPolicy, FilteringRuleRule, FilteringValidationState, + DisplayType, } from '../../../../common/types/connectors'; import { @@ -44,14 +45,16 @@ export const connectorIndex: ConnectorViewIndex = { api_key_id: null, configuration: { foo: { + default_value: '', depends_on: [], - display: 'textbox', + display: DisplayType.TEXTBOX, key: 'foo', label: 'bar', options: [], order: 1, required: false, sensitive: false, + tooltip: '', value: 'barbar', }, }, @@ -156,14 +159,16 @@ export const crawlerIndex: CrawlerViewIndex = { api_key_id: null, configuration: { foo: { + default_value: '', depends_on: [], - display: 'textbox', + display: DisplayType.TEXTBOX, key: 'foo', label: 'bar', options: [], order: 1, required: false, sensitive: false, + tooltip: '', value: 'barbar', }, }, diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/connector_configuration_field.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/connector_configuration_field.tsx index 5564388c0fdfd..1030cbdcfa5d9 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/connector_configuration_field.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/connector_configuration_field.tsx @@ -18,9 +18,11 @@ import { EuiSelect, EuiSwitch, EuiTextArea, + EuiToolTip, } from '@elastic/eui'; import { Status } from '../../../../../../common/types/api'; +import { DisplayType } from '../../../../../../common/types/connectors'; import { ConnectorConfigurationApiLogic } from '../../../api/connector/update_connector_configuration_api_logic'; @@ -42,10 +44,10 @@ export const ConnectorConfigurationField: React.FC 3 ? ( ); - case 'numeric': + case DisplayType.NUMERIC: return ( ); - case 'textarea': + case DisplayType.TEXTAREA: const textarea = ( +

{label}

+ + ); + return ( { setLocalConfigEntry({ ...configEntry, value: event.target.checked }); }} diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/connector_configuration_form.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/connector_configuration_form.tsx index 991e58a672016..2ef04c8e2c18b 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/connector_configuration_form.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/connector_configuration_form.tsx @@ -10,19 +10,20 @@ import React from 'react'; import { useActions, useValues } from 'kea'; import { - EuiForm, - EuiFormRow, - EuiFlexGroup, - EuiFlexItem, EuiButton, EuiButtonEmpty, + EuiFlexGroup, + EuiFlexItem, + EuiForm, + EuiFormRow, EuiPanel, + EuiToolTip, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { Status } from '../../../../../../common/types/api'; -import { DependencyLookup } from '../../../../../../common/types/connectors'; +import { DependencyLookup, DisplayType } from '../../../../../../common/types/connectors'; import { ConnectorConfigurationApiLogic } from '../../../api/connector/update_connector_configuration_api_logic'; @@ -56,13 +57,38 @@ export const ConnectorConfigurationForm = () => { component="form" > {localConfigView.map((configEntry) => { - const { depends_on: dependencies, key, label } = configEntry; + const { + default_value: defaultValue, + depends_on: dependencies, + key, + display, + label, + tooltip, + } = configEntry; + // toggle label goes next to the element, not in the row const hasDependencies = dependencies.length > 0; + const helpText = defaultValue + ? i18n.translate( + 'xpack.enterpriseSearch.content.indices.configurationConnector.config.defaultValue', + { + defaultMessage: 'If left empty, the default value {defaultValue} will be used.', + values: { defaultValue }, + } + ) + : ''; + const rowLabel = + display !== DisplayType.TOGGLE ? ( + +

{label}

+
+ ) : ( + <> + ); return hasDependencies ? ( dependenciesSatisfied(dependencies, dependencyLookup) ? ( - + @@ -70,7 +96,7 @@ export const ConnectorConfigurationForm = () => { <> ) ) : ( - + ); diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/connector_configuration_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/connector_configuration_logic.test.ts index ebb173a3c42ca..9ff1fc60db168 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/connector_configuration_logic.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/connector_configuration_logic.test.ts @@ -8,7 +8,7 @@ import { LogicMounter } from '../../../../__mocks__/kea_logic'; import { connectorIndex } from '../../../__mocks__/view_index.mock'; -import { ConnectorStatus } from '../../../../../../common/types/connectors'; +import { ConnectorStatus, DisplayType } from '../../../../../../common/types/connectors'; import { ConnectorConfigurationApiLogic } from '../../../api/connector/update_connector_configuration_api_logic'; import { CachedFetchIndexApiLogic } from '../../../api/index/cached_fetch_index_api_logic'; @@ -52,13 +52,15 @@ describe('ConnectorConfigurationLogic', () => { ConnectorConfigurationApiLogic.actions.apiSuccess({ configuration: { foo: { + default_value: '', depends_on: [], - display: 'textbox', + display: DisplayType.TEXTBOX, label: 'newBar', options: [], order: 1, required: false, sensitive: false, + tooltip: '', value: 'oldBar', }, }, @@ -68,26 +70,30 @@ describe('ConnectorConfigurationLogic', () => { ...DEFAULT_VALUES, configState: { foo: { + default_value: '', depends_on: [], - display: 'textbox', + display: DisplayType.TEXTBOX, label: 'newBar', options: [], order: 1, required: false, sensitive: false, + tooltip: '', value: 'oldBar', }, }, configView: [ { + default_value: '', depends_on: [], - display: 'textbox', + display: DisplayType.TEXTBOX, key: 'foo', label: 'newBar', options: [], order: 1, required: false, sensitive: false, + tooltip: '', value: 'oldBar', }, ], @@ -96,13 +102,15 @@ describe('ConnectorConfigurationLogic', () => { it('should set config on setConfigState', () => { ConnectorConfigurationLogic.actions.setConfigState({ foo: { + default_value: '', depends_on: [], - display: 'textbox', + display: DisplayType.TEXTBOX, label: 'thirdBar', options: [], order: 1, required: false, sensitive: false, + tooltip: '', value: 'fourthBar', }, }); @@ -110,26 +118,30 @@ describe('ConnectorConfigurationLogic', () => { ...DEFAULT_VALUES, configState: { foo: { + default_value: '', depends_on: [], - display: 'textbox', + display: DisplayType.TEXTBOX, label: 'thirdBar', options: [], order: 1, required: false, sensitive: false, + tooltip: '', value: 'fourthBar', }, }, configView: [ { + default_value: '', depends_on: [], - display: 'textbox', + display: DisplayType.TEXTBOX, key: 'foo', label: 'thirdBar', options: [], order: 1, required: false, sensitive: false, + tooltip: '', value: 'fourthBar', }, ], @@ -139,150 +151,176 @@ describe('ConnectorConfigurationLogic', () => { it('should set local config entry and sort keys', () => { ConnectorConfigurationLogic.actions.setConfigState({ bar: { + default_value: '', depends_on: [], - display: 'textbox', + display: DisplayType.TEXTBOX, label: 'foo', options: [], order: 1, required: false, sensitive: false, + tooltip: '', value: 'foofoo', }, password: { + default_value: '', depends_on: [], - display: 'textbox', + display: DisplayType.TEXTBOX, label: 'thirdBar', options: [], order: 2, required: false, sensitive: true, + tooltip: '', value: 'fourthBar', }, }); ConnectorConfigurationLogic.actions.setLocalConfigState({ bar: { + default_value: '', depends_on: [], - display: 'textbox', + display: DisplayType.TEXTBOX, label: 'foo', options: [], order: 1, required: false, sensitive: false, + tooltip: '', value: 'foofoo', }, password: { + default_value: '', depends_on: [], - display: 'textbox', + display: DisplayType.TEXTBOX, label: 'thirdBar', options: [], order: 2, required: false, sensitive: true, + tooltip: '', value: 'fourthBar', }, }); ConnectorConfigurationLogic.actions.setLocalConfigEntry({ + default_value: '', depends_on: [], - display: 'textbox', + display: DisplayType.TEXTBOX, key: 'bar', label: 'foo', options: [], order: 1, required: false, sensitive: false, + tooltip: '', value: 'fafa', }); expect(ConnectorConfigurationLogic.values).toEqual({ ...DEFAULT_VALUES, configState: { bar: { + default_value: '', depends_on: [], - display: 'textbox', + display: DisplayType.TEXTBOX, label: 'foo', options: [], order: 1, required: false, sensitive: false, + tooltip: '', value: 'foofoo', }, password: { + default_value: '', depends_on: [], - display: 'textbox', + display: DisplayType.TEXTBOX, label: 'thirdBar', options: [], order: 2, required: false, sensitive: true, + tooltip: '', value: 'fourthBar', }, }, configView: [ { + default_value: '', depends_on: [], - display: 'textbox', + display: DisplayType.TEXTBOX, key: 'bar', label: 'foo', options: [], order: 1, required: false, sensitive: false, + tooltip: '', value: 'foofoo', }, { + default_value: '', depends_on: [], - display: 'textbox', + display: DisplayType.TEXTBOX, key: 'password', label: 'thirdBar', options: [], order: 2, required: false, sensitive: true, + tooltip: '', value: 'fourthBar', }, ], localConfigState: { bar: { + default_value: '', depends_on: [], - display: 'textbox', + display: DisplayType.TEXTBOX, label: 'foo', options: [], order: 1, required: false, sensitive: false, + tooltip: '', value: 'fafa', }, password: { + default_value: '', depends_on: [], - display: 'textbox', + display: DisplayType.TEXTBOX, label: 'thirdBar', options: [], order: 2, required: false, sensitive: true, + tooltip: '', value: 'fourthBar', }, }, localConfigView: [ { + default_value: '', depends_on: [], - display: 'textbox', + display: DisplayType.TEXTBOX, key: 'bar', label: 'foo', options: [], order: 1, required: false, sensitive: false, + tooltip: '', value: 'fafa', }, { + default_value: '', depends_on: [], - display: 'textbox', + display: DisplayType.TEXTBOX, key: 'password', label: 'thirdBar', options: [], order: 2, required: false, sensitive: true, + tooltip: '', value: 'fourthBar', }, ], @@ -297,14 +335,16 @@ describe('ConnectorConfigurationLogic', () => { configState: connectorIndex.connector.configuration, configView: [ { + default_value: '', depends_on: [], - display: 'textbox', + display: DisplayType.TEXTBOX, key: 'foo', label: 'bar', options: [], order: 1, required: false, sensitive: false, + tooltip: '', value: 'barbar', }, ], @@ -331,14 +371,16 @@ describe('ConnectorConfigurationLogic', () => { configState: connectorIndex.connector.configuration, configView: [ { + default_value: '', depends_on: [], - display: 'textbox', + display: DisplayType.TEXTBOX, key: 'foo', label: 'bar', options: [], order: 1, required: false, sensitive: false, + tooltip: '', value: 'barbar', }, ], @@ -350,14 +392,16 @@ describe('ConnectorConfigurationLogic', () => { localConfigState: connectorIndex.connector.configuration, localConfigView: [ { + default_value: '', depends_on: [], - display: 'textbox', + display: DisplayType.TEXTBOX, key: 'foo', label: 'bar', options: [], order: 1, required: false, sensitive: false, + tooltip: '', value: 'barbar', }, ], @@ -371,13 +415,15 @@ describe('ConnectorConfigurationLogic', () => { ConnectorConfigurationLogic.actions.fetchIndexApiSuccess(connectorIndex); ConnectorConfigurationLogic.actions.setLocalConfigState({ foo: { + default_value: '', depends_on: [], - display: 'textbox', + display: DisplayType.TEXTBOX, label: 'bar', options: [], order: 1, required: false, sensitive: true, + tooltip: '', value: 'Barbara', }, }); diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/connector_configuration_logic.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/connector_configuration_logic.ts index fee9496b3c069..02ebb7888b7b8 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/connector_configuration_logic.ts +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/connector_configuration_logic.ts @@ -12,6 +12,7 @@ import { ConnectorStatus, Dependency, DependencyLookup, + DisplayType, SelectOption, } from '../../../../../../common/types/connectors'; import { isNotNullish } from '../../../../../../common/utils/is_not_nullish'; @@ -55,14 +56,16 @@ interface ConnectorConfigurationValues { } export interface ConfigEntry { + default_value: string | number | boolean | null; depends_on: Dependency[]; - display: string; + display: DisplayType; key: string; label: string; options: SelectOption[]; order?: number; required: boolean; sensitive: boolean; + tooltip: string; value: string | number | boolean | null; } @@ -229,11 +232,35 @@ export const ConnectorConfigurationLogic = kea< { setLocalConfigEntry: ( configState, - // eslint-disable-next-line @typescript-eslint/naming-convention - { key, depends_on, display, label, options, order, required, sensitive, value } + { + key, + // eslint-disable-next-line @typescript-eslint/naming-convention + default_value, + // eslint-disable-next-line @typescript-eslint/naming-convention + depends_on, + display, + label, + options, + order, + required, + sensitive, + tooltip, + value, + } ) => ({ ...configState, - [key]: { depends_on, display, label, options, order, required, sensitive, value }, + [key]: { + default_value, + depends_on, + display, + label, + options, + order, + required, + sensitive, + tooltip, + value, + }, }), setLocalConfigState: (_, { configState }) => configState, }, diff --git a/x-pack/plugins/translations/translations/en.json b/x-pack/plugins/translations/translations/en.json new file mode 100644 index 0000000000000..f6b5644656f8a --- /dev/null +++ b/x-pack/plugins/translations/translations/en.json @@ -0,0 +1,213 @@ +{ + formats: { + number: { + currency: { + style: 'currency', + }, + percent: { + style: 'percent', + }, + }, + date: { + short: { + month: 'numeric', + day: 'numeric', + year: '2-digit', + }, + medium: { + month: 'short', + day: 'numeric', + year: 'numeric', + }, + long: { + month: 'long', + day: 'numeric', + year: 'numeric', + }, + full: { + weekday: 'long', + month: 'long', + day: 'numeric', + year: 'numeric', + }, + }, + time: { + short: { + hour: 'numeric', + minute: 'numeric', + }, + medium: { + hour: 'numeric', + minute: 'numeric', + second: 'numeric', + }, + long: { + hour: 'numeric', + minute: 'numeric', + second: 'numeric', + timeZoneName: 'short', + }, + full: { + hour: 'numeric', + minute: 'numeric', + second: 'numeric', + timeZoneName: 'short', + }, + }, + relative: { + years: { + units: 'year', + }, + months: { + units: 'month', + }, + days: { + units: 'day', + }, + hours: { + units: 'hour', + }, + minutes: { + units: 'minute', + }, + seconds: { + units: 'second', + }, + }, + }, + messages: { + 'xpack.enterpriseSearch.connector.ingestionStatus.title': 'Ingestion status', + 'xpack.enterpriseSearch.content.index.connector.filteringRules.regExError': 'Value should be a regular expression', + 'xpack.enterpriseSearch.content.index.connector.syncRules.advancedFiltersDescription': 'These rules apply before the data is obtained from the data source.', + 'xpack.enterpriseSearch.content.index.connector.syncRules.advancedFiltersLinkTitle': 'Learn more about advanced sync rules.', + 'xpack.enterpriseSearch.content.index.connector.syncRules.advancedRulesTitle': 'Advanced rules', + 'xpack.enterpriseSearch.content.index.connector.syncRules.advancedTabTitle': 'Advanced rules', + 'xpack.enterpriseSearch.content.index.connector.syncRules.basicRulesDescription': 'These rules apply to documents during the integration filtering phase.', + 'xpack.enterpriseSearch.content.index.connector.syncRules.basicRulesTitle': 'Basic rules', + 'xpack.enterpriseSearch.content.index.connector.syncRules.basicTabTitle': 'Basic rules', + 'xpack.enterpriseSearch.content.index.connector.syncRules.description': 'Add a sync rule to customize what data is synchronized from {indexName}. Everything is included by default, and documents are validated against the configured set of sync rules in the listed order.', + 'xpack.enterpriseSearch.content.index.connector.syncRules.flyout.description': 'Plan and edit rules here before applying them to the next sync.', + 'xpack.enterpriseSearch.content.index.connector.syncRules.flyout.errorTitle': 'Sync {idsLength, plural, one {rule} other {rules}} {ids} {idsLength, plural, one {is} other {are}} invalid.', + 'xpack.enterpriseSearch.content.index.connector.syncRules.flyout.revertButtonTitle': 'Revert to active rules', + 'xpack.enterpriseSearch.content.index.connector.syncRules.flyout.title': 'Draft rules', + 'xpack.enterpriseSearch.content.index.connector.syncRules.link': 'Learn more about customizing your sync rules.', + 'xpack.enterpriseSearch.content.index.connector.syncRules.table.addRuleLabel': 'Add sync rule', + 'xpack.enterpriseSearch.content.indices.configurationConnector.apiKey.button.label': 'Generate API key', + 'xpack.enterpriseSearch.content.indices.configurationConnector.apiKey.confirmModal.cancelButton.label': 'Cancel', + 'xpack.enterpriseSearch.content.indices.configurationConnector.apiKey.confirmModal.confirmButton.label': 'Generate API key', + 'xpack.enterpriseSearch.content.indices.configurationConnector.apiKey.confirmModal.description': 'Generating a new API key will invalidate the previous key. Are you sure you want to generate a new API key? This can not be undone.', + 'xpack.enterpriseSearch.content.indices.configurationConnector.apiKey.confirmModal.title': 'Generate an Elasticsearch API key', + 'xpack.enterpriseSearch.content.indices.configurationConnector.apiKey.description': 'First, generate an Elasticsearch API key. This {apiKeyName} key will enable read and write permissions for the connector to index documents to the created {indexName} index. Save the key in a safe place, as you will need it to configure your connector.', + 'xpack.enterpriseSearch.content.indices.configurationConnector.config.cancelEditingButton.title': 'Cancel', + 'xpack.enterpriseSearch.content.indices.configurationConnector.config.connectorClientLink': 'example connector client', + 'xpack.enterpriseSearch.content.indices.configurationConnector.config.defaultValue': 'If left empty, the default value {defaultValue} will be used.', + 'xpack.enterpriseSearch.content.indices.configurationConnector.config.description.firstParagraph': 'Now that your connector is deployed, enhance the deployed connector client for your custom data source. There’s an {link} for you to start adding your data source specific implementation logic.', + 'xpack.enterpriseSearch.content.indices.configurationConnector.config.description.secondParagraph': 'While the connector clients in the repository are built in Ruby, there’s no technical limitation to only use Ruby. Build a connector client with the technology that works best for your skillset.', + 'xpack.enterpriseSearch.content.indices.configurationConnector.config.description.thirdParagraph': 'If you need help, you can always open an {issuesLink} in the repository or ask a question in our {discussLink} forum.', + 'xpack.enterpriseSearch.content.indices.configurationConnector.config.discussLink': 'Discuss', + 'xpack.enterpriseSearch.content.indices.configurationConnector.config.editButton.title': 'Edit configuration', + 'xpack.enterpriseSearch.content.indices.configurationConnector.config.error.title': 'Connector error', + 'xpack.enterpriseSearch.content.indices.configurationConnector.config.issuesLink': 'issue', + 'xpack.enterpriseSearch.content.indices.configurationConnector.config.submitButton.title': 'Save configuration', + 'xpack.enterpriseSearch.content.indices.configurationConnector.config.warning.title': 'This connector is tied to your Elastic index', + 'xpack.enterpriseSearch.content.indices.configurationConnector.connectorPackage.button.label': 'Explore the connectors repository', + 'xpack.enterpriseSearch.content.indices.configurationConnector.connectorPackage.clientExamplesLink': 'connector client examples', + 'xpack.enterpriseSearch.content.indices.configurationConnector.connectorPackage.configurationFileLink': 'configuration file', + 'xpack.enterpriseSearch.content.indices.configurationConnector.connectorPackage.connectorConnected': 'Your connector {name} has connected to Enterprise Search successfully.', + 'xpack.enterpriseSearch.content.indices.configurationConnector.connectorPackage.connectorDeployedText': 'Once you’ve configured the connector, deploy the connector to your self managed infrastructure.', + 'xpack.enterpriseSearch.content.indices.configurationConnector.connectorPackage.description.firstParagraph': 'The connectors repository contains several connector client examples to help you utilize our framework for accelerated development against custom data sources.', + 'xpack.enterpriseSearch.content.indices.configurationConnector.connectorPackage.description.secondParagraph': 'The connectors repository contains several {link} to help you utilize our framework for accelerated development against custom data sources.', + 'xpack.enterpriseSearch.content.indices.configurationConnector.connectorPackage.description.thirdParagraph': 'In this step, you will need to clone or fork the repository, and copy the generated API key and connector ID to the associated {link}. The connector ID will identify this connector to Enterprise Search.', + 'xpack.enterpriseSearch.content.indices.configurationConnector.connectorPackage.waitingForConnector.button.label': 'Recheck now', + 'xpack.enterpriseSearch.content.indices.configurationConnector.connectorPackage.waitingForConnectorText': 'Your connector has not connected to Enterprise Search. Troubleshoot your configuration and refresh the page.', + 'xpack.enterpriseSearch.content.indices.configurationConnector.connectorPackage.waitingForConnectorTitle': 'Waiting for your connector', + 'xpack.enterpriseSearch.content.indices.configurationConnector.nameAndDescriptionFlyout.description': 'By naming and describing this connector your colleagues and wider team will know what this connector is meant for.', + 'xpack.enterpriseSearch.content.indices.configurationConnector.nameAndDescriptionFlyout.saveButtonLabel': 'Save name and description', + 'xpack.enterpriseSearch.content.indices.configurationConnector.nameAndDescriptionFlyout.title': 'Describe this crawler', + 'xpack.enterpriseSearch.content.indices.configurationConnector.nameAndDescriptionForm.description': 'By naming and describing this connector your colleagues and wider team will know what this connector is meant for.', + 'xpack.enterpriseSearch.content.indices.configurationConnector.nativeConnector.config.encryptionWarningMessage': 'Encryption for data source credentials is unavailable in this beta. Your data source credentials will be stored, unencrypted, in Elasticsearch.', + 'xpack.enterpriseSearch.content.indices.configurationConnector.nativeConnector.config.securityDocumentationLinkLabel': 'Learn more about Elasticsearch security', + 'xpack.enterpriseSearch.content.indices.configurationConnector.nativeConnector.config.sourceSecurityDocumentationLinkLabel': '{name} authentication', + 'xpack.enterpriseSearch.content.indices.configurationConnector.nativeConnector.connectorConnected': 'Your connector {name} has connected to Enterprise Search successfully.', + 'xpack.enterpriseSearch.content.indices.configurationConnector.nativeConnector.schedulingReminder.description': 'Remember to set a sync schedule in the Scheduling tab to continually refresh your searchable data.', + 'xpack.enterpriseSearch.content.indices.configurationConnector.nativeConnector.schedulingReminder.title': 'Configurable sync schedule', + 'xpack.enterpriseSearch.content.indices.configurationConnector.nativeConnector.securityReminder.description': 'Restrict and personalize the read access users have to the index documents at query time.', + 'xpack.enterpriseSearch.content.indices.configurationConnector.nativeConnector.securityReminder.securityLinkLabel': 'Document level security', + 'xpack.enterpriseSearch.content.indices.configurationConnector.nativeConnector.securityReminder.title': 'Document level security', + 'xpack.enterpriseSearch.content.indices.configurationConnector.nativeConnector.steps.advancedConfigurationTitle': 'Advanced configuration', + 'xpack.enterpriseSearch.content.indices.configurationConnector.nativeConnector.steps.configurationTitle': 'Configuration', + 'xpack.enterpriseSearch.content.indices.configurationConnector.nativeConnector.steps.nameAndDescriptionTitle': 'Name and description', + 'xpack.enterpriseSearch.content.indices.configurationConnector.nativeConnector.steps.researchConfigurationTitle': 'Research configuration requirements', + 'xpack.enterpriseSearch.content.indices.configurationConnector.nativeConnectorAdvancedConfiguration.description': 'Finalize your connector by triggering a one time sync, or setting a recurring sync schedule.', + 'xpack.enterpriseSearch.content.indices.configurationConnector.nativeConnectorAdvancedConfiguration.schedulingButtonLabel': 'Set schedule and sync', + 'xpack.enterpriseSearch.content.indices.configurationConnector.researchConfiguration.connectorDocumentationLinkLabel': 'Documentation', + 'xpack.enterpriseSearch.content.indices.configurationConnector.researchConfiguration.description': 'This connector supports several authentication methods. Ask your administrator for the correct connection credentials.', + 'xpack.enterpriseSearch.content.indices.configurationConnector.researchConfiguration.serviceDocumentationLinkLabel': '{name} documentation', + 'xpack.enterpriseSearch.content.indices.configurationConnector.scheduleSync.description': 'Once your connectors are configured to your liking, don’t forget to set a recurring sync schedule to make sure your documents are indexed and relevant. You can also trigger a one-time sync without enabling a sync schedule.', + 'xpack.enterpriseSearch.content.indices.configurationConnector.steps.deployConnector.title': 'Deploy a connector', + 'xpack.enterpriseSearch.content.indices.configurationConnector.steps.enhance.title': 'Enhance your connector client', + 'xpack.enterpriseSearch.content.indices.configurationConnector.steps.generateApiKey.title': 'Generate an API key', + 'xpack.enterpriseSearch.content.indices.configurationConnector.steps.nameAndDescriptionTitle': 'Name and description', + 'xpack.enterpriseSearch.content.indices.configurationConnector.steps.schedule.button.label': 'Set schedule and sync', + 'xpack.enterpriseSearch.content.indices.configurationConnector.steps.schedule.title': 'Set a recurring sync schedule', + 'xpack.enterpriseSearch.content.indices.configurationConnector.support.connectorFeedback.label': 'Connector feedback', + 'xpack.enterpriseSearch.content.indices.configurationConnector.support.description': 'Your connector will have to be deployed to your own infrastructure.', + 'xpack.enterpriseSearch.content.indices.configurationConnector.support.dontSeeIntegration.label': 'Don’t see the integration you’re looking for?', + 'xpack.enterpriseSearch.content.indices.configurationConnector.support.getHelp.label': 'Get help', + 'xpack.enterpriseSearch.content.indices.configurationConnector.support.issue.label': 'File an issue', + 'xpack.enterpriseSearch.content.indices.configurationConnector.support.manageKeys.label': 'Manage keys', + 'xpack.enterpriseSearch.content.indices.configurationConnector.support.readme.label': 'Connector readme', + 'xpack.enterpriseSearch.content.indices.configurationConnector.support.searchUI.label': 'Use Search UI for Workplace Search', + 'xpack.enterpriseSearch.content.indices.configurationConnector.support.title': 'Support and documentation', + 'xpack.enterpriseSearch.content.indices.configurationConnector.support.viewDocumentation.label': 'View documentation', + 'xpack.enterpriseSearch.content.indices.configurationConnector.warning.description': 'If you sync at least one document before you’ve finalized your connector client, you will have to recreate your search index.', + 'xpack.enterpriseSearch.content.indices.connector.syncRules.advancedRules.error': 'JSON format is invalid', + 'xpack.enterpriseSearch.content.indices.connector.syncRules.advancedRules.title': 'Advanced rules', + 'xpack.enterpriseSearch.content.indices.connectorScheduling.configured.description': 'Your connector is configured and deployed. Configure a one-time sync by clicking the Sync button, or enable a recurring sync schedule. ', + 'xpack.enterpriseSearch.content.indices.connectorScheduling.error.title': 'Review your connector configuration for reported errors.', + 'xpack.enterpriseSearch.content.indices.connectorScheduling.notConnected.button.label': 'Configure', + 'xpack.enterpriseSearch.content.indices.connectorScheduling.notConnected.description': 'Configure and deploy your connector, then return here to set your sync schedule. This schedule will dictate the interval that the connector will sync with your data source for updated documents.', + 'xpack.enterpriseSearch.content.indices.connectorScheduling.notConnected.title': 'Configure your connector to schedule a sync', + 'xpack.enterpriseSearch.content.indices.connectorScheduling.resetButton.label': 'Reset', + 'xpack.enterpriseSearch.content.indices.connectorScheduling.saveButton.label': 'Save', + 'xpack.enterpriseSearch.content.indices.connectorScheduling.switch.label': 'Enable recurring syncs with the following schedule', + 'xpack.enterpriseSearch.content.indices.connectorScheduling.unsaved.title': 'You have not saved your changes, are you sure you want to leave?', + 'xpack.enterpriseSearch.content.indices.selectConnector.buildYourOwnConnectorLinkLabel': 'build your own', + 'xpack.enterpriseSearch.content.indices.selectConnector.connectorCheckable.description': 'Search over your {name} content with Enterprise Search.', + 'xpack.enterpriseSearch.content.indices.selectConnector.connectorCheckable.documentationLinkLabel': 'Documentation', + 'xpack.enterpriseSearch.content.indices.selectConnector.description': 'Get started by selecting the connector you\'d like to configure to extract, index and sync data from your data source into your newly created search index.', + 'xpack.enterpriseSearch.content.indices.selectConnector.moreConnectorsMessage': 'Looking for more connectors? {workplaceSearchLink} or {buildYourOwnConnectorLink}.', + 'xpack.enterpriseSearch.content.indices.selectConnector.selectAndConfigureButtonLabel': 'Select and configure', + 'xpack.enterpriseSearch.content.indices.selectConnector.successToast.title': 'Your index will now use the {connectorName} native connector.', + 'xpack.enterpriseSearch.content.indices.selectConnector.title': 'Select a connector', + 'xpack.enterpriseSearch.content.indices.selectConnector.workplaceSearchLinkLabel': 'View additional integrations in Workplace Search', + 'xpack.enterpriseSearch.content.nativeConnectors.mongodb.name': 'MongoDB', + 'xpack.enterpriseSearch.content.nativeConnectors.mysql.name': 'MySQL', + 'xpack.enterpriseSearch.content.searchIndex.totalStats.documentCountCardLabel': 'Document count', + 'xpack.enterpriseSearch.content.sources.basicRulesTable.includeEverythingMessage': 'Include everything else from this source', + 'xpack.enterpriseSearch.index.connector.rule.basicTable.policyTitle': 'Policy', + 'xpack.enterpriseSearch.index.connector.syncRules.basicTable.fieldTitle': 'Field', + 'xpack.enterpriseSearch.index.connector.syncRules.basicTable.ruleTitle': 'Rule', + 'xpack.enterpriseSearch.index.connector.syncRules.basicTable.valueTitle': 'Value', + 'xpack.enterpriseSearch.index.connector.syncRules.cancelEditingFilteringDraft': 'Cancel', + 'xpack.enterpriseSearch.index.connector.syncRules.description': 'Include or exclude high level items, file types and (file or folder) paths to + synchronize from {indexName}. Everything is included by default. Each document is + tested against the rules below and the first rule that matches will be applied.', + 'xpack.enterpriseSearch.index.connector.syncRules.draftNewFilterRulesTitle': 'Draft new sync rules', + 'xpack.enterpriseSearch.index.connector.syncRules.editFilterRulesTitle': 'Edit sync rules', + 'xpack.enterpriseSearch.index.connector.syncRules.errorCallout.editDraftRulesTitle': 'Edit draft rules', + 'xpack.enterpriseSearch.index.connector.syncRules.errorCallout.successEditDraftRulesTitle': 'Edit draft rules', + 'xpack.enterpriseSearch.index.connector.syncRules.invalidDescription': 'Draft rules did not validate. Edit the draft rules before they can be activated.', + 'xpack.enterpriseSearch.index.connector.syncRules.invalidTitle': 'Draft sync rules are invalid', + 'xpack.enterpriseSearch.index.connector.syncRules.successCallout.applyDraftRulesTitle': 'Activate draft rules', + 'xpack.enterpriseSearch.index.connector.syncRules.syncRulesLabel': 'Learn more about sync rules', + 'xpack.enterpriseSearch.index.connector.syncRules.title': 'Sync rules ', + 'xpack.enterpriseSearch.index.connector.syncRules.unsavedChanges': 'Your changes have not been saved. Are you sure you want to leave?', + 'xpack.enterpriseSearch.index.connector.syncRules.validatedDescription': 'Activate draft rules to take effect on the next sync.', + 'xpack.enterpriseSearch.index.connector.syncRules.validateDraftTitle': 'Save and validate draft', + 'xpack.enterpriseSearch.index.connector.syncRules.validatedTitle': 'Draft sync rules validated', + 'xpack.enterpriseSearch.index.connector.syncRules.validatingCallout.editDraftRulesTitle': 'Edit draft rules', + 'xpack.enterpriseSearch.index.connector.syncRules.validatingDescription': 'Draft rules need to be validated before they can be activated. This may take a few minutes.', + 'xpack.enterpriseSearch.index.connector.syncRules.validatingTitle': 'Draft sync rules are validating', + }, +} From 1b36fb83c45f364b5c676c2a24322448c8173080 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yulia=20=C4=8Cech?= <6585477+yuliacech@users.noreply.github.com> Date: Wed, 19 Apr 2023 12:17:10 +0200 Subject: [PATCH 31/78] [Upgrade Assistant] Make saved objects hidden (#154180) ## Summary Fixes https://github.com/elastic/kibana/issues/154037 This PR updates the saved objects types used in Upgrade Assistant for reindexing operations and ML snapshots. This is needed in preparation for serverless and should not have any affect on the UI. For testing this PR I would suggest using the same steps as in these 2 PRs for [reindexing](https://github.com/elastic/kibana/pull/150878) and [ml snapshots](https://github.com/elastic/kibana/pull/151014). --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> --- .../upgrade_assistant/server/plugin.ts | 8 ++++++-- .../server/routes/__mocks__/routes.mock.ts | 3 ++- .../server/routes/ml_snapshots.test.ts | 15 +++++++++----- .../server/routes/ml_snapshots.ts | 10 +++++++--- .../reindex_indices/batch_reindex_indices.ts | 13 +++++++----- .../routes/reindex_indices/reindex_indices.ts | 20 ++++++++++++------- .../server/saved_object_types/index.ts | 4 ++++ .../ml_upgrade_operation_saved_object_type.ts | 2 +- .../reindex_operation_saved_object_type.ts | 2 +- 9 files changed, 52 insertions(+), 25 deletions(-) diff --git a/x-pack/plugins/upgrade_assistant/server/plugin.ts b/x-pack/plugins/upgrade_assistant/server/plugin.ts index 922fdc8d89465..f77a5eabe1bda 100644 --- a/x-pack/plugins/upgrade_assistant/server/plugin.ts +++ b/x-pack/plugins/upgrade_assistant/server/plugin.ts @@ -29,7 +29,11 @@ import { registerUpgradeAssistantUsageCollector } from './lib/telemetry'; import { versionService } from './lib/version'; import { createReindexWorker } from './routes/reindex_indices'; import { registerRoutes } from './routes/register_routes'; -import { reindexOperationSavedObjectType, mlSavedObjectType } from './saved_object_types'; +import { + reindexOperationSavedObjectType, + mlSavedObjectType, + hiddenTypes, +} from './saved_object_types'; import { handleEsError } from './shared_imports'; import { RouteDependencies } from './types'; import type { UpgradeAssistantConfig } from './config'; @@ -169,7 +173,7 @@ export class UpgradeAssistantServerPlugin implements Plugin { elasticsearchService: elasticsearch, logger: this.logger, savedObjects: new SavedObjectsClient( - this.savedObjectsServiceStart.createInternalRepository() + this.savedObjectsServiceStart.createInternalRepository(hiddenTypes) ), security: this.securityPluginStart, }); diff --git a/x-pack/plugins/upgrade_assistant/server/routes/__mocks__/routes.mock.ts b/x-pack/plugins/upgrade_assistant/server/routes/__mocks__/routes.mock.ts index 172b1d53474d4..3e6870391328a 100644 --- a/x-pack/plugins/upgrade_assistant/server/routes/__mocks__/routes.mock.ts +++ b/x-pack/plugins/upgrade_assistant/server/routes/__mocks__/routes.mock.ts @@ -13,12 +13,13 @@ import { deprecationsServiceMock, } from '@kbn/core/server/mocks'; +export const savedObjectsClient = savedObjectsClientMock.create(); export const routeHandlerContextMock = { core: { elasticsearch: { client: elasticsearchServiceMock.createScopedClusterClient(), }, - savedObjects: { client: savedObjectsClientMock.create() }, + savedObjects: { getClient: () => savedObjectsClient }, deprecations: { client: deprecationsServiceMock.createClient() }, }, } as unknown as AwaitedProperties; diff --git a/x-pack/plugins/upgrade_assistant/server/routes/ml_snapshots.test.ts b/x-pack/plugins/upgrade_assistant/server/routes/ml_snapshots.test.ts index 3b53600170d59..a841f3042f964 100644 --- a/x-pack/plugins/upgrade_assistant/server/routes/ml_snapshots.test.ts +++ b/x-pack/plugins/upgrade_assistant/server/routes/ml_snapshots.test.ts @@ -9,7 +9,12 @@ import { kibanaResponseFactory, RequestHandler } from '@kbn/core/server'; import { errors as esErrors } from '@elastic/elasticsearch'; import { handleEsError } from '../shared_imports'; -import { createMockRouter, MockRouter, routeHandlerContextMock } from './__mocks__/routes.mock'; +import { + createMockRouter, + MockRouter, + routeHandlerContextMock, + savedObjectsClient, +} from './__mocks__/routes.mock'; import { createRequestMock } from './__mocks__/request.mock'; import { registerMlSnapshotRoutes } from './ml_snapshots'; @@ -285,7 +290,7 @@ describe('ML snapshots APIs', () => { ], }); - (routeHandlerContextMock.core.savedObjects.client.find as jest.Mock).mockResolvedValue({ + (savedObjectsClient.find as jest.Mock).mockResolvedValue({ total: 1, saved_objects: [ { @@ -356,7 +361,7 @@ describe('ML snapshots APIs', () => { ], }); - (routeHandlerContextMock.core.savedObjects.client.find as jest.Mock).mockResolvedValue({ + (savedObjectsClient.find as jest.Mock).mockResolvedValue({ total: 1, saved_objects: [ { @@ -421,7 +426,7 @@ describe('ML snapshots APIs', () => { ], }); - (routeHandlerContextMock.core.savedObjects.client.find as jest.Mock).mockResolvedValue({ + (savedObjectsClient.find as jest.Mock).mockResolvedValue({ total: 1, saved_objects: [ { @@ -449,7 +454,7 @@ describe('ML snapshots APIs', () => { index_settings: {}, }); - (routeHandlerContextMock.core.savedObjects.client.delete as jest.Mock).mockResolvedValue({}); + (savedObjectsClient.delete as jest.Mock).mockResolvedValue({}); const resp = await routeDependencies.router.getHandler({ method: 'get', diff --git a/x-pack/plugins/upgrade_assistant/server/routes/ml_snapshots.ts b/x-pack/plugins/upgrade_assistant/server/routes/ml_snapshots.ts index 02ea68bac7b5e..0ad6543de5251 100644 --- a/x-pack/plugins/upgrade_assistant/server/routes/ml_snapshots.ts +++ b/x-pack/plugins/upgrade_assistant/server/routes/ml_snapshots.ts @@ -155,7 +155,7 @@ export function registerMlSnapshotRoutes({ versionCheckHandlerWrapper(async ({ core }, request, response) => { try { const { - savedObjects: { client: savedObjectsClient }, + savedObjects: { getClient }, elasticsearch: { client: esClient }, } = await core; const { snapshotId, jobId } = request.body; @@ -173,7 +173,10 @@ export function registerMlSnapshotRoutes({ // Store snapshot in saved object if upgrade not complete if (body.completed !== true) { - await createMlOperation(savedObjectsClient, snapshotInfo); + await createMlOperation( + getClient({ includedHiddenTypes: [ML_UPGRADE_OP_TYPE] }), + snapshotInfo + ); } return response.ok({ @@ -202,9 +205,10 @@ export function registerMlSnapshotRoutes({ versionCheckHandlerWrapper(async ({ core }, request, response) => { try { const { - savedObjects: { client: savedObjectsClient }, + savedObjects: { getClient }, elasticsearch: { client: esClient }, } = await core; + const savedObjectsClient = getClient({ includedHiddenTypes: [ML_UPGRADE_OP_TYPE] }); const { snapshotId, jobId } = request.params; // Verify snapshot exists diff --git a/x-pack/plugins/upgrade_assistant/server/routes/reindex_indices/batch_reindex_indices.ts b/x-pack/plugins/upgrade_assistant/server/routes/reindex_indices/batch_reindex_indices.ts index c6ea9b4a8fb96..c9a8eedceffe3 100644 --- a/x-pack/plugins/upgrade_assistant/server/routes/reindex_indices/batch_reindex_indices.ts +++ b/x-pack/plugins/upgrade_assistant/server/routes/reindex_indices/batch_reindex_indices.ts @@ -9,7 +9,7 @@ import { schema } from '@kbn/config-schema'; import { errors } from '@elastic/elasticsearch'; import { API_BASE_PATH } from '../../../common/constants'; -import { ReindexStatus } from '../../../common/types'; +import { REINDEX_OP_TYPE, ReindexStatus } from '../../../common/types'; import { versionCheckHandlerWrapper } from '../../lib/es_version_precheck'; import { ReindexWorker } from '../../lib/reindexing'; import { reindexActionsFactory } from '../../lib/reindexing/reindex_actions'; @@ -43,9 +43,12 @@ export function registerBatchReindexIndicesRoutes( elasticsearch: { client: esClient }, savedObjects, } = await core; - const { client } = savedObjects; + const { getClient } = savedObjects; const callAsCurrentUser = esClient.asCurrentUser; - const reindexActions = reindexActionsFactory(client, callAsCurrentUser); + const reindexActions = reindexActionsFactory( + getClient({ includedHiddenTypes: [REINDEX_OP_TYPE] }), + callAsCurrentUser + ); try { const inProgressOps = await reindexActions.findAllByStatus(ReindexStatus.inProgress); const { queue } = sortAndOrderReindexOperations(inProgressOps); @@ -76,7 +79,7 @@ export function registerBatchReindexIndicesRoutes( }, versionCheckHandlerWrapper(async ({ core }, request, response) => { const { - savedObjects: { client: savedObjectsClient }, + savedObjects: { getClient }, elasticsearch: { client: esClient }, } = await core; const { indexNames } = request.body; @@ -87,7 +90,7 @@ export function registerBatchReindexIndicesRoutes( for (const indexName of indexNames) { try { const result = await reindexHandler({ - savedObjects: savedObjectsClient, + savedObjects: getClient({ includedHiddenTypes: [REINDEX_OP_TYPE] }), dataClient: esClient, indexName, log, diff --git a/x-pack/plugins/upgrade_assistant/server/routes/reindex_indices/reindex_indices.ts b/x-pack/plugins/upgrade_assistant/server/routes/reindex_indices/reindex_indices.ts index 11ebb6d5f4a98..0ffa9335c4de7 100644 --- a/x-pack/plugins/upgrade_assistant/server/routes/reindex_indices/reindex_indices.ts +++ b/x-pack/plugins/upgrade_assistant/server/routes/reindex_indices/reindex_indices.ts @@ -9,7 +9,7 @@ import { schema } from '@kbn/config-schema'; import { errors } from '@elastic/elasticsearch'; import { API_BASE_PATH } from '../../../common/constants'; -import type { ReindexStatusResponse } from '../../../common/types'; +import { ReindexStatusResponse, REINDEX_OP_TYPE } from '../../../common/types'; import { versionCheckHandlerWrapper } from '../../lib/es_version_precheck'; import { reindexServiceFactory, ReindexWorker, generateNewIndexName } from '../../lib/reindexing'; import { reindexActionsFactory } from '../../lib/reindexing/reindex_actions'; @@ -42,13 +42,13 @@ export function registerReindexIndicesRoutes( }, versionCheckHandlerWrapper(async ({ core }, request, response) => { const { - savedObjects: { client: savedObjectsClient }, + savedObjects: { getClient }, elasticsearch: { client: esClient }, } = await core; const { indexName } = request.params; try { const result = await reindexHandler({ - savedObjects: savedObjectsClient, + savedObjects: getClient({ includedHiddenTypes: [REINDEX_OP_TYPE] }), dataClient: esClient, indexName, log, @@ -88,10 +88,13 @@ export function registerReindexIndicesRoutes( savedObjects, elasticsearch: { client: esClient }, } = await core; - const { client } = savedObjects; + const { getClient } = savedObjects; const { indexName } = request.params; const asCurrentUser = esClient.asCurrentUser; - const reindexActions = reindexActionsFactory(client, asCurrentUser); + const reindexActions = reindexActionsFactory( + getClient({ includedHiddenTypes: [REINDEX_OP_TYPE] }), + asCurrentUser + ); const reindexService = reindexServiceFactory(asCurrentUser, reindexActions, log, licensing); try { @@ -143,9 +146,12 @@ export function registerReindexIndicesRoutes( elasticsearch: { client: esClient }, } = await core; const { indexName } = request.params; - const { client } = savedObjects; + const { getClient } = savedObjects; const callAsCurrentUser = esClient.asCurrentUser; - const reindexActions = reindexActionsFactory(client, callAsCurrentUser); + const reindexActions = reindexActionsFactory( + getClient({ includedHiddenTypes: [REINDEX_OP_TYPE] }), + callAsCurrentUser + ); const reindexService = reindexServiceFactory( callAsCurrentUser, reindexActions, diff --git a/x-pack/plugins/upgrade_assistant/server/saved_object_types/index.ts b/x-pack/plugins/upgrade_assistant/server/saved_object_types/index.ts index fa8c1ac679ad6..f8e9ec86d437a 100644 --- a/x-pack/plugins/upgrade_assistant/server/saved_object_types/index.ts +++ b/x-pack/plugins/upgrade_assistant/server/saved_object_types/index.ts @@ -5,5 +5,9 @@ * 2.0. */ +import { reindexOperationSavedObjectType } from './reindex_operation_saved_object_type'; +import { mlSavedObjectType } from './ml_upgrade_operation_saved_object_type'; + export { reindexOperationSavedObjectType } from './reindex_operation_saved_object_type'; export { mlSavedObjectType } from './ml_upgrade_operation_saved_object_type'; +export const hiddenTypes = [reindexOperationSavedObjectType.name, mlSavedObjectType.name]; diff --git a/x-pack/plugins/upgrade_assistant/server/saved_object_types/ml_upgrade_operation_saved_object_type.ts b/x-pack/plugins/upgrade_assistant/server/saved_object_types/ml_upgrade_operation_saved_object_type.ts index 7cb3d1d69f8af..9f677120a5374 100644 --- a/x-pack/plugins/upgrade_assistant/server/saved_object_types/ml_upgrade_operation_saved_object_type.ts +++ b/x-pack/plugins/upgrade_assistant/server/saved_object_types/ml_upgrade_operation_saved_object_type.ts @@ -11,7 +11,7 @@ import { ML_UPGRADE_OP_TYPE } from '../../common/types'; export const mlSavedObjectType: SavedObjectsType = { name: ML_UPGRADE_OP_TYPE, - hidden: false, + hidden: true, namespaceType: 'agnostic', mappings: { dynamic: false, diff --git a/x-pack/plugins/upgrade_assistant/server/saved_object_types/reindex_operation_saved_object_type.ts b/x-pack/plugins/upgrade_assistant/server/saved_object_types/reindex_operation_saved_object_type.ts index d57909dd3f720..0c66253312c7a 100644 --- a/x-pack/plugins/upgrade_assistant/server/saved_object_types/reindex_operation_saved_object_type.ts +++ b/x-pack/plugins/upgrade_assistant/server/saved_object_types/reindex_operation_saved_object_type.ts @@ -11,7 +11,7 @@ import { REINDEX_OP_TYPE } from '../../common/types'; export const reindexOperationSavedObjectType: SavedObjectsType = { name: REINDEX_OP_TYPE, - hidden: false, + hidden: true, namespaceType: 'agnostic', mappings: { dynamic: false, From f8c16c159c1ebe1d7779d45cb2f74d2cdbe22d61 Mon Sep 17 00:00:00 2001 From: Vitalii Dmyterko <92328789+vitaliidm@users.noreply.github.com> Date: Wed, 19 Apr 2023 11:48:37 +0100 Subject: [PATCH 32/78] [RAM][Security Solution][Alerts] moves legacy actions migration to rulesClient (#153101) ## Summary - this PR is the first part of work related to conditional logic actions. The rest of PRs, will be merged after this one. As they depend on a work implemented here. [List of tickets](https://github.com/elastic/security-team/issues/2894#issuecomment-1480253677) - addresses https://github.com/elastic/kibana/issues/151919 - moves code related to legacy actions migration from D&R to RulesClient, [details](https://github.com/elastic/kibana/issues/151919#issuecomment-1473913699) - similarly to D&R part, now rulesClient read APIs, would return legacy actions within rule - similarly, every mutation API in rulesClient, would migrate legacy actions, and remove sidecar SO - each migrated legacy action will have also [`frequency` object](https://github.com/elastic/kibana/blob/8.7/x-pack/plugins/alerting/server/types.ts#L234-L238), that would allow to have notifyWhen/throttle on action level once https://github.com/elastic/kibana/issues/151916 is implemented, which is targeted in 8.8, right after this PR. But before it's merged, `frequency` is getting removed in [update/bulk_edit/create APIs](https://github.com/elastic/kibana/blob/8.7/x-pack/plugins/alerting/server/rules_client/methods/update.ts#L151-L160). Hence it's not reflected in most of the tests at this point. - cleanup of legacy actions related code in D&R - adds unit tests for RulesClient - keeps functional/e2e tests in D&R Changes in behaviour, introduced in this PR: - since, migration happens within single rulesClient API call, revision in migrated rule will increment by `1` only. - legacy actions from sidecar SO, now will be merged with rules actions, if there any. Before, in the previous implementation, there was inconsistency in a way how legacy and rules actions were treated. - On read: actions existing in rule, [would take precedence over legacy ones ](https://github.com/elastic/kibana/blob/8.7/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/normalization/rule_actions.ts#L94-L114) - On migration: SO actions [only saved](https://github.com/elastic/kibana/blob/8.7/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/rule_actions/legacy_action_migration.ts#L114). If any actions present in rule, they will be lost. Here is an example video from **main** branch
Here is an example video from MAIN branch, where action in rule is overwritten by legacy action https://user-images.githubusercontent.com/92328789/230397535-d3fcd644-7cf9-4970-a573-18fd8c9f2235.mov
So, depends on sequence of events, different actions could be saved for identical use case: rule has both legacy and existing action. - if rule migrated through update API, existing in rule action will be saved - if rule migrated through enable/disable API, bulk edit, legacy action will be saved In this implementation, both existing in rule and legacy actions will be merged, to prevent loss of actions
Here is an example video from this PR branch, where actions are merged
- when duplicating rule, we don't migrate source rule anymore. It can lead to unwanted API key regeneration, with possible loss of privileges, earlier associated with the source rule. As part of UX, when duplicating any entity, users would not be expecting source entity to be changed TODO: - performance improvement issue for future https://github.com/elastic/kibana/issues/154438 - currently, in main branch, when migration is performed through rule enabling, actions not showing anymore in UI. Relevant ticket is https://github.com/elastic/kibana/issues/154567 I haven't fixed it in this PR, as in[ the next one ](https://github.com/elastic/kibana/pull/153113), we will display notifyWhen/throttle on action level in UI, rather than on rule level. So, once that PR is merged, actions should be displayed in new UI ### Checklist Delete any items that are not applicable to this PR. - [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 ### 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: Garrett Spong --- .../alerting/server/rules_client/lib/index.ts | 2 + .../format_legacy_actions.test.ts} | 240 ++++++--- .../format_legacy_actions.ts | 153 ++++++ .../migrate_legacy_actions.test.ts | 357 +++++++++++++ .../migrate_legacy_actions.ts | 113 ++++ .../retrieve_migrated_legacy_actions.mock.ts | 368 +++++++++++++ .../retrieve_migrated_legacy_actions.test.ts | 267 ++++++++++ .../retrieve_migrated_legacy_actions.ts | 117 +++++ .../transform_legacy_actions.test.ts | 89 ++++ .../transform_legacy_actions.ts | 59 +++ .../transform_to_notify_when.test.ts | 23 + .../transform_to_notify_when.ts | 25 + .../lib/siem_legacy_actions/types.ts | 50 ++ .../rules_client/methods/bulk_delete.ts | 24 +- .../rules_client/methods/bulk_disable.ts | 19 +- .../server/rules_client/methods/bulk_edit.ts | 19 +- .../rules_client/methods/bulk_enable.ts | 19 +- .../server/rules_client/methods/create.ts | 2 +- .../server/rules_client/methods/delete.ts | 7 + .../server/rules_client/methods/disable.ts | 23 +- .../server/rules_client/methods/enable.ts | 38 +- .../server/rules_client/methods/find.ts | 37 +- .../server/rules_client/methods/get.ts | 16 +- .../server/rules_client/methods/resolve.ts | 16 + .../server/rules_client/methods/update.ts | 19 +- .../rules_client/tests/bulk_delete.test.ts | 53 ++ .../rules_client/tests/bulk_disable.test.ts | 61 ++- .../rules_client/tests/bulk_edit.test.ts | 66 +++ .../rules_client/tests/bulk_enable.test.ts | 55 +- .../server/rules_client/tests/delete.test.ts | 35 ++ .../server/rules_client/tests/disable.test.ts | 45 ++ .../server/rules_client/tests/enable.test.ts | 68 +++ .../server/rules_client/tests/find.test.ts | 43 ++ .../server/rules_client/tests/get.test.ts | 76 +++ .../server/rules_client/tests/resolve.test.ts | 86 ++++ .../server/rules_client/tests/test_helpers.ts | 15 + .../server/rules_client/tests/update.test.ts | 71 +++ .../route.test.ts | 13 - .../update_prebuilt_rules.test.ts | 16 +- .../rule_objects/update_prebuilt_rules.ts | 19 +- .../routes/__mocks__/request_responses.ts | 356 +------------ .../rule_actions_legacy/index.ts | 2 - ...gacy_get_bulk_rule_actions_saved_object.ts | 84 --- .../api/rules/bulk_actions/route.test.ts | 13 - .../api/rules/bulk_actions/route.ts | 111 +--- .../api/rules/bulk_delete_rules/route.ts | 14 +- .../api/rules/bulk_patch_rules/route.test.ts | 16 - .../api/rules/bulk_patch_rules/route.ts | 10 +- .../api/rules/bulk_update_rules/route.test.ts | 15 - .../api/rules/bulk_update_rules/route.ts | 10 +- .../api/rules/delete_rule/route.test.ts | 15 +- .../api/rules/delete_rule/route.ts | 14 +- .../api/rules/find_rules/route.ts | 14 +- .../api/rules/patch_rule/route.test.ts | 15 - .../api/rules/patch_rule/route.ts | 10 +- .../api/rules/read_rule/route.ts | 11 +- .../api/rules/update_rule/route.test.ts | 15 +- .../api/rules/update_rule/route.ts | 10 +- .../detection_engine/rule_management/index.ts | 6 +- .../logic/export/get_export_all.ts | 12 +- .../logic/export/get_export_by_object_ids.ts | 12 +- .../logic/import/import_rules_utils.ts | 9 +- .../legacy_action_migration.test.ts | 486 ------------------ .../rule_actions/legacy_action_migration.ts | 178 ------- .../normalization/rule_actions.test.ts | 332 ++---------- .../normalization/rule_actions.ts | 56 +- .../normalization/rule_converters.ts | 11 +- .../rule_management/utils/utils.test.ts | 86 +--- .../rule_management/utils/utils.ts | 21 +- .../rule_management/utils/validate.test.ts | 4 +- .../rule_management/utils/validate.ts | 7 +- .../group1/delete_rules.ts | 13 + .../group1/delete_rules_bulk.ts | 13 + .../group10/import_rules.ts | 43 ++ .../group10/legacy_actions_migrations.ts | 22 +- .../group10/patch_rules.ts | 13 +- .../group10/patch_rules_bulk.ts | 16 +- .../group10/perform_bulk_action.ts | 86 +++- .../group10/update_rules.ts | 15 +- .../group10/update_rules_bulk.ts | 17 +- .../utils/wait_for_rule_status.ts | 3 + 81 files changed, 3044 insertions(+), 1976 deletions(-) rename x-pack/plugins/{security_solution/server/lib/detection_engine/rule_actions_legacy/logic/rule_actions/legacy_get_bulk_rule_actions_saved_object.test.ts => alerting/server/rules_client/lib/siem_legacy_actions/format_legacy_actions.test.ts} (66%) create mode 100644 x-pack/plugins/alerting/server/rules_client/lib/siem_legacy_actions/format_legacy_actions.ts create mode 100644 x-pack/plugins/alerting/server/rules_client/lib/siem_legacy_actions/migrate_legacy_actions.test.ts create mode 100644 x-pack/plugins/alerting/server/rules_client/lib/siem_legacy_actions/migrate_legacy_actions.ts create mode 100644 x-pack/plugins/alerting/server/rules_client/lib/siem_legacy_actions/retrieve_migrated_legacy_actions.mock.ts create mode 100644 x-pack/plugins/alerting/server/rules_client/lib/siem_legacy_actions/retrieve_migrated_legacy_actions.test.ts create mode 100644 x-pack/plugins/alerting/server/rules_client/lib/siem_legacy_actions/retrieve_migrated_legacy_actions.ts create mode 100644 x-pack/plugins/alerting/server/rules_client/lib/siem_legacy_actions/transform_legacy_actions.test.ts create mode 100644 x-pack/plugins/alerting/server/rules_client/lib/siem_legacy_actions/transform_legacy_actions.ts create mode 100644 x-pack/plugins/alerting/server/rules_client/lib/siem_legacy_actions/transform_to_notify_when.test.ts create mode 100644 x-pack/plugins/alerting/server/rules_client/lib/siem_legacy_actions/transform_to_notify_when.ts create mode 100644 x-pack/plugins/alerting/server/rules_client/lib/siem_legacy_actions/types.ts delete mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions_legacy/logic/rule_actions/legacy_get_bulk_rule_actions_saved_object.ts delete mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/rule_actions/legacy_action_migration.test.ts delete mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/rule_actions/legacy_action_migration.ts diff --git a/x-pack/plugins/alerting/server/rules_client/lib/index.ts b/x-pack/plugins/alerting/server/rules_client/lib/index.ts index 9f48184b94df6..0dcaa31fe51a1 100644 --- a/x-pack/plugins/alerting/server/rules_client/lib/index.ts +++ b/x-pack/plugins/alerting/server/rules_client/lib/index.ts @@ -15,5 +15,7 @@ export { checkAuthorizationAndGetTotal } from './check_authorization_and_get_tot export { scheduleTask } from './schedule_task'; export { createNewAPIKeySet } from './create_new_api_key_set'; export { recoverRuleAlerts } from './recover_rule_alerts'; +export { migrateLegacyActions } from './siem_legacy_actions/migrate_legacy_actions'; +export { formatLegacyActions } from './siem_legacy_actions/format_legacy_actions'; export { addGeneratedActionValues } from './add_generated_action_values'; export { incrementRevision } from './increment_revision'; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions_legacy/logic/rule_actions/legacy_get_bulk_rule_actions_saved_object.test.ts b/x-pack/plugins/alerting/server/rules_client/lib/siem_legacy_actions/format_legacy_actions.test.ts similarity index 66% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions_legacy/logic/rule_actions/legacy_get_bulk_rule_actions_saved_object.test.ts rename to x-pack/plugins/alerting/server/rules_client/lib/siem_legacy_actions/format_legacy_actions.test.ts index 6caca1b085be7..072a1e98cc3de 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions_legacy/logic/rule_actions/legacy_get_bulk_rule_actions_saved_object.test.ts +++ b/x-pack/plugins/alerting/server/rules_client/lib/siem_legacy_actions/format_legacy_actions.test.ts @@ -5,23 +5,23 @@ * 2.0. */ -import type { SavedObjectsFindOptions, SavedObjectsFindResult } from '@kbn/core/server'; +import type { SavedObjectsFindResult, SavedObjectAttribute } from '@kbn/core/server'; import { loggingSystemMock, savedObjectsClientMock } from '@kbn/core/server/mocks'; -// eslint-disable-next-line no-restricted-imports -import { legacyGetBulkRuleActionsSavedObject } from './legacy_get_bulk_rule_actions_saved_object'; -// eslint-disable-next-line no-restricted-imports -import type { LegacyRulesActionsSavedObject } from './legacy_get_rule_actions_saved_object'; -// eslint-disable-next-line no-restricted-imports -import { legacyRuleActionsSavedObjectType } from './legacy_saved_object_mappings'; -// eslint-disable-next-line no-restricted-imports -import type { LegacyIRuleActionsAttributesSavedObjectAttributes } from './legacy_types'; +import { Rule } from '../../../types'; -describe('legacy_get_bulk_rule_actions_saved_object', () => { +import { + legacyGetBulkRuleActionsSavedObject, + LegacyActionsObj, + formatLegacyActions, +} from './format_legacy_actions'; +import { legacyRuleActionsSavedObjectType } from './types'; + +describe('legacyGetBulkRuleActionsSavedObject', () => { let savedObjectsClient: ReturnType; let logger: ReturnType; - type FuncReturn = Record; + type FuncReturn = Record; beforeEach(() => { logger = loggingSystemMock.createLogger(); @@ -34,10 +34,9 @@ describe('legacy_get_bulk_rule_actions_saved_object', () => { }); }); - test('calls "savedObjectsClient.find" with the expected "hasReferences"', () => { - legacyGetBulkRuleActionsSavedObject({ alertIds: ['123'], savedObjectsClient, logger }); - const [[arg1]] = savedObjectsClient.find.mock.calls; - expect(arg1).toEqual({ + test('calls "savedObjectsClient.find" with the expected "hasReferences"', async () => { + await legacyGetBulkRuleActionsSavedObject({ alertIds: ['123'], savedObjectsClient, logger }); + expect(savedObjectsClient.find).toHaveBeenCalledWith({ hasReference: [{ id: '123', type: 'alert' }], perPage: 10000, type: legacyRuleActionsSavedObjectType, @@ -45,9 +44,7 @@ describe('legacy_get_bulk_rule_actions_saved_object', () => { }); test('returns nothing transformed through the find if it does not return any matches against the alert id', async () => { - const savedObjects: Array< - SavedObjectsFindResult - > = []; + const savedObjects: Array> = []; savedObjectsClient.find.mockResolvedValue({ total: 0, per_page: 0, @@ -64,9 +61,7 @@ describe('legacy_get_bulk_rule_actions_saved_object', () => { }); test('returns 1 action transformed through the find if 1 was found for 1 single alert id', async () => { - const savedObjects: Array< - SavedObjectsFindResult - > = [ + const savedObjects: Array> = [ { score: 0, id: '123', @@ -111,12 +106,15 @@ describe('legacy_get_bulk_rule_actions_saved_object', () => { }); expect(returnValue).toEqual({ 'alert-123': { - id: '123', - alertThrottle: '1d', ruleThrottle: '1d', - actions: [ + legacyRuleActions: [ { - action_type_id: 'action_type_1', + actionTypeId: 'action_type_1', + frequency: { + notifyWhen: 'onThrottleInterval', + summary: true, + throttle: '1d', + }, group: 'group_1', id: 'action-123', params: {}, @@ -127,9 +125,7 @@ describe('legacy_get_bulk_rule_actions_saved_object', () => { }); test('returns 1 action transformed through the find for 2 alerts with 1 action each', async () => { - const savedObjects: Array< - SavedObjectsFindResult - > = [ + const savedObjects: Array> = [ { score: 0, id: '123', @@ -203,12 +199,16 @@ describe('legacy_get_bulk_rule_actions_saved_object', () => { }); expect(returnValue).toEqual({ 'alert-123': { - id: '123', - alertThrottle: '1d', ruleThrottle: '1d', - actions: [ + + legacyRuleActions: [ { - action_type_id: 'action_type_1', + actionTypeId: 'action_type_1', + frequency: { + notifyWhen: 'onThrottleInterval', + summary: true, + throttle: '1d', + }, group: 'group_1', id: 'action-123', params: {}, @@ -216,12 +216,15 @@ describe('legacy_get_bulk_rule_actions_saved_object', () => { ], }, 'alert-456': { - id: '456', - alertThrottle: '1d', ruleThrottle: '1d', - actions: [ + legacyRuleActions: [ { - action_type_id: 'action_type_2', + actionTypeId: 'action_type_2', + frequency: { + notifyWhen: 'onThrottleInterval', + summary: true, + throttle: '1d', + }, group: 'group_2', id: 'action-456', params: {}, @@ -232,9 +235,7 @@ describe('legacy_get_bulk_rule_actions_saved_object', () => { }); test('returns 2 actions transformed through the find if they were found for 1 single alert id', async () => { - const savedObjects: Array< - SavedObjectsFindResult - > = [ + const savedObjects: Array> = [ { score: 0, id: '123', @@ -290,18 +291,26 @@ describe('legacy_get_bulk_rule_actions_saved_object', () => { }); expect(returnValue).toEqual({ 'alert-123': { - id: '123', - alertThrottle: '1d', ruleThrottle: '1d', - actions: [ + legacyRuleActions: [ { - action_type_id: 'action_type_1', + actionTypeId: 'action_type_1', + frequency: { + notifyWhen: 'onThrottleInterval', + summary: true, + throttle: '1d', + }, group: 'group_1', id: 'action-123', params: {}, }, { - action_type_id: 'action_type_2', + actionTypeId: 'action_type_2', + frequency: { + notifyWhen: 'onThrottleInterval', + summary: true, + throttle: '1d', + }, group: 'group_2', id: 'action-456', params: {}, @@ -312,9 +321,7 @@ describe('legacy_get_bulk_rule_actions_saved_object', () => { }); test('returns only 1 action if for some unusual reason the actions reference is missing an item for 1 single alert id', async () => { - const savedObjects: Array< - SavedObjectsFindResult - > = [ + const savedObjects: Array> = [ { score: 0, id: '123', @@ -366,12 +373,15 @@ describe('legacy_get_bulk_rule_actions_saved_object', () => { }); expect(returnValue).toEqual({ 'alert-123': { - id: '123', - alertThrottle: '1d', ruleThrottle: '1d', - actions: [ + legacyRuleActions: [ { - action_type_id: 'action_type_1', + actionTypeId: 'action_type_1', + frequency: { + notifyWhen: 'onThrottleInterval', + summary: true, + throttle: '1d', + }, group: 'group_1', id: 'action-123', params: {}, @@ -382,9 +392,7 @@ describe('legacy_get_bulk_rule_actions_saved_object', () => { }); test('returns only 1 action if for some unusual reason the action is missing from the attributes', async () => { - const savedObjects: Array< - SavedObjectsFindResult - > = [ + const savedObjects: Array> = [ { score: 0, id: '123', @@ -435,12 +443,15 @@ describe('legacy_get_bulk_rule_actions_saved_object', () => { }); expect(returnValue).toEqual({ 'alert-123': { - id: '123', - alertThrottle: '1d', ruleThrottle: '1d', - actions: [ + legacyRuleActions: [ { - action_type_id: 'action_type_1', + actionTypeId: 'action_type_1', + frequency: { + notifyWhen: 'onThrottleInterval', + summary: true, + throttle: '1d', + }, group: 'group_1', id: 'action-123', params: {}, @@ -451,9 +462,7 @@ describe('legacy_get_bulk_rule_actions_saved_object', () => { }); test('returns nothing if the alert id is missing within the references array', async () => { - const savedObjects: Array< - SavedObjectsFindResult - > = [ + const savedObjects: Array> = [ { score: 0, id: '123', @@ -495,3 +504,112 @@ describe('legacy_get_bulk_rule_actions_saved_object', () => { expect(returnValue).toEqual({}); }); }); + +describe('formatLegacyActions', () => { + let savedObjectsClient: ReturnType; + let logger: ReturnType; + + beforeEach(() => { + logger = loggingSystemMock.createLogger(); + savedObjectsClient = savedObjectsClientMock.create(); + }); + + it('should return not modified rule when error is thrown within method', async () => { + savedObjectsClient.find.mockRejectedValueOnce(new Error('test failure')); + const mockRules = [{ id: 'mock-id0' }, { id: 'mock-id1' }] as Rule[]; + expect( + await formatLegacyActions(mockRules, { + logger, + savedObjectsClient, + }) + ).toEqual(mockRules); + + expect(logger.error).toHaveBeenCalledWith( + `formatLegacyActions(): Failed to read legacy actions for SIEM rules mock-id0, mock-id1: test failure` + ); + }); + + it('should format rule correctly', async () => { + const savedObjects: Array> = [ + { + score: 0, + id: '123', + type: legacyRuleActionsSavedObjectType, + references: [ + { + name: 'alert_0', + id: 'alert-123', + type: 'alert', + }, + { + name: 'action_0', + id: 'action-123', + type: 'action', + }, + ], + attributes: { + actions: [ + { + group: 'group_1', + params: {}, + action_type_id: 'action_type_1', + actionRef: 'action_0', + }, + ], + ruleThrottle: '1d', + alertThrottle: '1d', + }, + }, + ]; + savedObjectsClient.find.mockResolvedValue({ + total: 0, + per_page: 0, + page: 1, + saved_objects: savedObjects, + }); + + const mockRules = [ + { + id: 'alert-123', + actions: [ + { + actionTypeId: 'action_type_2', + group: 'group_1', + id: 'action-456', + params: {}, + }, + ], + }, + ] as Rule[]; + const migratedRules = await formatLegacyActions(mockRules, { + logger, + savedObjectsClient, + }); + + expect(migratedRules).toEqual([ + { + // actions have been merged + actions: [ + { + actionTypeId: 'action_type_2', + group: 'group_1', + id: 'action-456', + params: {}, + }, + { + actionTypeId: 'action_type_1', + frequency: { notifyWhen: 'onThrottleInterval', summary: true, throttle: '1d' }, + group: 'group_1', + id: 'action-123', + params: {}, + }, + ], + id: 'alert-123', + // muteAll set to false + muteAll: false, + notifyWhen: 'onThrottleInterval', + throttle: '1d', + }, + ]); + }); +}); diff --git a/x-pack/plugins/alerting/server/rules_client/lib/siem_legacy_actions/format_legacy_actions.ts b/x-pack/plugins/alerting/server/rules_client/lib/siem_legacy_actions/format_legacy_actions.ts new file mode 100644 index 0000000000000..8689113ca7907 --- /dev/null +++ b/x-pack/plugins/alerting/server/rules_client/lib/siem_legacy_actions/format_legacy_actions.ts @@ -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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { chunk } from 'lodash'; +import type { SavedObjectsFindOptionsReference, Logger } from '@kbn/core/server'; +import pMap from 'p-map'; +import { RuleAction, Rule } from '../../../types'; +import type { RuleExecutorServices } from '../../..'; +import { injectReferencesIntoActions } from '../../common'; +import { transformToNotifyWhen } from './transform_to_notify_when'; +import { transformFromLegacyActions } from './transform_legacy_actions'; +import { LegacyIRuleActionsAttributes, legacyRuleActionsSavedObjectType } from './types'; + +/** + * @deprecated Once we are confident all rules relying on side-car actions SO's have been migrated to SO references we should remove this function + */ +interface LegacyGetBulkRuleActionsSavedObject { + alertIds: string[]; + savedObjectsClient: RuleExecutorServices['savedObjectsClient']; + logger: Logger; +} + +/** + * @deprecated Once we are confident all rules relying on side-car actions SO's have been migrated to SO references we should remove this function + */ +export interface LegacyActionsObj { + ruleThrottle: string | null; + legacyRuleActions: RuleAction[]; +} + +/** + * @deprecated Once we are confident all rules relying on side-car actions SO's have been migrated to SO references we should remove this function + * this function finds all legacy actions associated with rules in bulk + * it's useful for such methods as find, so we do not request legacy actions in a separate request per rule + * @params params.alertIds - list of rule ids to look for legacy actions for + * @params params.savedObjectsClient - savedObjectsClient + * @params params.logger - logger + * @returns map of legacy actions objects per rule with legacy actions + */ +export const legacyGetBulkRuleActionsSavedObject = async ({ + alertIds, + savedObjectsClient, + logger, +}: LegacyGetBulkRuleActionsSavedObject): Promise> => { + const references = alertIds.map((alertId) => ({ + id: alertId, + type: 'alert', + })); + const errors: unknown[] = []; + const results = await pMap( + chunk(references, 1000), + async (referencesChunk) => { + try { + return savedObjectsClient.find({ + // here we looking legacyRuleActionsSavedObjectType, as not all of rules create `siem.notifications` + // more information on that can be found in https://github.com/elastic/kibana/pull/130511 PR + type: legacyRuleActionsSavedObjectType, + perPage: 10000, + hasReference: referencesChunk, + }); + } catch (error) { + logger.error( + `Error fetching rule actions: ${error instanceof Error ? error.message : String(error)}` + ); + errors.push(error); + return []; + } + }, + { concurrency: 1 } + ); + const actionSavedObjects = results.flat().flatMap((r) => r.saved_objects); + + if (errors.length) { + throw new AggregateError(errors, 'Error fetching rule actions'); + } + + return actionSavedObjects.reduce((acc: { [key: string]: LegacyActionsObj }, savedObject) => { + const ruleAlertId = savedObject.references.find((reference) => { + // Find the first rule alert and assume that is the one we want since we should only ever have 1. + return reference.type === 'alert'; + }); + // We check to ensure we have found a "ruleAlertId" and hopefully we have. + const ruleAlertIdKey = ruleAlertId != null ? ruleAlertId.id : undefined; + if (ruleAlertIdKey != null) { + const legacyRawActions = transformFromLegacyActions( + savedObject.attributes, + savedObject.references + ); + acc[ruleAlertIdKey] = { + ruleThrottle: savedObject.attributes.ruleThrottle, + legacyRuleActions: injectReferencesIntoActions( + ruleAlertIdKey, + legacyRawActions, + savedObject.references + ) // remove uuid from action, as this uuid is not persistent + .map(({ uuid, ...action }) => action), + }; + } else { + logger.error( + `Security Solution notification (Legacy) Was expecting to find a reference of type "alert" within ${savedObject.references} but did not. Skipping this notification.` + ); + } + return acc; + }, {}); +}; + +/** + * @deprecated Once we are confident all rules relying on side-car actions SO's have been migrated to SO references we should remove this function + * formats rules with associated SIEM legacy actions, if any legacy actions present + * @param rules - list of rules to format + * @param params - logger, savedObjectsClient + * @returns + */ +export const formatLegacyActions = async ( + rules: T[], + { logger, savedObjectsClient }: Omit +): Promise => { + try { + const res = await legacyGetBulkRuleActionsSavedObject({ + alertIds: rules.map((rule) => rule.id), + savedObjectsClient, + logger, + }); + + return rules.map((rule) => { + const legacyRuleActionsMatch = res[rule.id]; + if (!legacyRuleActionsMatch) { + return rule; + } + + const { legacyRuleActions, ruleThrottle } = legacyRuleActionsMatch; + return { + ...rule, + actions: [...rule.actions, ...legacyRuleActions], + throttle: (legacyRuleActions.length ? ruleThrottle : rule.throttle) ?? 'no_actions', + notifyWhen: transformToNotifyWhen(ruleThrottle), + // muteAll property is disregarded in further rule processing in Security Solution when legacy actions are present. + // So it should be safe to set it as false, so it won't be displayed to user as w/o actions see transformFromAlertThrottle method + muteAll: legacyRuleActions.length ? false : rule.muteAll, + }; + }); + } catch (e) { + const ruleIds = rules.map((rule) => rule.id).join(', '); + logger.error( + `formatLegacyActions(): Failed to read legacy actions for SIEM rules ${ruleIds}: ${e.message}` + ); + return rules; + } +}; diff --git a/x-pack/plugins/alerting/server/rules_client/lib/siem_legacy_actions/migrate_legacy_actions.test.ts b/x-pack/plugins/alerting/server/rules_client/lib/siem_legacy_actions/migrate_legacy_actions.test.ts new file mode 100644 index 0000000000000..bf67bb5a97191 --- /dev/null +++ b/x-pack/plugins/alerting/server/rules_client/lib/siem_legacy_actions/migrate_legacy_actions.test.ts @@ -0,0 +1,357 @@ +/* + * Copyright 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 { AlertConsumers } from '@kbn/rule-data-utils'; + +import type { SavedObjectReference } from '@kbn/core/server'; + +import { migrateLegacyActions } from './migrate_legacy_actions'; +import { retrieveMigratedLegacyActions } from './retrieve_migrated_legacy_actions'; +import { injectReferencesIntoActions } from '../../common'; + +import { validateActions } from '../validate_actions'; +import { RulesClientContext } from '../..'; +import { RawRuleAction, RawRule } from '../../../types'; + +import { UntypedNormalizedRuleType } from '../../../rule_type_registry'; +import { RecoveredActionGroup } from '../../../../common'; + +jest.mock('./retrieve_migrated_legacy_actions', () => ({ + retrieveMigratedLegacyActions: jest.fn(), +})); + +jest.mock('../validate_actions', () => ({ + validateActions: jest.fn(), +})); + +jest.mock('../../common', () => ({ + injectReferencesIntoActions: jest.fn(), +})); + +const ruleType: jest.Mocked = { + id: 'test', + name: 'My test rule', + actionGroups: [{ id: 'default', name: 'Default' }, RecoveredActionGroup], + defaultActionGroupId: 'default', + minimumLicenseRequired: 'basic', + isExportable: true, + recoveryActionGroup: RecoveredActionGroup, + executor: jest.fn(), + producer: 'alerts', + cancelAlertsOnRuleTimeout: true, + ruleTaskTimeout: '5m', + getSummarizedAlerts: jest.fn(), +}; + +const context = { + ruleTypeRegistry: { + get: () => ruleType, + }, + logger: { + error: jest.fn(), + }, +} as unknown as RulesClientContext; + +const ruleId = 'rule_id_1'; + +const attributes = { + alertTypeId: 'siem.query', + consumer: AlertConsumers.SIEM, +} as unknown as RawRule; + +(retrieveMigratedLegacyActions as jest.Mock).mockResolvedValue({ + legacyActions: [], + legacyActionsReferences: [], +}); + +const legacyActionsMock: RawRuleAction[] = [ + { + group: 'default', + params: { + message: 'Rule {{context.rule.name}} generated {{state.signals_count}} alerts', + to: ['test@test.com'], + subject: 'Test Actions', + }, + actionTypeId: '.email', + uuid: '11403909-ca9b-49ba-9d7a-7e5320e68d05', + actionRef: 'action_0', + frequency: { + notifyWhen: 'onThrottleInterval', + summary: true, + throttle: '1d', + }, + }, +]; + +const legacyReferencesMock: SavedObjectReference[] = [ + { + id: 'cc85da20-d480-11ed-8e69-1df522116c28', + name: 'action_0', + type: 'action', + }, +]; + +const existingActionsMock: RawRuleAction[] = [ + { + group: 'default', + params: { + body: { + test_web_hook: 'alert.id - {{alert.id}}', + }, + }, + actionTypeId: '.webhook', + uuid: '6e253775-693c-4dcb-a4f5-ad37d9524ecf', + actionRef: 'action_0', + }, +]; + +const referencesMock: SavedObjectReference[] = [ + { + id: 'b2fd3f90-cd81-11ed-9f6d-a746729ca213', + name: 'action_0', + type: 'action', + }, +]; + +describe('migrateLegacyActions', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('should return empty migratedActions when error is thrown within method', async () => { + (retrieveMigratedLegacyActions as jest.Mock).mockRejectedValueOnce(new Error('test failure')); + const migratedActions = await migrateLegacyActions(context, { + ruleId, + attributes, + }); + + expect(migratedActions).toEqual({ + resultedActions: [], + hasLegacyActions: false, + resultedReferences: [], + }); + expect(context.logger.error).toHaveBeenCalledWith( + `migrateLegacyActions(): Failed to migrate legacy actions for SIEM rule ${ruleId}: test failure` + ); + }); + + it('should return earley empty migratedActions when consumer is not SIEM', async () => { + (retrieveMigratedLegacyActions as jest.Mock).mockResolvedValue({ + legacyActions: [], + legacyActionsReferences: [], + }); + const migratedActions = await migrateLegacyActions(context, { + ruleId, + attributes: { ...attributes, consumer: 'mine' }, + }); + + expect(migratedActions).toEqual({ + resultedActions: [], + hasLegacyActions: false, + resultedReferences: [], + }); + expect(retrieveMigratedLegacyActions).not.toHaveBeenCalled(); + expect(validateActions).not.toHaveBeenCalled(); + expect(injectReferencesIntoActions).not.toHaveBeenCalled(); + }); + + it('should call retrieveMigratedLegacyActions with correct rule id', async () => { + (retrieveMigratedLegacyActions as jest.Mock).mockResolvedValue({ + legacyActions: [], + legacyActionsReferences: [], + }); + await migrateLegacyActions(context, { ruleId, attributes }); + + expect(retrieveMigratedLegacyActions).toHaveBeenCalledWith(context, { ruleId }); + }); + + it('should not call validateActions and injectReferencesIntoActions if skipActionsValidation=true', async () => { + await migrateLegacyActions(context, { ruleId, attributes, skipActionsValidation: true }); + + expect(validateActions).not.toHaveBeenCalled(); + expect(injectReferencesIntoActions).not.toHaveBeenCalled(); + }); + + it('should call validateActions and injectReferencesIntoActions if attributes provided', async () => { + (retrieveMigratedLegacyActions as jest.Mock).mockResolvedValueOnce({ + legacyActions: legacyActionsMock, + legacyActionsReferences: legacyReferencesMock, + }); + + (injectReferencesIntoActions as jest.Mock).mockReturnValue('actions-with-references'); + await migrateLegacyActions(context, { ruleId, attributes }); + + expect(validateActions).toHaveBeenCalledWith(context, ruleType, { + ...attributes, + actions: 'actions-with-references', + }); + + expect(injectReferencesIntoActions).toHaveBeenCalledWith( + 'rule_id_1', + [ + { + actionRef: 'action_0', + actionTypeId: '.email', + group: 'default', + params: { + message: 'Rule {{context.rule.name}} generated {{state.signals_count}} alerts', + subject: 'Test Actions', + to: ['test@test.com'], + }, + uuid: '11403909-ca9b-49ba-9d7a-7e5320e68d05', + frequency: { + notifyWhen: 'onThrottleInterval', + summary: true, + throttle: '1d', + }, + }, + ], + [{ id: 'cc85da20-d480-11ed-8e69-1df522116c28', name: 'action_0', type: 'action' }] + ); + }); + + it('should set frequency props from rule level to existing actions', async () => { + const result = await migrateLegacyActions(context, { + ruleId, + actions: existingActionsMock, + references: referencesMock, + attributes: { ...attributes, throttle: '1h', notifyWhen: 'onThrottleInterval' }, + }); + + expect(result).toHaveProperty('hasLegacyActions', false); + expect(result).toHaveProperty('resultedReferences', referencesMock); + expect(result).toHaveProperty('resultedActions', [ + { + actionRef: 'action_0', + actionTypeId: '.webhook', + group: 'default', + params: { body: { test_web_hook: 'alert.id - {{alert.id}}' } }, + uuid: '6e253775-693c-4dcb-a4f5-ad37d9524ecf', + frequency: { + notifyWhen: 'onThrottleInterval', + summary: true, + throttle: '1h', + }, + }, + ]); + }); + + it('should return correct response when legacy actions empty and existing empty', async () => { + const result = await migrateLegacyActions(context, { + ruleId, + actions: existingActionsMock, + references: referencesMock, + attributes, + }); + + expect(result).toHaveProperty('hasLegacyActions', false); + expect(result).toHaveProperty('resultedReferences', referencesMock); + expect(result).toHaveProperty('resultedActions', [ + { + actionRef: 'action_0', + actionTypeId: '.webhook', + group: 'default', + params: { body: { test_web_hook: 'alert.id - {{alert.id}}' } }, + uuid: '6e253775-693c-4dcb-a4f5-ad37d9524ecf', + frequency: { + notifyWhen: 'onActiveAlert', + summary: true, + throttle: null, + }, + }, + ]); + }); + + it('should return correct response when legacy actions empty and existing actions empty', async () => { + const result = await migrateLegacyActions(context, { + ruleId, + attributes, + }); + + expect(result).toHaveProperty('hasLegacyActions', false); + expect(result).toHaveProperty('resultedReferences', []); + expect(result).toHaveProperty('resultedActions', []); + }); + it('should return correct response when existing actions empty and legacy present', async () => { + (retrieveMigratedLegacyActions as jest.Mock).mockResolvedValueOnce({ + legacyActions: legacyActionsMock, + legacyActionsReferences: legacyReferencesMock, + }); + + const result = await migrateLegacyActions(context, { + ruleId, + attributes, + }); + + expect(result).toHaveProperty('hasLegacyActions', true); + expect(result).toHaveProperty('resultedReferences', legacyReferencesMock); + expect(result).toHaveProperty('resultedActions', legacyActionsMock); + }); + it('should merge actions and references correctly when existing and legacy actions both present', async () => { + (retrieveMigratedLegacyActions as jest.Mock).mockResolvedValueOnce({ + legacyActions: legacyActionsMock, + legacyActionsReferences: legacyReferencesMock, + }); + + const result = await migrateLegacyActions(context, { + ruleId, + actions: existingActionsMock, + references: referencesMock, + attributes, + }); + + expect(result.resultedReferences[0].name).toBe('action_0'); + expect(result.resultedReferences[1].name).toBe('action_1'); + + expect(result).toHaveProperty('hasLegacyActions', true); + + // ensure references are correct + expect(result.resultedReferences[0].name).toBe('action_0'); + expect(result.resultedReferences[1].name).toBe('action_1'); + expect(result).toHaveProperty('resultedReferences', [ + { + id: 'b2fd3f90-cd81-11ed-9f6d-a746729ca213', + name: 'action_0', + type: 'action', + }, + { + id: 'cc85da20-d480-11ed-8e69-1df522116c28', + name: 'action_1', + type: 'action', + }, + ]); + + // ensure actionsRefs are correct + expect(result.resultedActions[0].actionRef).toBe('action_0'); + expect(result.resultedActions[1].actionRef).toBe('action_1'); + expect(result).toHaveProperty('resultedActions', [ + { + actionRef: 'action_0', + actionTypeId: '.webhook', + group: 'default', + params: { body: { test_web_hook: 'alert.id - {{alert.id}}' } }, + uuid: '6e253775-693c-4dcb-a4f5-ad37d9524ecf', + frequency: { + notifyWhen: 'onActiveAlert', + summary: true, + throttle: null, + }, + }, + { + actionRef: 'action_1', + actionTypeId: '.email', + frequency: { notifyWhen: 'onThrottleInterval', summary: true, throttle: '1d' }, + group: 'default', + params: { + message: 'Rule {{context.rule.name}} generated {{state.signals_count}} alerts', + subject: 'Test Actions', + to: ['test@test.com'], + }, + uuid: '11403909-ca9b-49ba-9d7a-7e5320e68d05', + }, + ]); + }); +}); diff --git a/x-pack/plugins/alerting/server/rules_client/lib/siem_legacy_actions/migrate_legacy_actions.ts b/x-pack/plugins/alerting/server/rules_client/lib/siem_legacy_actions/migrate_legacy_actions.ts new file mode 100644 index 0000000000000..c092e3fed982b --- /dev/null +++ b/x-pack/plugins/alerting/server/rules_client/lib/siem_legacy_actions/migrate_legacy_actions.ts @@ -0,0 +1,113 @@ +/* + * Copyright 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 { AlertConsumers } from '@kbn/rule-data-utils'; + +import type { SavedObjectReference } from '@kbn/core/server'; +import type { RulesClientContext } from '../..'; +import { RawRuleAction, RawRule } from '../../../types'; +import { validateActions } from '../validate_actions'; +import { injectReferencesIntoActions } from '../../common'; +import { retrieveMigratedLegacyActions } from './retrieve_migrated_legacy_actions'; + +type MigrateLegacyActions = ( + context: RulesClientContext, + params: { + ruleId: string; + references?: SavedObjectReference[]; + actions?: RawRuleAction[]; + attributes: RawRule; + skipActionsValidation?: boolean; + } +) => Promise<{ + resultedActions: RawRuleAction[]; + resultedReferences: SavedObjectReference[]; + hasLegacyActions: boolean; +}>; + +/** + * @deprecated Once we are confident all rules relying on side-car actions SO's have been migrated to SO references we should remove this function + * migrates SIEM legacy actions and merges rule actions and references + * @param context RulesClient context + * @param params + * @returns + */ +export const migrateLegacyActions: MigrateLegacyActions = async ( + context: RulesClientContext, + { ruleId, actions = [], references = [], attributes, skipActionsValidation } +) => { + try { + if (attributes.consumer !== AlertConsumers.SIEM) { + return { + resultedActions: [], + hasLegacyActions: false, + resultedReferences: [], + }; + } + + const { legacyActions, legacyActionsReferences } = await retrieveMigratedLegacyActions( + context, + { + ruleId, + } + ); + + // sometimes we don't need to validate legacy actions. For example, when delete rules or update rule from payload + if (skipActionsValidation !== true) { + const ruleType = context.ruleTypeRegistry.get(attributes.alertTypeId); + await validateActions(context, ruleType, { + ...attributes, + // set to undefined to avoid both per-actin and rule level values clashing + throttle: undefined, + notifyWhen: undefined, + actions: injectReferencesIntoActions(ruleId, legacyActions, legacyActionsReferences), + }); + } + + // fix references for a case when actions present in a rule + if (actions.length) { + legacyActions.forEach((legacyAction, i) => { + const oldReference = legacyAction.actionRef; + const legacyReference = legacyActionsReferences.find(({ name }) => name === oldReference); + const newReference = `action_${actions.length + i}`; + legacyAction.actionRef = newReference; + + if (legacyReference) { + legacyReference.name = newReference; + } + }); + } + + // put frequencies into existing actions + // the situation when both action in rule nad legacy exist should be rare one + // but if it happens, we put frequency in existing actions per-action level + const existingActionsWithFrequencies: RawRuleAction[] = actions.map((action) => ({ + ...action, + frequency: { + summary: true, + notifyWhen: attributes.notifyWhen ?? 'onActiveAlert', + throttle: attributes.throttle ?? null, + }, + })); + + return { + resultedActions: [...existingActionsWithFrequencies, ...legacyActions], + hasLegacyActions: legacyActions.length > 0, + resultedReferences: [...references, ...legacyActionsReferences], + }; + } catch (e) { + context.logger.error( + `migrateLegacyActions(): Failed to migrate legacy actions for SIEM rule ${ruleId}: ${e.message}` + ); + + return { + resultedActions: [], + hasLegacyActions: false, + resultedReferences: [], + }; + } +}; diff --git a/x-pack/plugins/alerting/server/rules_client/lib/siem_legacy_actions/retrieve_migrated_legacy_actions.mock.ts b/x-pack/plugins/alerting/server/rules_client/lib/siem_legacy_actions/retrieve_migrated_legacy_actions.mock.ts new file mode 100644 index 0000000000000..d45c86c3f05a5 --- /dev/null +++ b/x-pack/plugins/alerting/server/rules_client/lib/siem_legacy_actions/retrieve_migrated_legacy_actions.mock.ts @@ -0,0 +1,368 @@ +/* + * Copyright 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 { + SavedObjectsFindResponse, + SavedObjectsFindResult, + SavedObjectAttribute, +} from '@kbn/core/server'; + +import type { LegacyRuleNotificationAlertType } from './types'; + +export const migrateLegacyActionsMock = { + legacyActions: [], + legacyActionsReferences: [], +}; + +/** + * @deprecated Once we are confident all rules relying on side-car actions SO's have been migrated to SO references we should remove this function + */ +export const legacyGetHourlyNotificationResult = ( + id = '456', + ruleId = '123' +): LegacyRuleNotificationAlertType => ({ + id, + name: 'Notification for Rule Test', + tags: [], + alertTypeId: 'siem.notifications', + consumer: 'siem', + params: { + ruleAlertId: `${ruleId}`, + }, + schedule: { + interval: '1h', + }, + enabled: true, + actions: [ + { + group: 'default', + params: { + message: 'Rule {{context.rule.name}} generated {{state.signals_count}} alerts', + to: ['test@test.com'], + subject: 'Test Actions', + }, + actionTypeId: '.email', + id: '99403909-ca9b-49ba-9d7a-7e5320e68d05', + }, + ], + throttle: null, + notifyWhen: 'onActiveAlert', + apiKey: null, + apiKeyOwner: 'elastic', + createdBy: 'elastic', + updatedBy: 'elastic', + createdAt: new Date('2020-03-21T11:15:13.530Z'), + muteAll: false, + mutedInstanceIds: [], + scheduledTaskId: '62b3a130-6b70-11ea-9ce9-6b9818c4cbd7', + updatedAt: new Date('2020-03-21T12:37:08.730Z'), + executionStatus: { + status: 'unknown', + lastExecutionDate: new Date('2020-08-20T19:23:38Z'), + }, + revision: 0, +}); + +/** + * @deprecated Once we are confident all rules relying on side-car actions SO's have been migrated to SO references we should remove this function + */ +export const legacyGetWeeklyNotificationResult = ( + id = '456', + ruleId = '123' +): LegacyRuleNotificationAlertType => ({ + id, + name: 'Notification for Rule Test', + tags: [], + alertTypeId: 'siem.notifications', + consumer: 'siem', + params: { + ruleAlertId: `${ruleId}`, + }, + schedule: { + interval: '7d', + }, + enabled: true, + actions: [ + { + group: 'default', + params: { + message: 'Rule {{context.rule.name}} generated {{state.signals_count}} alerts', + to: ['test@test.com'], + subject: 'Test Actions', + }, + actionTypeId: '.email', + id: '99403909-ca9b-49ba-9d7a-7e5320e68d05', + }, + ], + throttle: null, + notifyWhen: 'onActiveAlert', + apiKey: null, + apiKeyOwner: 'elastic', + createdBy: 'elastic', + updatedBy: 'elastic', + createdAt: new Date('2020-03-21T11:15:13.530Z'), + muteAll: false, + mutedInstanceIds: [], + scheduledTaskId: '62b3a130-6b70-11ea-9ce9-6b9818c4cbd7', + updatedAt: new Date('2020-03-21T12:37:08.730Z'), + executionStatus: { + status: 'unknown', + lastExecutionDate: new Date('2020-08-20T19:23:38Z'), + }, + revision: 0, +}); + +/** + * @deprecated Once we are confident all rules relying on side-car actions SO's have been migrated to SO references we should remove this function + */ +export const legacyGetDailyNotificationResult = ( + id = '456', + ruleId = '123' +): LegacyRuleNotificationAlertType => ({ + id, + name: 'Notification for Rule Test', + tags: [], + alertTypeId: 'siem.notifications', + consumer: 'siem', + params: { + ruleAlertId: `${ruleId}`, + }, + schedule: { + interval: '1d', + }, + enabled: true, + actions: [ + { + group: 'default', + params: { + message: 'Rule {{context.rule.name}} generated {{state.signals_count}} alerts', + to: ['test@test.com'], + subject: 'Test Actions', + }, + actionTypeId: '.email', + id: '99403909-ca9b-49ba-9d7a-7e5320e68d05', + }, + ], + throttle: null, + notifyWhen: 'onActiveAlert', + apiKey: null, + apiKeyOwner: 'elastic', + createdBy: 'elastic', + updatedBy: 'elastic', + createdAt: new Date('2020-03-21T11:15:13.530Z'), + muteAll: false, + mutedInstanceIds: [], + scheduledTaskId: '62b3a130-6b70-11ea-9ce9-6b9818c4cbd7', + updatedAt: new Date('2020-03-21T12:37:08.730Z'), + executionStatus: { + status: 'unknown', + lastExecutionDate: new Date('2020-08-20T19:23:38Z'), + }, + revision: 0, +}); + +/** + * @deprecated Once we are confident all rules relying on side-car actions SO's have been migrated to SO references we should remove this function + */ +export const legacyGetSiemNotificationRuleNoActionsSOResult = ( + ruleId = '123' +): SavedObjectsFindResult => ({ + type: 'siem-detection-engine-rule-actions', + id: 'ID_OF_LEGACY_SIDECAR_NO_ACTIONS', + namespaces: ['default'], + attributes: { + actions: [], + ruleThrottle: 'no_actions', + alertThrottle: null, + }, + references: [{ id: ruleId, type: 'alert', name: 'alert_0' }], + migrationVersion: { + 'siem-detection-engine-rule-actions': '7.11.2', + }, + coreMigrationVersion: '7.15.2', + updated_at: '2022-03-31T19:06:40.473Z', + version: 'WzIzNywxXQ==', + score: 0, +}); + +/** + * @deprecated Once we are confident all rules relying on side-car actions SO's have been migrated to SO references we should remove this function + */ +export const legacyGetSiemNotificationRuleEveryRunSOResult = ( + ruleId = '123' +): SavedObjectsFindResult => ({ + type: 'siem-detection-engine-rule-actions', + id: 'ID_OF_LEGACY_SIDECAR_RULE_RUN_ACTIONS', + namespaces: ['default'], + attributes: { + actions: [ + { + group: 'default', + actionRef: 'action_0', + params: { + message: 'Rule {{context.rule.name}} generated {{state.signals_count}} alerts', + to: ['test@test.com'], + subject: 'Test Actions', + }, + action_type_id: '.email', + }, + ], + ruleThrottle: 'rule', + alertThrottle: null, + }, + references: [{ id: ruleId, type: 'alert', name: 'alert_0' }], + migrationVersion: { + 'siem-detection-engine-rule-actions': '7.11.2', + }, + coreMigrationVersion: '7.15.2', + updated_at: '2022-03-31T19:06:40.473Z', + version: 'WzIzNywxXQ==', + score: 0, +}); + +/** + * @deprecated Once we are confident all rules relying on side-car actions SO's have been migrated to SO references we should remove this function + */ +export const legacyGetSiemNotificationRuleHourlyActionsSOResult = ( + ruleId = '123', + connectorId = '456' +): SavedObjectsFindResult => ({ + type: 'siem-detection-engine-rule-actions', + id: 'ID_OF_LEGACY_SIDECAR_HOURLY_ACTIONS', + namespaces: ['default'], + attributes: { + actions: [ + { + group: 'default', + actionRef: 'action_0', + params: { + message: 'Rule {{context.rule.name}} generated {{state.signals_count}} alerts', + to: ['test@test.com'], + subject: 'Test Actions', + }, + action_type_id: '.email', + }, + ], + ruleThrottle: '1h', + alertThrottle: '1h', + }, + references: [ + { id: ruleId, type: 'alert', name: 'alert_0' }, + { id: connectorId, type: 'action', name: 'action_0' }, + ], + migrationVersion: { + 'siem-detection-engine-rule-actions': '7.11.2', + }, + coreMigrationVersion: '7.15.2', + updated_at: '2022-03-31T19:06:40.473Z', + version: 'WzIzNywxXQ==', + score: 0, +}); + +/** + * @deprecated Once we are confident all rules relying on side-car actions SO's have been migrated to SO references we should remove this function + */ +export const legacyGetSiemNotificationRuleDailyActionsSOResult = ( + ruleId = '123', + connectorId = '456' +): SavedObjectsFindResult => ({ + type: 'siem-detection-engine-rule-actions', + id: 'ID_OF_LEGACY_SIDECAR_DAILY_ACTIONS', + namespaces: ['default'], + attributes: { + actions: [ + { + group: 'default', + actionRef: 'action_0', + params: { + message: 'Rule {{context.rule.name}} generated {{state.signals_count}} alerts', + to: ['test@test.com'], + subject: 'Test Actions', + }, + action_type_id: '.email', + }, + ], + ruleThrottle: '1d', + alertThrottle: '1d', + }, + references: [ + { id: ruleId, type: 'alert', name: 'alert_0' }, + { id: connectorId, type: 'action', name: 'action_0' }, + ], + migrationVersion: { + 'siem-detection-engine-rule-actions': '7.11.2', + }, + coreMigrationVersion: '7.15.2', + updated_at: '2022-03-31T19:06:40.473Z', + version: 'WzIzNywxXQ==', + score: 0, +}); + +/** + * @deprecated Once we are confident all rules relying on side-car actions SO's have been migrated to SO references we should remove this function + */ +export const legacyGetSiemNotificationRuleWeeklyActionsSOResult = ( + ruleId = '123', + connectorId = '456' +): SavedObjectsFindResult => ({ + type: 'siem-detection-engine-rule-actions', + id: 'ID_OF_LEGACY_SIDECAR_WEEKLY_ACTIONS', + namespaces: ['default'], + attributes: { + actions: [ + { + group: 'default', + actionRef: 'action_0', + params: { + message: 'Rule {{context.rule.name}} generated {{state.signals_count}} alerts', + to: ['test@test.com'], + subject: 'Test Actions', + }, + action_type_id: '.email', + }, + ], + ruleThrottle: '7d', + alertThrottle: '7d', + }, + references: [ + { id: ruleId, type: 'alert', name: 'alert_0' }, + { id: connectorId, type: 'action', name: 'action_0' }, + ], + migrationVersion: { + 'siem-detection-engine-rule-actions': '7.11.2', + }, + coreMigrationVersion: '7.15.2', + updated_at: '2022-03-31T19:06:40.473Z', + version: 'WzIzNywxXQ==', + score: 0, +}); + +const getLegacyActionSOs = (ruleId = '123', connectorId = '456') => ({ + none: () => legacyGetSiemNotificationRuleNoActionsSOResult(ruleId), + rule: () => legacyGetSiemNotificationRuleEveryRunSOResult(ruleId), + hourly: () => legacyGetSiemNotificationRuleHourlyActionsSOResult(ruleId, connectorId), + daily: () => legacyGetSiemNotificationRuleDailyActionsSOResult(ruleId, connectorId), + weekly: () => legacyGetSiemNotificationRuleWeeklyActionsSOResult(ruleId, connectorId), +}); + +/** + * @deprecated Once we are confident all rules relying on side-car actions SO's have been migrated to SO references we should remove this function + */ +export const legacyGetSiemNotificationRuleActionsSOResultWithSingleHit = ( + actionTypes: Array<'none' | 'rule' | 'daily' | 'hourly' | 'weekly'>, + ruleId = '123', + connectorId = '456' +): SavedObjectsFindResponse => { + const actions = getLegacyActionSOs(ruleId, connectorId); + + return { + page: 1, + per_page: 1, + total: 1, + saved_objects: actionTypes.map((type) => actions[type]()), + }; +}; diff --git a/x-pack/plugins/alerting/server/rules_client/lib/siem_legacy_actions/retrieve_migrated_legacy_actions.test.ts b/x-pack/plugins/alerting/server/rules_client/lib/siem_legacy_actions/retrieve_migrated_legacy_actions.test.ts new file mode 100644 index 0000000000000..b49497d086efe --- /dev/null +++ b/x-pack/plugins/alerting/server/rules_client/lib/siem_legacy_actions/retrieve_migrated_legacy_actions.test.ts @@ -0,0 +1,267 @@ +/* + * Copyright 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 { RulesClientContext } from '../..'; +import { loggingSystemMock, savedObjectsClientMock } from '@kbn/core/server/mocks'; + +import { + legacyGetDailyNotificationResult, + legacyGetHourlyNotificationResult, + legacyGetSiemNotificationRuleActionsSOResultWithSingleHit, + legacyGetWeeklyNotificationResult, +} from './retrieve_migrated_legacy_actions.mock'; + +import { retrieveMigratedLegacyActions } from './retrieve_migrated_legacy_actions'; + +import { find } from '../../methods/find'; +import { deleteRule } from '../../methods/delete'; + +jest.mock('../../methods/find', () => { + return { + find: jest.fn(), + }; +}); + +jest.mock('../../methods/delete', () => { + return { + deleteRule: jest.fn(), + }; +}); + +const findMock = find as jest.Mock; +const deleteRuleMock = deleteRule as jest.Mock; + +const getEmptyFindResult = () => ({ + page: 0, + per_page: 0, + total: 0, + data: [], +}); + +describe('Legacy rule action migration logic', () => { + describe('legacyMigrate', () => { + let savedObjectsClient: ReturnType; + let logger: ReturnType; + + beforeEach(() => { + logger = loggingSystemMock.createLogger(); + savedObjectsClient = savedObjectsClientMock.create(); + }); + + const ruleId = '123'; + const connectorId = '456'; + + test('it does no cleanup or migration if no legacy remnants found', async () => { + findMock.mockResolvedValueOnce(getEmptyFindResult()); + savedObjectsClient.find.mockResolvedValueOnce({ + total: 0, + per_page: 0, + page: 1, + saved_objects: [], + }); + + const migratedProperties = await retrieveMigratedLegacyActions( + { + unsecuredSavedObjectsClient: savedObjectsClient, + logger, + } as unknown as RulesClientContext, + { ruleId } + ); + + expect(deleteRuleMock).not.toHaveBeenCalled(); + expect(savedObjectsClient.delete).not.toHaveBeenCalled(); + expect(migratedProperties).toEqual({ legacyActions: [], legacyActionsReferences: [] }); + }); + + // Even if a rule is created with no actions pre 7.16, a + // siem-detection-engine-rule-actions SO is still created + test('it migrates a rule with no actions', async () => { + // siem.notifications is not created for a rule with no actions + findMock.mockResolvedValueOnce(getEmptyFindResult()); + // siem-detection-engine-rule-actions SO is still created + savedObjectsClient.find.mockResolvedValueOnce( + legacyGetSiemNotificationRuleActionsSOResultWithSingleHit(['none'], ruleId, connectorId) + ); + + const migratedProperties = await retrieveMigratedLegacyActions( + { + unsecuredSavedObjectsClient: savedObjectsClient, + logger, + } as unknown as RulesClientContext, + { ruleId } + ); + + expect(deleteRuleMock).not.toHaveBeenCalled(); + expect(savedObjectsClient.delete).toHaveBeenCalledWith( + 'siem-detection-engine-rule-actions', + 'ID_OF_LEGACY_SIDECAR_NO_ACTIONS' + ); + expect(migratedProperties).toEqual({ legacyActions: [], legacyActionsReferences: [] }); + }); + + test('it migrates a rule with every rule run action', async () => { + // siem.notifications is not created for a rule with actions run every rule run + findMock.mockResolvedValueOnce(getEmptyFindResult()); + // siem-detection-engine-rule-actions SO is still created + savedObjectsClient.find.mockResolvedValueOnce( + legacyGetSiemNotificationRuleActionsSOResultWithSingleHit(['rule'], ruleId, connectorId) + ); + + const migratedProperties = await retrieveMigratedLegacyActions( + { + unsecuredSavedObjectsClient: savedObjectsClient, + logger, + } as unknown as RulesClientContext, + { ruleId } + ); + + expect(deleteRuleMock).not.toHaveBeenCalled(); + expect(savedObjectsClient.delete).toHaveBeenCalledWith( + 'siem-detection-engine-rule-actions', + 'ID_OF_LEGACY_SIDECAR_RULE_RUN_ACTIONS' + ); + + expect(migratedProperties).toEqual({ legacyActions: [], legacyActionsReferences: [] }); + }); + + test('it migrates a rule with daily legacy actions', async () => { + // siem.notifications is not created for a rule with no actions + findMock.mockResolvedValueOnce({ + page: 1, + perPage: 1, + total: 1, + data: [legacyGetDailyNotificationResult(connectorId, ruleId)], + }); + // siem-detection-engine-rule-actions SO is still created + savedObjectsClient.find.mockResolvedValueOnce( + legacyGetSiemNotificationRuleActionsSOResultWithSingleHit(['daily'], ruleId, connectorId) + ); + + const migratedProperties = await retrieveMigratedLegacyActions( + { + unsecuredSavedObjectsClient: savedObjectsClient, + logger, + } as unknown as RulesClientContext, + { ruleId } + ); + + expect(deleteRuleMock).toHaveBeenCalledWith(expect.any(Object), { id: '456' }); + expect(savedObjectsClient.delete).toHaveBeenCalledWith( + 'siem-detection-engine-rule-actions', + 'ID_OF_LEGACY_SIDECAR_DAILY_ACTIONS' + ); + + expect(migratedProperties).toEqual({ + legacyActions: [ + { + actionRef: 'action_0', + actionTypeId: '.email', + frequency: { notifyWhen: 'onThrottleInterval', summary: true, throttle: '1d' }, + group: 'default', + params: { + message: 'Rule {{context.rule.name}} generated {{state.signals_count}} alerts', + subject: 'Test Actions', + to: ['test@test.com'], + }, + uuid: expect.any(String), + }, + ], + legacyActionsReferences: [{ id: '456', name: 'action_0', type: 'action' }], + }); + }); + + test('it migrates a rule with hourly legacy actions', async () => { + // siem.notifications is not created for a rule with no actions + findMock.mockResolvedValueOnce({ + page: 1, + perPage: 1, + total: 1, + data: [legacyGetHourlyNotificationResult(connectorId, ruleId)], + }); + // siem-detection-engine-rule-actions SO is still created + savedObjectsClient.find.mockResolvedValueOnce( + legacyGetSiemNotificationRuleActionsSOResultWithSingleHit(['hourly'], ruleId, connectorId) + ); + + const migratedProperties = await retrieveMigratedLegacyActions( + { + unsecuredSavedObjectsClient: savedObjectsClient, + logger, + } as unknown as RulesClientContext, + { ruleId } + ); + + expect(deleteRuleMock).toHaveBeenCalledWith(expect.any(Object), { id: '456' }); + expect(savedObjectsClient.delete).toHaveBeenCalledWith( + 'siem-detection-engine-rule-actions', + 'ID_OF_LEGACY_SIDECAR_HOURLY_ACTIONS' + ); + expect(migratedProperties).toEqual({ + legacyActions: [ + { + actionRef: 'action_0', + actionTypeId: '.email', + frequency: { notifyWhen: 'onThrottleInterval', summary: true, throttle: '1h' }, + group: 'default', + params: { + message: 'Rule {{context.rule.name}} generated {{state.signals_count}} alerts', + subject: 'Test Actions', + to: ['test@test.com'], + }, + uuid: expect.any(String), + }, + ], + legacyActionsReferences: [{ id: '456', name: 'action_0', type: 'action' }], + }); + }); + + test('it migrates a rule with weekly legacy actions', async () => { + // siem.notifications is not created for a rule with no actions + findMock.mockResolvedValueOnce({ + page: 1, + perPage: 1, + total: 1, + data: [legacyGetWeeklyNotificationResult(connectorId, ruleId)], + }); + // siem-detection-engine-rule-actions SO is still created + savedObjectsClient.find.mockResolvedValueOnce( + legacyGetSiemNotificationRuleActionsSOResultWithSingleHit(['weekly'], ruleId, connectorId) + ); + + const migratedProperties = await retrieveMigratedLegacyActions( + { + unsecuredSavedObjectsClient: savedObjectsClient, + logger, + } as unknown as RulesClientContext, + { ruleId } + ); + + expect(deleteRuleMock).toHaveBeenCalledWith(expect.any(Object), { id: '456' }); + expect(savedObjectsClient.delete).toHaveBeenCalledWith( + 'siem-detection-engine-rule-actions', + 'ID_OF_LEGACY_SIDECAR_WEEKLY_ACTIONS' + ); + expect(migratedProperties).toEqual({ + legacyActions: [ + { + actionRef: 'action_0', + actionTypeId: '.email', + frequency: { notifyWhen: 'onThrottleInterval', summary: true, throttle: '7d' }, + group: 'default', + params: { + message: 'Rule {{context.rule.name}} generated {{state.signals_count}} alerts', + subject: 'Test Actions', + to: ['test@test.com'], + }, + uuid: expect.any(String), + }, + ], + legacyActionsReferences: [{ id: '456', name: 'action_0', type: 'action' }], + }); + }); + }); +}); diff --git a/x-pack/plugins/alerting/server/rules_client/lib/siem_legacy_actions/retrieve_migrated_legacy_actions.ts b/x-pack/plugins/alerting/server/rules_client/lib/siem_legacy_actions/retrieve_migrated_legacy_actions.ts new file mode 100644 index 0000000000000..83cd315aaa5a5 --- /dev/null +++ b/x-pack/plugins/alerting/server/rules_client/lib/siem_legacy_actions/retrieve_migrated_legacy_actions.ts @@ -0,0 +1,117 @@ +/* + * Copyright 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 { SavedObjectReference } from '@kbn/core/server'; +import type { RulesClientContext } from '../..'; +import { RawRuleAction } from '../../../types'; +import { find } from '../../methods/find'; +import { deleteRule } from '../../methods/delete'; +import { LegacyIRuleActionsAttributes, legacyRuleActionsSavedObjectType } from './types'; +import { transformFromLegacyActions } from './transform_legacy_actions'; + +type RetrieveMigratedLegacyActions = ( + context: RulesClientContext, + { ruleId }: { ruleId: string } +) => Promise<{ legacyActions: RawRuleAction[]; legacyActionsReferences: SavedObjectReference[] }>; + +/** + * @deprecated Once we are confident all rules relying on side-car actions SO's have been migrated to SO references we should remove this function + * retrieves legacy actions for SIEM rule and deletes associated sidecar SO + * @param context RulesClient context + * @param params.ruleId - id of rule to be migrated + * @returns + */ +export const retrieveMigratedLegacyActions: RetrieveMigratedLegacyActions = async ( + context, + { ruleId } +) => { + const { unsecuredSavedObjectsClient } = context; + try { + if (ruleId == null) { + return { legacyActions: [], legacyActionsReferences: [] }; + } + + /** + * On update / patch I'm going to take the actions as they are, better off taking rules client.find (siem.notification) result + * and putting that into the actions array of the rule, then set the rules onThrottle property, notifyWhen and throttle from null -> actual value (1hr etc..) + * Then use the rules client to delete the siem.notification + * Then with the legacy Rule Actions saved object type, just delete it. + */ + // find it using the references array, not params.ruleAlertId + const [siemNotification, legacyRuleActionsSO] = await Promise.all([ + find(context, { + options: { + filter: 'alert.attributes.alertTypeId:(siem.notifications)', + hasReference: { + type: 'alert', + id: ruleId, + }, + }, + }), + unsecuredSavedObjectsClient.find({ + type: legacyRuleActionsSavedObjectType, + hasReference: { + type: 'alert', + id: ruleId, + }, + }), + ]); + + const siemNotificationsExist = siemNotification != null && siemNotification.data.length > 0; + const legacyRuleNotificationSOsExist = + legacyRuleActionsSO != null && legacyRuleActionsSO.saved_objects.length > 0; + + // Assumption: if no legacy sidecar SO or notification rule types exist + // that reference the rule in question, assume rule actions are not legacy + if (!siemNotificationsExist && !legacyRuleNotificationSOsExist) { + return { legacyActions: [], legacyActionsReferences: [] }; + } + + await Promise.all([ + // If the legacy notification rule type ("siem.notification") exist, + // migration and cleanup are needed + siemNotificationsExist && deleteRule(context, { id: siemNotification.data[0].id }), + // Delete the legacy sidecar SO if it exists + legacyRuleNotificationSOsExist && + unsecuredSavedObjectsClient.delete( + legacyRuleActionsSavedObjectType, + legacyRuleActionsSO.saved_objects[0].id + ), + ]); + + // If legacy notification sidecar ("siem-detection-engine-rule-actions") + // exist, migration is needed + if (legacyRuleNotificationSOsExist) { + // If "siem-detection-engine-rule-actions" notes that `ruleThrottle` is + // "no_actions" or "rule", rule has no actions or rule is set to run + // action on every rule run. In these cases, sidecar deletion is the only + // cleanup needed and updates to the "throttle" and "notifyWhen". "siem.notification" are + // not created for these action types + if ( + legacyRuleActionsSO.saved_objects[0].attributes.ruleThrottle === 'no_actions' || + legacyRuleActionsSO.saved_objects[0].attributes.ruleThrottle === 'rule' + ) { + return { legacyActions: [], legacyActionsReferences: [] }; + } + + return { + legacyActions: transformFromLegacyActions( + legacyRuleActionsSO.saved_objects[0].attributes, + legacyRuleActionsSO.saved_objects[0].references + ), + legacyActionsReferences: + // only action references need to be saved + legacyRuleActionsSO.saved_objects[0].references.filter(({ type }) => type === 'action') ?? + [], + }; + } + } catch (e) { + context.logger.debug(`Migration has failed for rule ${ruleId}: ${e.message}`); + } + + return { legacyActions: [], legacyActionsReferences: [] }; +}; diff --git a/x-pack/plugins/alerting/server/rules_client/lib/siem_legacy_actions/transform_legacy_actions.test.ts b/x-pack/plugins/alerting/server/rules_client/lib/siem_legacy_actions/transform_legacy_actions.test.ts new file mode 100644 index 0000000000000..b23798294b300 --- /dev/null +++ b/x-pack/plugins/alerting/server/rules_client/lib/siem_legacy_actions/transform_legacy_actions.test.ts @@ -0,0 +1,89 @@ +/* + * Copyright 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 { SavedObjectReference } from '@kbn/core/server'; + +import { transformFromLegacyActions } from './transform_legacy_actions'; +import { transformToNotifyWhen } from './transform_to_notify_when'; +import { LegacyIRuleActionsAttributes } from './types'; + +jest.mock('./transform_to_notify_when', () => ({ + transformToNotifyWhen: jest.fn(), +})); + +const legacyActionsAttr: LegacyIRuleActionsAttributes = { + actions: [ + { + group: 'group_1', + params: {}, + action_type_id: 'action_type_1', + actionRef: 'action_0', + }, + ], + ruleThrottle: '1d', + alertThrottle: '1d', +}; + +const references: SavedObjectReference[] = [ + { + name: 'action_0', + id: 'action-123', + type: 'action', + }, +]; + +describe('transformFromLegacyActions', () => { + it('should throw error if if references are empty', () => { + const executor = () => { + return transformFromLegacyActions(legacyActionsAttr, []); + }; + expect(executor).toThrow('Connector reference id not found.'); + }); + it('should return empty list of actions if legacy actions do not have correct references', () => { + const actions = transformFromLegacyActions(legacyActionsAttr, [ + { + name: 'alert_0', + id: 'alert-1', + type: 'alert', + }, + ]); + + expect(actions).toHaveLength(0); + }); + + it('should return notifyWhen as result of transformToNotifyWhen if it is not null', () => { + (transformToNotifyWhen as jest.Mock).mockReturnValueOnce('onActiveAlert'); + const actions = transformFromLegacyActions(legacyActionsAttr, references); + + expect(transformToNotifyWhen).toHaveBeenCalledWith('1d'); + expect(actions[0].frequency?.notifyWhen).toBe('onActiveAlert'); + }); + + it('should return notifyWhen as onThrottleInterval when transformToNotifyWhen returns null', () => { + (transformToNotifyWhen as jest.Mock).mockReturnValueOnce(null); + const actions = transformFromLegacyActions(legacyActionsAttr, references); + + expect(actions[0].frequency?.notifyWhen).toBe('onThrottleInterval'); + }); + + it('should return transformed legacy actions', () => { + (transformToNotifyWhen as jest.Mock).mockReturnValue('onThrottleInterval'); + + const actions = transformFromLegacyActions(legacyActionsAttr, references); + + expect(actions).toEqual([ + { + actionRef: 'action_0', + actionTypeId: 'action_type_1', + frequency: { notifyWhen: 'onThrottleInterval', summary: true, throttle: '1d' }, + group: 'group_1', + params: {}, + uuid: expect.any(String), + }, + ]); + }); +}); diff --git a/x-pack/plugins/alerting/server/rules_client/lib/siem_legacy_actions/transform_legacy_actions.ts b/x-pack/plugins/alerting/server/rules_client/lib/siem_legacy_actions/transform_legacy_actions.ts new file mode 100644 index 0000000000000..485a32f781695 --- /dev/null +++ b/x-pack/plugins/alerting/server/rules_client/lib/siem_legacy_actions/transform_legacy_actions.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 { v4 } from 'uuid'; +import { isEmpty } from 'lodash/fp'; +import type { SavedObjectReference } from '@kbn/core/server'; +import { RawRuleAction } from '../../../types'; +import { transformToNotifyWhen } from './transform_to_notify_when'; +import { LegacyIRuleActionsAttributes } from './types'; + +/** + * @deprecated Once we are confident all rules relying on side-car actions SO's have been migrated to SO references we should remove this function + * transforms siem legacy actions {@link LegacyIRuleActionsAttributes} objects into {@link RawRuleAction} + * @param legacyActionsAttr + * @param references + * @returns array of RawRuleAction + */ +export const transformFromLegacyActions = ( + legacyActionsAttr: LegacyIRuleActionsAttributes, + references: SavedObjectReference[] +): RawRuleAction[] => { + const actionReference = references.reduce>( + (acc, reference) => { + acc[reference.name] = reference; + return acc; + }, + {} + ); + + if (isEmpty(actionReference)) { + throw new Error(`Connector reference id not found.`); + } + + return legacyActionsAttr.actions.reduce((acc, action) => { + const { actionRef, action_type_id: actionTypeId, group, params } = action; + if (!actionReference[actionRef]) { + return acc; + } + return [ + ...acc, + { + group, + params, + uuid: v4(), + actionRef, + actionTypeId, + frequency: { + summary: true, + notifyWhen: transformToNotifyWhen(legacyActionsAttr.ruleThrottle) ?? 'onThrottleInterval', + throttle: legacyActionsAttr.ruleThrottle, + }, + }, + ]; + }, []); +}; diff --git a/x-pack/plugins/alerting/server/rules_client/lib/siem_legacy_actions/transform_to_notify_when.test.ts b/x-pack/plugins/alerting/server/rules_client/lib/siem_legacy_actions/transform_to_notify_when.test.ts new file mode 100644 index 0000000000000..9d3fae5d3cb2c --- /dev/null +++ b/x-pack/plugins/alerting/server/rules_client/lib/siem_legacy_actions/transform_to_notify_when.test.ts @@ -0,0 +1,23 @@ +/* + * Copyright 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 { transformToNotifyWhen } from './transform_to_notify_when'; + +describe('transformToNotifyWhen', () => { + it('should return null when throttle is null OR no_actions', () => { + expect(transformToNotifyWhen(null)).toBeNull(); + expect(transformToNotifyWhen('no_actions')).toBeNull(); + }); + it('should return onActiveAlert when throttle is rule', () => { + expect(transformToNotifyWhen('rule')).toBe('onActiveAlert'); + }); + it('should return onThrottleInterval for other throttle values', () => { + expect(transformToNotifyWhen('1h')).toBe('onThrottleInterval'); + expect(transformToNotifyWhen('1m')).toBe('onThrottleInterval'); + expect(transformToNotifyWhen('1d')).toBe('onThrottleInterval'); + }); +}); diff --git a/x-pack/plugins/alerting/server/rules_client/lib/siem_legacy_actions/transform_to_notify_when.ts b/x-pack/plugins/alerting/server/rules_client/lib/siem_legacy_actions/transform_to_notify_when.ts new file mode 100644 index 0000000000000..76c43617adc22 --- /dev/null +++ b/x-pack/plugins/alerting/server/rules_client/lib/siem_legacy_actions/transform_to_notify_when.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. + */ +import { RuleNotifyWhenType } from '../../../../common'; + +/** + * Given a throttle from a "security_solution" rule this will transform it into an "alerting" notifyWhen + * on their saved object. + * @params throttle The throttle from a "security_solution" rule + * @returns The correct "NotifyWhen" for a Kibana alerting. + */ +export const transformToNotifyWhen = ( + throttle: string | null | undefined +): RuleNotifyWhenType | null => { + if (throttle == null || throttle === 'no_actions') { + return null; // Although I return null, this does not change the value of the "notifyWhen" and it keeps the current value of "notifyWhen" + } else if (throttle === 'rule') { + return 'onActiveAlert'; + } else { + return 'onThrottleInterval'; + } +}; diff --git a/x-pack/plugins/alerting/server/rules_client/lib/siem_legacy_actions/types.ts b/x-pack/plugins/alerting/server/rules_client/lib/siem_legacy_actions/types.ts new file mode 100644 index 0000000000000..487f18807d35b --- /dev/null +++ b/x-pack/plugins/alerting/server/rules_client/lib/siem_legacy_actions/types.ts @@ -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 { RuleActionParams } from '../../../types'; +import type { RuleTypeParams } from '../../..'; +import type { Rule } from '../../../../common'; + +/** + * @deprecated Once we are confident all rules relying on side-car actions SO's have been migrated to SO references we should remove this function + */ +export const legacyRuleActionsSavedObjectType = 'siem-detection-engine-rule-actions'; + +/** + * This is how it is stored on disk in its "raw format" for 7.16+ + * @deprecated Remove this once the legacy notification/side car is gone + */ +export interface LegacyRuleAlertSavedObjectAction { + group: string; + params: RuleActionParams; + action_type_id: string; + actionRef: string; +} + +/** + * We keep this around to migrate and update data for the old deprecated rule actions saved object mapping but we + * do not use it anymore within the code base. Once we feel comfortable that users are upgrade far enough and this is no longer + * needed then it will be safe to remove this saved object and all its migrations. + * @deprecated Remove this once the legacy notification/side car is gone + */ +export interface LegacyIRuleActionsAttributes extends Record { + actions: LegacyRuleAlertSavedObjectAction[]; + ruleThrottle: string; + alertThrottle: string | null; +} + +/** + * @deprecated Once we are confident all rules relying on side-car actions SO's have been migrated to SO references we should remove this function + */ +interface LegacyRuleNotificationAlertTypeParams extends RuleTypeParams { + ruleAlertId: string; +} + +/** + * @deprecated Once we are confident all rules relying on side-car actions SO's have been migrated to SO references we should remove this function + */ +export type LegacyRuleNotificationAlertType = Rule; diff --git a/x-pack/plugins/alerting/server/rules_client/methods/bulk_delete.ts b/x-pack/plugins/alerting/server/rules_client/methods/bulk_delete.ts index 0142fbe9e4ebb..03ce78952e9db 100644 --- a/x-pack/plugins/alerting/server/rules_client/methods/bulk_delete.ts +++ b/x-pack/plugins/alerting/server/rules_client/methods/bulk_delete.ts @@ -4,7 +4,7 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ - +import pMap from 'p-map'; import { KueryNode, nodeBuilder } from '@kbn/es-query'; import { SavedObjectsBulkUpdateObject } from '@kbn/core/server'; import { withSpan } from '@kbn/apm-utils'; @@ -13,7 +13,13 @@ import { convertRuleIdsToKueryNode } from '../../lib'; import { bulkMarkApiKeysForInvalidation } from '../../invalidate_pending_api_keys/bulk_mark_api_keys_for_invalidation'; import { ruleAuditEvent, RuleAuditAction } from '../common/audit_events'; import { tryToRemoveTasks } from '../common'; -import { getAuthorizationFilter, checkAuthorizationAndGetTotal, getAlertFromRaw } from '../lib'; +import { API_KEY_GENERATE_CONCURRENCY } from '../common/constants'; +import { + getAuthorizationFilter, + checkAuthorizationAndGetTotal, + getAlertFromRaw, + migrateLegacyActions, +} from '../lib'; import { retryIfBulkOperationConflicts, buildKueryNodeFilter, @@ -166,6 +172,20 @@ const bulkDeleteWithOCC = async ( }); const rules = rulesToDelete.filter((rule) => deletedRuleIds.includes(rule.id)); + // migrate legacy actions only for SIEM rules + await pMap( + rules, + async (rule) => { + await migrateLegacyActions(context, { + ruleId: rule.id, + attributes: rule.attributes as RawRule, + skipActionsValidation: true, + }); + }, + // max concurrency for bulk edit operations, that is limited by api key generations, should be sufficient for bulk migrations + { concurrency: API_KEY_GENERATE_CONCURRENCY } + ); + return { errors, rules, diff --git a/x-pack/plugins/alerting/server/rules_client/methods/bulk_disable.ts b/x-pack/plugins/alerting/server/rules_client/methods/bulk_disable.ts index 06efa425eccef..5ec0ae99ca99c 100644 --- a/x-pack/plugins/alerting/server/rules_client/methods/bulk_disable.ts +++ b/x-pack/plugins/alerting/server/rules_client/methods/bulk_disable.ts @@ -4,7 +4,6 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ - import { KueryNode, nodeBuilder } from '@kbn/es-query'; import { SavedObjectsBulkUpdateObject } from '@kbn/core/server'; import { withSpan } from '@kbn/apm-utils'; @@ -25,6 +24,7 @@ import { getAlertFromRaw, recoverRuleAlerts, updateMeta, + migrateLegacyActions, } from '../lib'; import { BulkOptions, BulkOperationError, RulesClientContext } from '../types'; import { tryToRemoveTasks } from '../common'; @@ -119,8 +119,22 @@ const bulkDisableRulesWithOCC = async ( ruleNameToRuleIdMapping[rule.id] = rule.attributes.name; } + const migratedActions = await migrateLegacyActions(context, { + ruleId: rule.id, + actions: rule.attributes.actions, + references: rule.references, + attributes: rule.attributes, + }); + const updatedAttributes = updateMeta(context, { ...rule.attributes, + ...(migratedActions.hasLegacyActions + ? { + actions: migratedActions.resultedActions, + throttle: undefined, + notifyWhen: undefined, + } + : {}), enabled: false, scheduledTaskId: rule.attributes.scheduledTaskId === rule.id @@ -135,6 +149,9 @@ const bulkDisableRulesWithOCC = async ( attributes: { ...updatedAttributes, }, + ...(migratedActions.hasLegacyActions + ? { references: migratedActions.resultedReferences } + : {}), }); context.auditLogger?.log( diff --git a/x-pack/plugins/alerting/server/rules_client/methods/bulk_edit.ts b/x-pack/plugins/alerting/server/rules_client/methods/bulk_edit.ts index a3d845af2c0af..dfe4ecc952bf6 100644 --- a/x-pack/plugins/alerting/server/rules_client/methods/bulk_edit.ts +++ b/x-pack/plugins/alerting/server/rules_client/methods/bulk_edit.ts @@ -69,6 +69,8 @@ import { NormalizedAlertActionWithGeneratedValues, } from '../types'; +import { migrateLegacyActions } from '../lib'; + export type BulkEditFields = keyof Pick< Rule, 'actions' | 'tags' | 'schedule' | 'throttle' | 'notifyWhen' | 'snoozeSchedule' | 'apiKey' @@ -449,6 +451,19 @@ async function updateRuleAttributesAndParamsInMemory action?.frequency + )?.frequency; if (rule.attributes.consumer === AlertConsumers.SIEM && firstFrequency) { ruleActions.actions = ruleActions.actions.map((action) => omit(action, 'frequency')); if (!attributes.notifyWhen) { diff --git a/x-pack/plugins/alerting/server/rules_client/methods/bulk_enable.ts b/x-pack/plugins/alerting/server/rules_client/methods/bulk_enable.ts index 7273855379a1c..570ad3d5abc56 100644 --- a/x-pack/plugins/alerting/server/rules_client/methods/bulk_enable.ts +++ b/x-pack/plugins/alerting/server/rules_client/methods/bulk_enable.ts @@ -4,7 +4,6 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ - import pMap from 'p-map'; import { KueryNode, nodeBuilder } from '@kbn/es-query'; import { SavedObjectsBulkUpdateObject } from '@kbn/core/server'; @@ -26,6 +25,7 @@ import { scheduleTask, updateMeta, createNewAPIKeySet, + migrateLegacyActions, } from '../lib'; import { RulesClientContext, BulkOperationError, BulkOptions } from '../types'; @@ -143,6 +143,13 @@ const bulkEnableRulesWithOCC = async ( ruleNameToRuleIdMapping[rule.id] = rule.attributes.name; } + const migratedActions = await migrateLegacyActions(context, { + ruleId: rule.id, + actions: rule.attributes.actions, + references: rule.references, + attributes: rule.attributes, + }); + const updatedAttributes = updateMeta(context, { ...rule.attributes, ...(!rule.attributes.apiKey && @@ -152,6 +159,13 @@ const bulkEnableRulesWithOCC = async ( username, shouldUpdateApiKey: true, }))), + ...(migratedActions.hasLegacyActions + ? { + actions: migratedActions.resultedActions, + throttle: undefined, + notifyWhen: undefined, + } + : {}), enabled: true, updatedBy: username, updatedAt: new Date().toISOString(), @@ -187,6 +201,9 @@ const bulkEnableRulesWithOCC = async ( ...updatedAttributes, ...(scheduledTaskId ? { scheduledTaskId } : undefined), }, + ...(migratedActions.hasLegacyActions + ? { references: migratedActions.resultedReferences } + : {}), }); context.auditLogger?.log( diff --git a/x-pack/plugins/alerting/server/rules_client/methods/create.ts b/x-pack/plugins/alerting/server/rules_client/methods/create.ts index 4d2e586dcaade..4bf83043f99af 100644 --- a/x-pack/plugins/alerting/server/rules_client/methods/create.ts +++ b/x-pack/plugins/alerting/server/rules_client/methods/create.ts @@ -113,7 +113,7 @@ export async function create( // TODO https://github.com/elastic/kibana/issues/148414 // If any action-level frequencies get pushed into a SIEM rule, strip their frequencies - const firstFrequency = data.actions[0]?.frequency; + const firstFrequency = data.actions.find((action) => action?.frequency)?.frequency; if (data.consumer === AlertConsumers.SIEM && firstFrequency) { data.actions = data.actions.map((action) => omit(action, 'frequency')); if (!data.notifyWhen) { diff --git a/x-pack/plugins/alerting/server/rules_client/methods/delete.ts b/x-pack/plugins/alerting/server/rules_client/methods/delete.ts index ca6feefe3ff9b..605753cfcdfc8 100644 --- a/x-pack/plugins/alerting/server/rules_client/methods/delete.ts +++ b/x-pack/plugins/alerting/server/rules_client/methods/delete.ts @@ -5,12 +5,14 @@ * 2.0. */ +import { AlertConsumers } from '@kbn/rule-data-utils'; import { RawRule } from '../../types'; import { WriteOperations, AlertingAuthorizationEntity } from '../../authorization'; import { retryIfConflicts } from '../../lib/retry_if_conflicts'; import { bulkMarkApiKeysForInvalidation } from '../../invalidate_pending_api_keys/bulk_mark_api_keys_for_invalidation'; import { ruleAuditEvent, RuleAuditAction } from '../common/audit_events'; import { RulesClientContext } from '../types'; +import { migrateLegacyActions } from '../lib'; export async function deleteRule(context: RulesClientContext, { id }: { id: string }) { return await retryIfConflicts( @@ -64,6 +66,11 @@ async function deleteWithOCC(context: RulesClientContext, { id }: { id: string } throw error; } + // migrate legacy actions only for SIEM rules + if (attributes.consumer === AlertConsumers.SIEM) { + await migrateLegacyActions(context, { ruleId: id, attributes, skipActionsValidation: true }); + } + context.auditLogger?.log( ruleAuditEvent({ action: RuleAuditAction.DELETE, diff --git a/x-pack/plugins/alerting/server/rules_client/methods/disable.ts b/x-pack/plugins/alerting/server/rules_client/methods/disable.ts index bbdb1ade167eb..1382b7d14b111 100644 --- a/x-pack/plugins/alerting/server/rules_client/methods/disable.ts +++ b/x-pack/plugins/alerting/server/rules_client/methods/disable.ts @@ -4,13 +4,14 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ +import type { SavedObjectReference } from '@kbn/core/server'; import { RawRule } from '../../types'; import { WriteOperations, AlertingAuthorizationEntity } from '../../authorization'; import { retryIfConflicts } from '../../lib/retry_if_conflicts'; import { ruleAuditEvent, RuleAuditAction } from '../common/audit_events'; import { RulesClientContext } from '../types'; -import { recoverRuleAlerts, updateMeta } from '../lib'; +import { recoverRuleAlerts, updateMeta, migrateLegacyActions } from '../lib'; export async function disable(context: RulesClientContext, { id }: { id: string }): Promise { return await retryIfConflicts( @@ -23,6 +24,7 @@ export async function disable(context: RulesClientContext, { id }: { id: string async function disableWithOCC(context: RulesClientContext, { id }: { id: string }) { let attributes: RawRule; let version: string | undefined; + let references: SavedObjectReference[]; try { const decryptedAlert = @@ -31,12 +33,14 @@ async function disableWithOCC(context: RulesClientContext, { id }: { id: string }); attributes = decryptedAlert.attributes; version = decryptedAlert.version; + references = decryptedAlert.references; } catch (e) { context.logger.error(`disable(): Failed to load API key of alert ${id}: ${e.message}`); // Still attempt to load the attributes and version using SOC const alert = await context.unsecuredSavedObjectsClient.get('alert', id); attributes = alert.attributes; version = alert.version; + references = alert.references; } await recoverRuleAlerts(context, id, attributes); @@ -70,6 +74,13 @@ async function disableWithOCC(context: RulesClientContext, { id }: { id: string context.ruleTypeRegistry.ensureRuleTypeEnabled(attributes.alertTypeId); if (attributes.enabled === true) { + const migratedActions = await migrateLegacyActions(context, { + ruleId: id, + actions: attributes.actions, + references, + attributes, + }); + await context.unsecuredSavedObjectsClient.update( 'alert', id, @@ -80,8 +91,16 @@ async function disableWithOCC(context: RulesClientContext, { id }: { id: string updatedBy: await context.getUserName(), updatedAt: new Date().toISOString(), nextRun: null, + ...(migratedActions.hasLegacyActions + ? { actions: migratedActions.resultedActions, throttle: undefined, notifyWhen: undefined } + : {}), }), - { version } + { + version, + ...(migratedActions.hasLegacyActions + ? { references: migratedActions.resultedReferences } + : {}), + } ); // If the scheduledTaskId does not match the rule id, we should diff --git a/x-pack/plugins/alerting/server/rules_client/methods/enable.ts b/x-pack/plugins/alerting/server/rules_client/methods/enable.ts index a70b71b7b5116..0cd42f282bcbc 100644 --- a/x-pack/plugins/alerting/server/rules_client/methods/enable.ts +++ b/x-pack/plugins/alerting/server/rules_client/methods/enable.ts @@ -4,7 +4,7 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ - +import type { SavedObjectReference } from '@kbn/core/server'; import { TaskStatus } from '@kbn/task-manager-plugin/server'; import { RawRule, IntervalSchedule } from '../../types'; import { resetMonitoringLastRun, getNextRun } from '../../lib'; @@ -12,7 +12,7 @@ import { WriteOperations, AlertingAuthorizationEntity } from '../../authorizatio import { retryIfConflicts } from '../../lib/retry_if_conflicts'; import { ruleAuditEvent, RuleAuditAction } from '../common/audit_events'; import { RulesClientContext } from '../types'; -import { updateMeta, createNewAPIKeySet, scheduleTask } from '../lib'; +import { updateMeta, createNewAPIKeySet, scheduleTask, migrateLegacyActions } from '../lib'; export async function enable(context: RulesClientContext, { id }: { id: string }): Promise { return await retryIfConflicts( @@ -26,6 +26,7 @@ async function enableWithOCC(context: RulesClientContext, { id }: { id: string } let existingApiKey: string | null = null; let attributes: RawRule; let version: string | undefined; + let references: SavedObjectReference[]; try { const decryptedAlert = @@ -35,12 +36,14 @@ async function enableWithOCC(context: RulesClientContext, { id }: { id: string } existingApiKey = decryptedAlert.attributes.apiKey; attributes = decryptedAlert.attributes; version = decryptedAlert.version; + references = decryptedAlert.references; } catch (e) { context.logger.error(`enable(): Failed to load API key of alert ${id}: ${e.message}`); // Still attempt to load the attributes and version using SOC const alert = await context.unsecuredSavedObjectsClient.get('alert', id); attributes = alert.attributes; version = alert.version; + references = alert.references; } try { @@ -76,6 +79,13 @@ async function enableWithOCC(context: RulesClientContext, { id }: { id: string } context.ruleTypeRegistry.ensureRuleTypeEnabled(attributes.alertTypeId); if (attributes.enabled === false) { + const migratedActions = await migrateLegacyActions(context, { + ruleId: id, + actions: attributes.actions, + references, + attributes, + }); + const username = await context.getUserName(); const now = new Date(); @@ -107,7 +117,29 @@ async function enableWithOCC(context: RulesClientContext, { id }: { id: string } }); try { - await context.unsecuredSavedObjectsClient.update('alert', id, updateAttributes, { version }); + // to mitigate AAD issues(actions property is not used for encrypting API key in partial SO update) + // we call create with overwrite=true + if (migratedActions.hasLegacyActions) { + await context.unsecuredSavedObjectsClient.create( + 'alert', + { + ...updateAttributes, + actions: migratedActions.resultedActions, + throttle: undefined, + notifyWhen: undefined, + }, + { + id, + overwrite: true, + version, + references: migratedActions.resultedReferences, + } + ); + } else { + await context.unsecuredSavedObjectsClient.update('alert', id, updateAttributes, { + version, + }); + } } catch (e) { throw e; } diff --git a/x-pack/plugins/alerting/server/rules_client/methods/find.ts b/x-pack/plugins/alerting/server/rules_client/methods/find.ts index 080c72c624cb3..6493174a61b6a 100644 --- a/x-pack/plugins/alerting/server/rules_client/methods/find.ts +++ b/x-pack/plugins/alerting/server/rules_client/methods/find.ts @@ -9,7 +9,8 @@ import Boom from '@hapi/boom'; import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import { pick } from 'lodash'; import { KueryNode, nodeBuilder } from '@kbn/es-query'; -import { RawRule, RuleTypeParams, SanitizedRule } from '../../types'; +import { AlertConsumers } from '@kbn/rule-data-utils'; +import { RawRule, RuleTypeParams, SanitizedRule, Rule } from '../../types'; import { AlertingAuthorizationEntity } from '../../authorization'; import { ruleAuditEvent, RuleAuditAction } from '../common/audit_events'; import { @@ -27,6 +28,7 @@ import { import { alertingAuthorizationFilterOpts } from '../common/constants'; import { getAlertFromRaw } from '../lib/get_alert_from_raw'; import type { IndexType, RulesClientContext } from '../types'; +import { formatLegacyActions } from '../lib'; export interface FindParams { options?: FindOptions; @@ -132,6 +134,8 @@ export async function find( type: 'alert', }); + const siemRules: Rule[] = []; + const authorizedData = data.map(({ id, attributes, references }) => { try { ensureRuleTypeIsAuthorized( @@ -149,7 +153,8 @@ export async function find( ); throw error; } - return getAlertFromRaw( + + const rule = getAlertFromRaw( context, id, attributes.alertTypeId, @@ -159,6 +164,13 @@ export async function find( excludeFromPublicApi, includeSnoozeData ); + + // collect SIEM rule for further formatting legacy actions + if (attributes.consumer === AlertConsumers.SIEM) { + siemRules.push(rule); + } + + return rule; }); authorizedData.forEach(({ id }) => @@ -170,6 +182,27 @@ export async function find( ) ); + // format legacy actions for SIEM rules, if there any + if (siemRules.length) { + const formattedRules = await formatLegacyActions(siemRules, { + savedObjectsClient: context.unsecuredSavedObjectsClient, + logger: context.logger, + }); + + const formattedRulesMap = formattedRules.reduce>((acc, rule) => { + acc[rule.id] = rule; + return acc; + }, {}); + + return { + page, + perPage, + total, + // replace siem formatted rules + data: authorizedData.map((rule) => formattedRulesMap[rule.id] ?? rule), + }; + } + return { page, perPage, diff --git a/x-pack/plugins/alerting/server/rules_client/methods/get.ts b/x-pack/plugins/alerting/server/rules_client/methods/get.ts index 932772f06d209..8b25f990d3cf1 100644 --- a/x-pack/plugins/alerting/server/rules_client/methods/get.ts +++ b/x-pack/plugins/alerting/server/rules_client/methods/get.ts @@ -4,12 +4,14 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ +import { AlertConsumers } from '@kbn/rule-data-utils'; import { RawRule, SanitizedRule, RuleTypeParams, SanitizedRuleWithLegacyId } from '../../types'; import { ReadOperations, AlertingAuthorizationEntity } from '../../authorization'; import { ruleAuditEvent, RuleAuditAction } from '../common/audit_events'; import { getAlertFromRaw } from '../lib/get_alert_from_raw'; import { RulesClientContext } from '../types'; +import { formatLegacyActions } from '../lib'; export interface GetParams { id: string; @@ -51,7 +53,7 @@ export async function get( savedObject: { type: 'alert', id }, }) ); - return getAlertFromRaw( + const rule = getAlertFromRaw( context, result.id, result.attributes.alertTypeId, @@ -61,4 +63,16 @@ export async function get( excludeFromPublicApi, includeSnoozeData ); + + // format legacy actions for SIEM rules + if (result.attributes.consumer === AlertConsumers.SIEM) { + const [migratedRule] = await formatLegacyActions([rule], { + savedObjectsClient: context.unsecuredSavedObjectsClient, + logger: context.logger, + }); + + return migratedRule; + } + + return rule; } diff --git a/x-pack/plugins/alerting/server/rules_client/methods/resolve.ts b/x-pack/plugins/alerting/server/rules_client/methods/resolve.ts index 539e4c089d36d..a663b9aac56d1 100644 --- a/x-pack/plugins/alerting/server/rules_client/methods/resolve.ts +++ b/x-pack/plugins/alerting/server/rules_client/methods/resolve.ts @@ -5,11 +5,14 @@ * 2.0. */ +import { AlertConsumers } from '@kbn/rule-data-utils'; + import { RawRule, RuleTypeParams, ResolvedSanitizedRule } from '../../types'; import { ReadOperations, AlertingAuthorizationEntity } from '../../authorization'; import { ruleAuditEvent, RuleAuditAction } from '../common/audit_events'; import { getAlertFromRaw } from '../lib/get_alert_from_raw'; import { RulesClientContext } from '../types'; +import { formatLegacyActions } from '../lib'; export interface ResolveParams { id: string; @@ -58,6 +61,19 @@ export async function resolve( includeSnoozeData ); + // format legacy actions for SIEM rules + if (result.attributes.consumer === AlertConsumers.SIEM) { + const [migratedRule] = await formatLegacyActions([rule], { + savedObjectsClient: context.unsecuredSavedObjectsClient, + logger: context.logger, + }); + + return { + ...migratedRule, + ...resolveResponse, + }; + } + return { ...rule, ...resolveResponse, diff --git a/x-pack/plugins/alerting/server/rules_client/methods/update.ts b/x-pack/plugins/alerting/server/rules_client/methods/update.ts index d2b57d44fc528..4703cd5b92b28 100644 --- a/x-pack/plugins/alerting/server/rules_client/methods/update.ts +++ b/x-pack/plugins/alerting/server/rules_client/methods/update.ts @@ -33,6 +33,7 @@ import { addGeneratedActionValues, incrementRevision, createNewAPIKeySet, + migrateLegacyActions, } from '../lib'; export interface UpdateOptions { @@ -115,10 +116,24 @@ async function updateWithOCC( context.ruleTypeRegistry.ensureRuleTypeEnabled(alertSavedObject.attributes.alertTypeId); + const migratedActions = await migrateLegacyActions(context, { + ruleId: id, + attributes: alertSavedObject.attributes, + }); + const updateResult = await updateAlert( context, { id, data, allowMissingConnectorSecrets, shouldIncrementRevision }, - alertSavedObject + migratedActions.hasLegacyActions + ? { + ...alertSavedObject, + attributes: { + ...alertSavedObject.attributes, + notifyWhen: undefined, + throttle: undefined, + }, + } + : alertSavedObject ); await Promise.all([ @@ -173,7 +188,7 @@ async function updateAlert( // TODO https://github.com/elastic/kibana/issues/148414 // If any action-level frequencies get pushed into a SIEM rule, strip their frequencies - const firstFrequency = data.actions[0]?.frequency; + const firstFrequency = data.actions.find((action) => action?.frequency)?.frequency; if (attributes.consumer === AlertConsumers.SIEM && firstFrequency) { data.actions = data.actions.map((action) => omit(action, 'frequency')); if (!attributes.notifyWhen) { diff --git a/x-pack/plugins/alerting/server/rules_client/tests/bulk_delete.test.ts b/x-pack/plugins/alerting/server/rules_client/tests/bulk_delete.test.ts index 8e9f1a79b8de0..74808613f869d 100644 --- a/x-pack/plugins/alerting/server/rules_client/tests/bulk_delete.test.ts +++ b/x-pack/plugins/alerting/server/rules_client/tests/bulk_delete.test.ts @@ -25,7 +25,20 @@ import { enabledRule2, returnedRule1, returnedRule2, + siemRule1, } from './test_helpers'; +import { migrateLegacyActions } from '../lib'; + +jest.mock('../lib/siem_legacy_actions/migrate_legacy_actions', () => { + return { + migrateLegacyActions: jest.fn(), + }; +}); +(migrateLegacyActions as jest.Mock).mockResolvedValue({ + hasLegacyActions: false, + resultedActions: [], + resultedReferences: [], +}); jest.mock('../../invalidate_pending_api_keys/bulk_mark_api_keys_for_invalidation', () => ({ bulkMarkApiKeysForInvalidation: jest.fn(), @@ -453,6 +466,46 @@ describe('bulkDelete', () => { }); }); + describe('legacy actions migration for SIEM', () => { + test('should call migrateLegacyActions', async () => { + encryptedSavedObjects.createPointInTimeFinderDecryptedAsInternalUser = jest + .fn() + .mockResolvedValueOnce({ + close: jest.fn(), + find: function* asyncGenerator() { + yield { saved_objects: [enabledRule1, enabledRule2, siemRule1] }; + }, + }); + + unsecuredSavedObjectsClient.bulkDelete.mockResolvedValue({ + statuses: [ + { id: enabledRule1.id, type: 'alert', success: true }, + { id: enabledRule2.id, type: 'alert', success: true }, + { id: siemRule1.id, type: 'alert', success: true }, + ], + }); + + await rulesClient.bulkDeleteRules({ filter: 'fake_filter' }); + + expect(migrateLegacyActions).toHaveBeenCalledTimes(3); + expect(migrateLegacyActions).toHaveBeenCalledWith(expect.any(Object), { + ruleId: enabledRule1.id, + skipActionsValidation: true, + attributes: enabledRule1.attributes, + }); + expect(migrateLegacyActions).toHaveBeenCalledWith(expect.any(Object), { + ruleId: enabledRule2.id, + skipActionsValidation: true, + attributes: enabledRule2.attributes, + }); + expect(migrateLegacyActions).toHaveBeenCalledWith(expect.any(Object), { + ruleId: siemRule1.id, + skipActionsValidation: true, + attributes: siemRule1.attributes, + }); + }); + }); + describe('auditLogger', () => { jest.spyOn(auditLogger, 'log').mockImplementation(); diff --git a/x-pack/plugins/alerting/server/rules_client/tests/bulk_disable.test.ts b/x-pack/plugins/alerting/server/rules_client/tests/bulk_disable.test.ts index 03c637adaae00..18cc4492be8c9 100644 --- a/x-pack/plugins/alerting/server/rules_client/tests/bulk_disable.test.ts +++ b/x-pack/plugins/alerting/server/rules_client/tests/bulk_disable.test.ts @@ -4,7 +4,7 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ - +import { AlertConsumers } from '@kbn/rule-data-utils'; import { RulesClient, ConstructorOptions } from '../rules_client'; import { savedObjectsClientMock } from '@kbn/core/server/mocks'; import { taskManagerMock } from '@kbn/task-manager-plugin/server/mocks'; @@ -29,7 +29,16 @@ import { savedObjectWith500Error, returnedDisabledRule1, returnedDisabledRule2, + siemRule1, + siemRule2, } from './test_helpers'; +import { migrateLegacyActions } from '../lib'; + +jest.mock('../lib/siem_legacy_actions/migrate_legacy_actions', () => { + return { + migrateLegacyActions: jest.fn(), + }; +}); jest.mock('../../invalidate_pending_api_keys/bulk_mark_api_keys_for_invalidation', () => ({ bulkMarkApiKeysForInvalidation: jest.fn(), @@ -126,6 +135,11 @@ describe('bulkDisableRules', () => { }); mockCreatePointInTimeFinderAsInternalUser(); mockUnsecuredSavedObjectFind(2); + (migrateLegacyActions as jest.Mock).mockResolvedValue({ + hasLegacyActions: false, + resultedActions: [], + resultedReferences: [], + }); }); test('should disable two rule', async () => { @@ -598,4 +612,49 @@ describe('bulkDisableRules', () => { ); }); }); + + describe('legacy actions migration for SIEM', () => { + test('should call migrateLegacyActions', async () => { + encryptedSavedObjects.createPointInTimeFinderDecryptedAsInternalUser = jest + .fn() + .mockResolvedValueOnce({ + close: jest.fn(), + find: function* asyncGenerator() { + yield { saved_objects: [enabledRule1, enabledRule2, siemRule1, siemRule2] }; + }, + }); + + unsecuredSavedObjectsClient.bulkCreate.mockResolvedValue({ + saved_objects: [enabledRule1, enabledRule2, siemRule1, siemRule2], + }); + + await rulesClient.bulkDisableRules({ filter: 'fake_filter' }); + + expect(migrateLegacyActions).toHaveBeenCalledTimes(4); + expect(migrateLegacyActions).toHaveBeenCalledWith(expect.any(Object), { + attributes: enabledRule1.attributes, + ruleId: enabledRule1.id, + actions: [], + references: [], + }); + expect(migrateLegacyActions).toHaveBeenCalledWith(expect.any(Object), { + attributes: enabledRule2.attributes, + ruleId: enabledRule2.id, + actions: [], + references: [], + }); + expect(migrateLegacyActions).toHaveBeenCalledWith(expect.any(Object), { + attributes: expect.objectContaining({ consumer: AlertConsumers.SIEM }), + ruleId: siemRule1.id, + actions: [], + references: [], + }); + expect(migrateLegacyActions).toHaveBeenCalledWith(expect.any(Object), { + attributes: expect.objectContaining({ consumer: AlertConsumers.SIEM }), + ruleId: siemRule2.id, + actions: [], + references: [], + }); + }); + }); }); diff --git a/x-pack/plugins/alerting/server/rules_client/tests/bulk_edit.test.ts b/x-pack/plugins/alerting/server/rules_client/tests/bulk_edit.test.ts index b91bb1cae5e3c..c9e9302fb0146 100644 --- a/x-pack/plugins/alerting/server/rules_client/tests/bulk_edit.test.ts +++ b/x-pack/plugins/alerting/server/rules_client/tests/bulk_edit.test.ts @@ -7,6 +7,7 @@ import { schema } from '@kbn/config-schema'; import { v4 as uuidv4 } from 'uuid'; +import { AlertConsumers } from '@kbn/rule-data-utils'; import { RulesClient, ConstructorOptions } from '../rules_client'; import { savedObjectsClientMock, loggingSystemMock } from '@kbn/core/server/mocks'; import { taskManagerMock } from '@kbn/task-manager-plugin/server/mocks'; @@ -21,6 +22,20 @@ import { auditLoggerMock } from '@kbn/security-plugin/server/audit/mocks'; import { getBeforeSetup, setGlobalDate } from './lib'; import { bulkMarkApiKeysForInvalidation } from '../../invalidate_pending_api_keys/bulk_mark_api_keys_for_invalidation'; import { NormalizedAlertAction } from '../types'; +import { enabledRule1, enabledRule2, siemRule1, siemRule2 } from './test_helpers'; +import { migrateLegacyActions } from '../lib'; +import { migrateLegacyActionsMock } from '../lib/siem_legacy_actions/retrieve_migrated_legacy_actions.mock'; + +jest.mock('../lib/siem_legacy_actions/migrate_legacy_actions', () => { + return { + migrateLegacyActions: jest.fn(), + }; +}); +(migrateLegacyActions as jest.Mock).mockResolvedValue({ + hasLegacyActions: false, + resultedActions: [], + resultedReferences: [], +}); jest.mock('../../invalidate_pending_api_keys/bulk_mark_api_keys_for_invalidation', () => ({ bulkMarkApiKeysForInvalidation: jest.fn(), @@ -196,6 +211,8 @@ describe('bulkEdit()', () => { }, producer: 'alerts', }); + + (migrateLegacyActions as jest.Mock).mockResolvedValue(migrateLegacyActionsMock); }); describe('tags operations', () => { @@ -2491,4 +2508,53 @@ describe('bulkEdit()', () => { expect(taskManager.bulkUpdateSchedules).not.toHaveBeenCalled(); }); }); + + describe('legacy actions migration for SIEM', () => { + test('should call migrateLegacyActions', async () => { + encryptedSavedObjects.createPointInTimeFinderDecryptedAsInternalUser = jest + .fn() + .mockResolvedValueOnce({ + close: jest.fn(), + find: function* asyncGenerator() { + yield { saved_objects: [enabledRule1, enabledRule2, siemRule1, siemRule2] }; + }, + }); + + await rulesClient.bulkEdit({ + operations: [ + { + field: 'tags', + operation: 'set', + value: ['test-tag'], + }, + ], + }); + + expect(migrateLegacyActions).toHaveBeenCalledTimes(4); + expect(migrateLegacyActions).toHaveBeenCalledWith(expect.any(Object), { + attributes: enabledRule1.attributes, + ruleId: enabledRule1.id, + actions: [], + references: [], + }); + expect(migrateLegacyActions).toHaveBeenCalledWith(expect.any(Object), { + attributes: enabledRule2.attributes, + ruleId: enabledRule2.id, + actions: [], + references: [], + }); + expect(migrateLegacyActions).toHaveBeenCalledWith(expect.any(Object), { + attributes: expect.objectContaining({ consumer: AlertConsumers.SIEM }), + ruleId: siemRule1.id, + actions: [], + references: [], + }); + expect(migrateLegacyActions).toHaveBeenCalledWith(expect.any(Object), { + attributes: expect.objectContaining({ consumer: AlertConsumers.SIEM }), + ruleId: siemRule2.id, + actions: [], + references: [], + }); + }); + }); }); diff --git a/x-pack/plugins/alerting/server/rules_client/tests/bulk_enable.test.ts b/x-pack/plugins/alerting/server/rules_client/tests/bulk_enable.test.ts index b82001e83886c..03f9c06109059 100644 --- a/x-pack/plugins/alerting/server/rules_client/tests/bulk_enable.test.ts +++ b/x-pack/plugins/alerting/server/rules_client/tests/bulk_enable.test.ts @@ -4,7 +4,7 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ - +import { AlertConsumers } from '@kbn/rule-data-utils'; import { RulesClient, ConstructorOptions } from '../rules_client'; import { savedObjectsClientMock } from '@kbn/core/server/mocks'; import { taskManagerMock } from '@kbn/task-manager-plugin/server/mocks'; @@ -29,8 +29,17 @@ import { savedObjectWith500Error, returnedRule1, returnedRule2, + siemRule1, + siemRule2, } from './test_helpers'; import { TaskStatus } from '@kbn/task-manager-plugin/server'; +import { migrateLegacyActions } from '../lib'; + +jest.mock('../lib/siem_legacy_actions/migrate_legacy_actions', () => { + return { + migrateLegacyActions: jest.fn(), + }; +}); jest.mock('../../invalidate_pending_api_keys/bulk_mark_api_keys_for_invalidation', () => ({ bulkMarkApiKeysForInvalidation: jest.fn(), @@ -78,6 +87,11 @@ beforeEach(() => { } as unknown as BulkUpdateTaskResult) ); (auditLogger.log as jest.Mock).mockClear(); + (migrateLegacyActions as jest.Mock).mockResolvedValue({ + hasLegacyActions: false, + resultedActions: [], + resultedReferences: [], + }); }); setGlobalDate(); @@ -779,4 +793,43 @@ describe('bulkEnableRules', () => { expect(auditLogger.log.mock.calls[0][0]?.event?.outcome).toEqual('failure'); }); }); + + describe('legacy actions migration for SIEM', () => { + test('should call migrateLegacyActions', async () => { + encryptedSavedObjects.createPointInTimeFinderDecryptedAsInternalUser = jest + .fn() + .mockResolvedValueOnce({ + close: jest.fn(), + find: function* asyncGenerator() { + yield { saved_objects: [disabledRule1, siemRule1, siemRule2] }; + }, + }); + + unsecuredSavedObjectsClient.bulkCreate.mockResolvedValue({ + saved_objects: [disabledRule1, siemRule1, siemRule2], + }); + + await rulesClient.bulkEnableRules({ filter: 'fake_filter' }); + + expect(migrateLegacyActions).toHaveBeenCalledTimes(3); + expect(migrateLegacyActions).toHaveBeenCalledWith(expect.any(Object), { + attributes: disabledRule1.attributes, + ruleId: disabledRule1.id, + actions: [], + references: [], + }); + expect(migrateLegacyActions).toHaveBeenCalledWith(expect.any(Object), { + attributes: expect.objectContaining({ consumer: AlertConsumers.SIEM }), + ruleId: siemRule1.id, + actions: [], + references: [], + }); + expect(migrateLegacyActions).toHaveBeenCalledWith(expect.any(Object), { + attributes: expect.objectContaining({ consumer: AlertConsumers.SIEM }), + ruleId: siemRule2.id, + actions: [], + references: [], + }); + }); + }); }); diff --git a/x-pack/plugins/alerting/server/rules_client/tests/delete.test.ts b/x-pack/plugins/alerting/server/rules_client/tests/delete.test.ts index edf03a2d54e42..a8f13dc25869d 100644 --- a/x-pack/plugins/alerting/server/rules_client/tests/delete.test.ts +++ b/x-pack/plugins/alerting/server/rules_client/tests/delete.test.ts @@ -5,6 +5,8 @@ * 2.0. */ +import { AlertConsumers } from '@kbn/rule-data-utils'; + import { RulesClient, ConstructorOptions } from '../rules_client'; import { savedObjectsClientMock, loggingSystemMock } from '@kbn/core/server/mocks'; import { taskManagerMock } from '@kbn/task-manager-plugin/server/mocks'; @@ -17,6 +19,18 @@ import { ActionsAuthorization } from '@kbn/actions-plugin/server'; import { auditLoggerMock } from '@kbn/security-plugin/server/audit/mocks'; import { getBeforeSetup } from './lib'; import { bulkMarkApiKeysForInvalidation } from '../../invalidate_pending_api_keys/bulk_mark_api_keys_for_invalidation'; +import { migrateLegacyActions } from '../lib'; + +jest.mock('../lib/siem_legacy_actions/migrate_legacy_actions', () => { + return { + migrateLegacyActions: jest.fn(), + }; +}); +(migrateLegacyActions as jest.Mock).mockResolvedValue({ + hasLegacyActions: false, + resultedActions: [], + resultedReferences: [], +}); jest.mock('../../invalidate_pending_api_keys/bulk_mark_api_keys_for_invalidation', () => ({ bulkMarkApiKeysForInvalidation: jest.fn(), @@ -215,6 +229,27 @@ describe('delete()', () => { ); }); + describe('legacy actions migration for SIEM', () => { + test('should call migrateLegacyActions', async () => { + const existingDecryptedSiemAlert = { + ...existingDecryptedAlert, + attributes: { ...existingDecryptedAlert.attributes, consumer: AlertConsumers.SIEM }, + }; + + encryptedSavedObjects.getDecryptedAsInternalUser.mockResolvedValueOnce( + existingDecryptedSiemAlert + ); + + await rulesClient.delete({ id: '1' }); + + expect(migrateLegacyActions).toHaveBeenCalledWith(expect.any(Object), { + ruleId: '1', + skipActionsValidation: true, + attributes: existingDecryptedSiemAlert.attributes, + }); + }); + }); + describe('authorization', () => { test('ensures user is authorised to delete this type of alert under the consumer', async () => { await rulesClient.delete({ id: '1' }); diff --git a/x-pack/plugins/alerting/server/rules_client/tests/disable.test.ts b/x-pack/plugins/alerting/server/rules_client/tests/disable.test.ts index 09dd8f54d01fd..c09b491dbbdcc 100644 --- a/x-pack/plugins/alerting/server/rules_client/tests/disable.test.ts +++ b/x-pack/plugins/alerting/server/rules_client/tests/disable.test.ts @@ -4,6 +4,7 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ +import { AlertConsumers } from '@kbn/rule-data-utils'; import { RulesClient, ConstructorOptions } from '../rules_client'; import { savedObjectsClientMock, loggingSystemMock } from '@kbn/core/server/mocks'; @@ -18,6 +19,14 @@ import { auditLoggerMock } from '@kbn/security-plugin/server/audit/mocks'; import { getBeforeSetup, setGlobalDate } from './lib'; import { eventLoggerMock } from '@kbn/event-log-plugin/server/event_logger.mock'; import { TaskStatus } from '@kbn/task-manager-plugin/server'; +import { migrateLegacyActions } from '../lib'; +import { migrateLegacyActionsMock } from '../lib/siem_legacy_actions/retrieve_migrated_legacy_actions.mock'; + +jest.mock('../lib/siem_legacy_actions/migrate_legacy_actions', () => { + return { + migrateLegacyActions: jest.fn(), + }; +}); jest.mock('../../invalidate_pending_api_keys/bulk_mark_api_keys_for_invalidation', () => ({ bulkMarkApiKeysForInvalidation: jest.fn(), @@ -122,6 +131,11 @@ describe('disable()', () => { rulesClient = new RulesClient(rulesClientParams); unsecuredSavedObjectsClient.get.mockResolvedValue(existingRule); encryptedSavedObjects.getDecryptedAsInternalUser.mockResolvedValue(existingDecryptedRule); + (migrateLegacyActions as jest.Mock).mockResolvedValue({ + hasLegacyActions: false, + resultedActions: [], + resultedReferences: [], + }); }); describe('authorization', () => { @@ -573,4 +587,35 @@ describe('disable()', () => { ); expect(taskManager.bulkDisable).not.toHaveBeenCalled(); }); + + describe('legacy actions migration for SIEM', () => { + test('should call migrateLegacyActions', async () => { + const existingDecryptedSiemRule = { + ...existingDecryptedRule, + attributes: { ...existingDecryptedRule.attributes, consumer: AlertConsumers.SIEM }, + }; + + encryptedSavedObjects.getDecryptedAsInternalUser.mockResolvedValue(existingDecryptedSiemRule); + (migrateLegacyActions as jest.Mock).mockResolvedValue(migrateLegacyActionsMock); + + await rulesClient.disable({ id: '1' }); + + expect(migrateLegacyActions).toHaveBeenCalledWith(expect.any(Object), { + attributes: expect.objectContaining({ consumer: AlertConsumers.SIEM }), + actions: [ + { + actionRef: '1', + actionTypeId: '1', + group: 'default', + id: '1', + params: { + foo: true, + }, + }, + ], + references: [], + ruleId: '1', + }); + }); + }); }); diff --git a/x-pack/plugins/alerting/server/rules_client/tests/enable.test.ts b/x-pack/plugins/alerting/server/rules_client/tests/enable.test.ts index d15e96f573a84..d7175e9a47b89 100644 --- a/x-pack/plugins/alerting/server/rules_client/tests/enable.test.ts +++ b/x-pack/plugins/alerting/server/rules_client/tests/enable.test.ts @@ -4,6 +4,7 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ +import { AlertConsumers } from '@kbn/rule-data-utils'; import { RulesClient, ConstructorOptions } from '../rules_client'; import { savedObjectsClientMock, loggingSystemMock } from '@kbn/core/server/mocks'; @@ -17,6 +18,14 @@ import { ActionsAuthorization } from '@kbn/actions-plugin/server'; import { TaskStatus } from '@kbn/task-manager-plugin/server'; import { auditLoggerMock } from '@kbn/security-plugin/server/audit/mocks'; import { getBeforeSetup, setGlobalDate } from './lib'; +import { migrateLegacyActions } from '../lib'; +import { migrateLegacyActionsMock } from '../lib/siem_legacy_actions/retrieve_migrated_legacy_actions.mock'; + +jest.mock('../lib/siem_legacy_actions/migrate_legacy_actions', () => { + return { + migrateLegacyActions: jest.fn(), + }; +}); jest.mock('../../invalidate_pending_api_keys/bulk_mark_api_keys_for_invalidation', () => ({ bulkMarkApiKeysForInvalidation: jest.fn(), @@ -122,6 +131,11 @@ describe('enable()', () => { apiKeysEnabled: false, }); taskManager.get.mockResolvedValue(mockTask); + (migrateLegacyActions as jest.Mock).mockResolvedValue({ + hasLegacyActions: false, + resultedActions: [], + resultedReferences: [], + }); }); describe('authorization', () => { @@ -658,4 +672,58 @@ describe('enable()', () => { scheduledTaskId: '1', }); }); + + describe('legacy actions migration for SIEM', () => { + test('should call migrateLegacyActions', async () => { + (migrateLegacyActions as jest.Mock).mockResolvedValueOnce({ + hasLegacyActions: true, + resultedActions: ['fake-action-1'], + resultedReferences: ['fake-ref-1'], + }); + + const existingDecryptedSiemRule = { + ...existingRule, + attributes: { ...existingRule.attributes, consumer: AlertConsumers.SIEM }, + }; + + encryptedSavedObjects.getDecryptedAsInternalUser.mockResolvedValue(existingDecryptedSiemRule); + (migrateLegacyActions as jest.Mock).mockResolvedValue(migrateLegacyActionsMock); + + await rulesClient.enable({ id: '1' }); + + expect(migrateLegacyActions).toHaveBeenCalledWith(expect.any(Object), { + attributes: expect.objectContaining({ consumer: AlertConsumers.SIEM }), + actions: [ + { + actionRef: '1', + actionTypeId: '1', + group: 'default', + id: '1', + params: { + foo: true, + }, + }, + ], + references: [], + ruleId: '1', + }); + // to mitigate AAD issues, we call create with overwrite=true and actions related props + expect(unsecuredSavedObjectsClient.create).toHaveBeenCalledWith( + 'alert', + expect.objectContaining({ + ...existingDecryptedSiemRule.attributes, + actions: ['fake-action-1'], + throttle: undefined, + notifyWhen: undefined, + enabled: true, + }), + { + id: existingDecryptedSiemRule.id, + overwrite: true, + references: ['fake-ref-1'], + version: existingDecryptedSiemRule.version, + } + ); + }); + }); }); diff --git a/x-pack/plugins/alerting/server/rules_client/tests/find.test.ts b/x-pack/plugins/alerting/server/rules_client/tests/find.test.ts index b8eb4d4d0c478..3573508d891cd 100644 --- a/x-pack/plugins/alerting/server/rules_client/tests/find.test.ts +++ b/x-pack/plugins/alerting/server/rules_client/tests/find.test.ts @@ -19,6 +19,14 @@ import { auditLoggerMock } from '@kbn/security-plugin/server/audit/mocks'; import { getBeforeSetup, setGlobalDate } from './lib'; import { RecoveredActionGroup } from '../../../common'; import { RegistryRuleType } from '../../rule_type_registry'; +import { enabledRule1, enabledRule2, siemRule1, siemRule2 } from './test_helpers'; +import { formatLegacyActions } from '../lib'; + +jest.mock('../lib/siem_legacy_actions/format_legacy_actions', () => { + return { + formatLegacyActions: jest.fn(), + }; +}); const taskManager = taskManagerMock.createStart(); const ruleTypeRegistry = ruleTypeRegistryMock.create(); @@ -806,4 +814,39 @@ describe('find()', () => { ); }); }); + + describe('legacy actions migration for SIEM', () => { + test('should call migrateLegacyActions', async () => { + const rulesClient = new RulesClient(rulesClientParams); + + (formatLegacyActions as jest.Mock).mockResolvedValueOnce([ + { ...siemRule1, migrated: true }, + { ...siemRule2, migrated: true }, + ]); + + unsecuredSavedObjectsClient.find.mockReset(); + unsecuredSavedObjectsClient.find.mockResolvedValueOnce({ + total: 1, + per_page: 10, + page: 1, + saved_objects: [enabledRule1, enabledRule2, siemRule1, siemRule2].map((r) => ({ + ...r, + score: 1, + })), + }); + + const result = await rulesClient.find({ options: {} }); + + expect(formatLegacyActions).toHaveBeenCalledTimes(1); + expect(formatLegacyActions).toHaveBeenCalledWith( + [ + expect.objectContaining({ id: siemRule1.id }), + expect.objectContaining({ id: siemRule2.id }), + ], + expect.any(Object) + ); + expect(result.data[2]).toEqual(expect.objectContaining({ id: siemRule1.id, migrated: true })); + expect(result.data[3]).toEqual(expect.objectContaining({ id: siemRule2.id, migrated: true })); + }); + }); }); diff --git a/x-pack/plugins/alerting/server/rules_client/tests/get.test.ts b/x-pack/plugins/alerting/server/rules_client/tests/get.test.ts index 30abf4c661d19..1ca6e664fc359 100644 --- a/x-pack/plugins/alerting/server/rules_client/tests/get.test.ts +++ b/x-pack/plugins/alerting/server/rules_client/tests/get.test.ts @@ -4,6 +4,7 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ +import { AlertConsumers } from '@kbn/rule-data-utils'; import { RulesClient, ConstructorOptions } from '../rules_client'; import { savedObjectsClientMock, loggingSystemMock } from '@kbn/core/server/mocks'; @@ -17,6 +18,13 @@ import { ActionsAuthorization } from '@kbn/actions-plugin/server'; import { auditLoggerMock } from '@kbn/security-plugin/server/audit/mocks'; import { getBeforeSetup, setGlobalDate } from './lib'; import { RecoveredActionGroup } from '../../../common'; +import { formatLegacyActions } from '../lib'; + +jest.mock('../lib/siem_legacy_actions/format_legacy_actions', () => { + return { + formatLegacyActions: jest.fn(), + }; +}); const taskManager = taskManagerMock.createStart(); const ruleTypeRegistry = ruleTypeRegistryMock.create(); @@ -509,4 +517,72 @@ describe('get()', () => { ); }); }); + + describe('legacy actions migration for SIEM', () => { + const rule = { + id: '1', + type: 'alert', + attributes: { + alertTypeId: '123', + schedule: { interval: '10s' }, + params: { + bar: true, + }, + createdAt: new Date().toISOString(), + updatedAt: new Date().toISOString(), + actions: [ + { + group: 'default', + actionRef: 'action_0', + params: { + foo: true, + }, + }, + ], + notifyWhen: 'onActiveAlert', + }, + references: [ + { + name: 'action_0', + type: 'action', + id: '1', + }, + ], + }; + + test('should call formatLegacyActions if consumer is SIEM', async () => { + const rulesClient = new RulesClient(rulesClientParams); + unsecuredSavedObjectsClient.get.mockResolvedValueOnce({ + ...rule, + attributes: { + ...rule.attributes, + consumer: AlertConsumers.SIEM, + }, + }); + (formatLegacyActions as jest.Mock).mockResolvedValue([ + { + id: 'migrated_rule_mock', + }, + ]); + + const result = await rulesClient.get({ id: '1' }); + + expect(formatLegacyActions).toHaveBeenCalledWith( + [expect.objectContaining({ id: '1' })], + expect.any(Object) + ); + + expect(result).toEqual({ + id: 'migrated_rule_mock', + }); + }); + + test('should not call formatLegacyActions if consumer is not SIEM', async () => { + unsecuredSavedObjectsClient.get.mockResolvedValueOnce(rule); + const rulesClient = new RulesClient(rulesClientParams); + await rulesClient.get({ id: '1' }); + + expect(formatLegacyActions).not.toHaveBeenCalled(); + }); + }); }); diff --git a/x-pack/plugins/alerting/server/rules_client/tests/resolve.test.ts b/x-pack/plugins/alerting/server/rules_client/tests/resolve.test.ts index 6f52da62023e9..bb22b5f6d0d53 100644 --- a/x-pack/plugins/alerting/server/rules_client/tests/resolve.test.ts +++ b/x-pack/plugins/alerting/server/rules_client/tests/resolve.test.ts @@ -4,6 +4,7 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ +import { AlertConsumers } from '@kbn/rule-data-utils'; import { RulesClient, ConstructorOptions } from '../rules_client'; import { savedObjectsClientMock, loggingSystemMock } from '@kbn/core/server/mocks'; @@ -17,6 +18,13 @@ import { ActionsAuthorization } from '@kbn/actions-plugin/server'; import { auditLoggerMock } from '@kbn/security-plugin/server/audit/mocks'; import { getBeforeSetup, setGlobalDate } from './lib'; import { RecoveredActionGroup } from '../../../common'; +import { formatLegacyActions } from '../lib'; + +jest.mock('../lib/siem_legacy_actions/format_legacy_actions', () => { + return { + formatLegacyActions: jest.fn(), + }; +}); const taskManager = taskManagerMock.createStart(); const ruleTypeRegistry = ruleTypeRegistryMock.create(); @@ -583,4 +591,82 @@ describe('resolve()', () => { ); }); }); + + describe('legacy actions migration for SIEM', () => { + const rule = { + id: '1', + type: 'alert', + attributes: { + alertTypeId: '123', + schedule: { interval: '10s' }, + params: { + bar: true, + }, + createdAt: new Date().toISOString(), + updatedAt: new Date().toISOString(), + actions: [ + { + group: 'default', + actionRef: 'action_0', + params: { + foo: true, + }, + }, + ], + notifyWhen: 'onActiveAlert', + }, + references: [ + { + name: 'action_0', + type: 'action', + id: '1', + }, + ], + }; + + test('should call formatLegacyActions if consumer is SIEM', async () => { + const rulesClient = new RulesClient(rulesClientParams); + unsecuredSavedObjectsClient.resolve.mockResolvedValueOnce({ + saved_object: { + ...rule, + attributes: { + ...rule.attributes, + consumer: AlertConsumers.SIEM, + }, + }, + outcome: 'aliasMatch', + alias_target_id: '2', + }); + (formatLegacyActions as jest.Mock).mockResolvedValue([ + { + id: 'migrated_rule_mock', + }, + ]); + + const result = await rulesClient.resolve({ id: '1' }); + + expect(formatLegacyActions).toHaveBeenCalledWith( + [expect.objectContaining({ id: '1' })], + expect.any(Object) + ); + + expect(result).toEqual({ + id: 'migrated_rule_mock', + outcome: 'aliasMatch', + alias_target_id: '2', + }); + }); + + test('should not call formatLegacyActions if consumer is not SIEM', async () => { + unsecuredSavedObjectsClient.resolve.mockResolvedValueOnce({ + saved_object: rule, + outcome: 'aliasMatch', + alias_target_id: '2', + }); + const rulesClient = new RulesClient(rulesClientParams); + await rulesClient.resolve({ id: '1' }); + + expect(formatLegacyActions).not.toHaveBeenCalled(); + }); + }); }); diff --git a/x-pack/plugins/alerting/server/rules_client/tests/test_helpers.ts b/x-pack/plugins/alerting/server/rules_client/tests/test_helpers.ts index 08c037e9410e7..fd4e534838940 100644 --- a/x-pack/plugins/alerting/server/rules_client/tests/test_helpers.ts +++ b/x-pack/plugins/alerting/server/rules_client/tests/test_helpers.ts @@ -4,6 +4,7 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ +import { AlertConsumers } from '@kbn/rule-data-utils'; import type { SavedObject } from '@kbn/core-saved-objects-server'; @@ -41,6 +42,20 @@ export const defaultRule = { version: '1', }; +export const siemRule1 = { + ...defaultRule, + attributes: { + ...defaultRule.attributes, + consumer: AlertConsumers.SIEM, + }, + id: 'siem-id1', +}; + +export const siemRule2 = { + ...siemRule1, + id: 'siem-id2', +}; + export const enabledRule1 = { ...defaultRule, attributes: { diff --git a/x-pack/plugins/alerting/server/rules_client/tests/update.test.ts b/x-pack/plugins/alerting/server/rules_client/tests/update.test.ts index 6fcc0cb915a36..b76d0607f22a8 100644 --- a/x-pack/plugins/alerting/server/rules_client/tests/update.test.ts +++ b/x-pack/plugins/alerting/server/rules_client/tests/update.test.ts @@ -7,6 +7,7 @@ import { v4 as uuidv4 } from 'uuid'; import { schema } from '@kbn/config-schema'; +import { AlertConsumers } from '@kbn/rule-data-utils'; import { RulesClient, ConstructorOptions } from '../rules_client'; import { savedObjectsClientMock, loggingSystemMock } from '@kbn/core/server/mocks'; import { taskManagerMock } from '@kbn/task-manager-plugin/server/mocks'; @@ -22,6 +23,13 @@ import { TaskStatus } from '@kbn/task-manager-plugin/server'; import { auditLoggerMock } from '@kbn/security-plugin/server/audit/mocks'; import { getBeforeSetup, setGlobalDate } from './lib'; import { bulkMarkApiKeysForInvalidation } from '../../invalidate_pending_api_keys/bulk_mark_api_keys_for_invalidation'; +import { migrateLegacyActions } from '../lib'; + +jest.mock('../lib/siem_legacy_actions/migrate_legacy_actions', () => { + return { + migrateLegacyActions: jest.fn(), + }; +}); jest.mock('@kbn/core-saved-objects-utils-server', () => { const actual = jest.requireActual('@kbn/core-saved-objects-utils-server'); @@ -164,6 +172,11 @@ describe('update()', () => { }, producer: 'alerts', }); + (migrateLegacyActions as jest.Mock).mockResolvedValue({ + hasLegacyActions: false, + resultedActions: [], + resultedReferences: [], + }); }); test('updates given parameters', async () => { @@ -2734,6 +2747,64 @@ describe('update()', () => { ); }); + describe('legacy actions migration for SIEM', () => { + beforeEach(() => { + unsecuredSavedObjectsClient.create.mockResolvedValueOnce({ + id: '1', + type: 'alert', + attributes: { + enabled: true, + schedule: { interval: '1m' }, + params: { + bar: true, + }, + actions: [], + notifyWhen: 'onActiveAlert', + scheduledTaskId: 'task-123', + createdAt: new Date().toISOString(), + updatedAt: new Date().toISOString(), + }, + references: [], + }); + }); + + test('should call migrateLegacyActions', async () => { + const existingDecryptedSiemAlert = { + ...existingDecryptedAlert, + attributes: { ...existingDecryptedAlert, consumer: AlertConsumers.SIEM }, + }; + + encryptedSavedObjects.getDecryptedAsInternalUser.mockResolvedValueOnce( + existingDecryptedSiemAlert + ); + + actionsClient.getBulk.mockReset(); + actionsClient.isPreconfigured.mockReset(); + + await rulesClient.update({ + id: '1', + data: { + schedule: { interval: '1m' }, + name: 'abc', + tags: ['foo'], + params: { + bar: true, + risk_score: 40, + severity: 'low', + }, + throttle: null, + notifyWhen: 'onActiveAlert', + actions: [], + }, + }); + + expect(migrateLegacyActions).toHaveBeenCalledWith(expect.any(Object), { + ruleId: '1', + attributes: existingDecryptedSiemAlert.attributes, + }); + }); + }); + it('calls the authentication API key function if the user is authenticated using an api key', async () => { rulesClientParams.isAuthenticationTypeAPIKey.mockReturnValueOnce(true); rulesClientParams.getAuthenticationAPIKey.mockReturnValueOnce({ diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/install_prebuilt_rules_and_timelines/route.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/install_prebuilt_rules_and_timelines/route.test.ts index af6fca55078fb..38e98266aee7a 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/install_prebuilt_rules_and_timelines/route.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/install_prebuilt_rules_and_timelines/route.test.ts @@ -19,17 +19,6 @@ import type { ExceptionListClient } from '@kbn/lists-plugin/server'; import { installPrepackagedTimelines } from '../../../../timeline/routes/prepackaged_timelines/install_prepackaged_timelines'; import { elasticsearchClientMock } from '@kbn/core-elasticsearch-client-server-mocks'; import { getQueryRuleParams } from '../../../rule_schema/mocks'; -import { legacyMigrate } from '../../../rule_management'; - -jest.mock('../../../rule_management/logic/rule_actions/legacy_action_migration', () => { - const actual = jest.requireActual( - '../../../rule_management/logic/rule_actions/legacy_action_migration' - ); - return { - ...actual, - legacyMigrate: jest.fn(), - }; -}); jest.mock('../../logic/rule_assets/prebuilt_rule_assets_client', () => { return { @@ -105,8 +94,6 @@ describe('add_prepackaged_rules_route', () => { errors: [], }); - (legacyMigrate as jest.Mock).mockResolvedValue(getRuleMock(getQueryRuleParams())); - context.core.elasticsearch.client.asCurrentUser.search.mockResolvedValue( elasticsearchClientMock.createSuccessTransportRequestPromise(getBasicEmptySearchResponse()) ); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/rule_objects/update_prebuilt_rules.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/rule_objects/update_prebuilt_rules.test.ts index 13e91b1bc2dfa..5afe5a4753c10 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/rule_objects/update_prebuilt_rules.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/rule_objects/update_prebuilt_rules.test.ts @@ -14,21 +14,10 @@ import { import { updatePrebuiltRules } from './update_prebuilt_rules'; import { patchRules } from '../../../rule_management/logic/crud/patch_rules'; import { getPrebuiltRuleMock, getPrebuiltThreatMatchRuleMock } from '../../mocks'; -import { legacyMigrate } from '../../../rule_management'; -import { getQueryRuleParams, getThreatRuleParams } from '../../../rule_schema/mocks'; +import { getThreatRuleParams } from '../../../rule_schema/mocks'; jest.mock('../../../rule_management/logic/crud/patch_rules'); -jest.mock('../../../rule_management/logic/rule_actions/legacy_action_migration', () => { - const actual = jest.requireActual( - '../../../rule_management/logic/rule_actions/legacy_action_migration' - ); - return { - ...actual, - legacyMigrate: jest.fn(), - }; -}); - describe('updatePrebuiltRules', () => { let rulesClient: ReturnType; let savedObjectsClient: ReturnType; @@ -36,8 +25,6 @@ describe('updatePrebuiltRules', () => { beforeEach(() => { rulesClient = rulesClientMock.create(); savedObjectsClient = savedObjectsClientMock.create(); - - (legacyMigrate as jest.Mock).mockResolvedValue(getRuleMock(getQueryRuleParams())); }); it('should omit actions and enabled when calling patchRules', async () => { @@ -82,7 +69,6 @@ describe('updatePrebuiltRules', () => { ...getFindResultWithSingleHit(), data: [getRuleMock(getThreatRuleParams())], }); - (legacyMigrate as jest.Mock).mockResolvedValue(getRuleMock(getThreatRuleParams())); await updatePrebuiltRules(rulesClient, savedObjectsClient, [ { ...prepackagedRule, ...updatedThreatParams }, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/rule_objects/update_prebuilt_rules.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/rule_objects/update_prebuilt_rules.ts index 407ffac13e48c..cbd9f32069a95 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/rule_objects/update_prebuilt_rules.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/rule_objects/update_prebuilt_rules.ts @@ -12,7 +12,6 @@ import type { RulesClient, PartialRule } from '@kbn/alerting-plugin/server'; import { transformAlertToRuleAction } from '../../../../../../common/detection_engine/transform_actions'; import { MAX_RULES_TO_UPDATE_IN_PARALLEL } from '../../../../../../common/constants'; -import { legacyMigrate } from '../../../rule_management'; import { createRules } from '../../../rule_management/logic/crud/create_rules'; import { readRules } from '../../../rule_management/logic/crud/read_rules'; import { patchRules } from '../../../rule_management/logic/crud/patch_rules'; @@ -62,22 +61,16 @@ const createPromises = ( id: undefined, }); - const migratedRule = await legacyMigrate({ - rulesClient, - savedObjectsClient, - rule: existingRule, - }); - - if (!migratedRule) { + if (!existingRule) { throw new PrepackagedRulesError(`Failed to find rule ${rule.rule_id}`, 500); } // If we're trying to change the type of a prepackaged rule, we need to delete the old one // and replace it with the new rule, keeping the enabled setting, actions, throttle, id, // and exception lists from the old rule - if (rule.type !== migratedRule.params.type) { + if (rule.type !== existingRule.params.type) { await deleteRules({ - ruleId: migratedRule.id, + ruleId: existingRule.id, rulesClient, }); @@ -87,14 +80,14 @@ const createPromises = ( ...rule, // Force the prepackaged rule to use the enabled state from the existing rule, // regardless of what the prepackaged rule says - enabled: migratedRule.enabled, - actions: migratedRule.actions.map(transformAlertToRuleAction), + enabled: existingRule.enabled, + actions: existingRule.actions.map(transformAlertToRuleAction), }, }); } else { return patchRules({ rulesClient, - existingRule: migratedRule, + existingRule, nextParams: { ...rule, // Force enabled to use the enabled state from the existing rule by passing in undefined to patchRules diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/__mocks__/request_responses.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/__mocks__/request_responses.ts index a9fb2781129e9..d8ac197423f8e 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/__mocks__/request_responses.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/__mocks__/request_responses.ts @@ -6,7 +6,7 @@ */ import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; -import type { SavedObjectsFindResponse, SavedObjectsFindResult } from '@kbn/core/server'; +import type { SavedObjectsFindResponse } from '@kbn/core/server'; import { ALERT_WORKFLOW_STATUS } from '@kbn/rule-data-utils'; import { ruleTypeMappings } from '@kbn/securitysolution-rules'; import type { SanitizedRule, ResolvedSanitizedRule } from '@kbn/alerting-plugin/common'; @@ -42,10 +42,7 @@ import { getFinalizeSignalsMigrationSchemaMock } from '../../../../../common/det import { getSignalsMigrationStatusSchemaMock } from '../../../../../common/detection_engine/schemas/request/get_signals_migration_status_schema.mock'; // eslint-disable-next-line no-restricted-imports -import type { - LegacyRuleNotificationAlertType, - LegacyIRuleActionsAttributes, -} from '../../rule_actions_legacy'; +import type { LegacyRuleNotificationAlertType } from '../../rule_actions_legacy'; import type { HapiReadableStream } from '../../rule_management/logic/import/hapi_readable_stream'; import type { RuleAlertType, RuleParams } from '../../rule_schema'; import { getQueryRuleParams } from '../../rule_schema/mocks'; @@ -556,153 +553,6 @@ export const legacyGetNotificationResult = ({ revision: 0, }); -/** - * @deprecated Once we are confident all rules relying on side-car actions SO's have been migrated to SO references we should remove this function - */ -export const legacyGetHourlyNotificationResult = ( - id = '456', - ruleId = '123' -): LegacyRuleNotificationAlertType => ({ - id, - name: 'Notification for Rule Test', - tags: [], - alertTypeId: 'siem.notifications', - consumer: 'siem', - params: { - ruleAlertId: `${ruleId}`, - }, - schedule: { - interval: '1h', - }, - enabled: true, - actions: [ - { - group: 'default', - params: { - message: 'Rule {{context.rule.name}} generated {{state.signals_count}} alerts', - to: ['test@test.com'], - subject: 'Test Actions', - }, - actionTypeId: '.email', - id: '99403909-ca9b-49ba-9d7a-7e5320e68d05', - }, - ], - throttle: null, - notifyWhen: 'onActiveAlert', - apiKey: null, - apiKeyOwner: 'elastic', - createdBy: 'elastic', - updatedBy: 'elastic', - createdAt: new Date('2020-03-21T11:15:13.530Z'), - muteAll: false, - mutedInstanceIds: [], - scheduledTaskId: '62b3a130-6b70-11ea-9ce9-6b9818c4cbd7', - updatedAt: new Date('2020-03-21T12:37:08.730Z'), - executionStatus: { - status: 'unknown', - lastExecutionDate: new Date('2020-08-20T19:23:38Z'), - }, - revision: 0, -}); - -/** - * @deprecated Once we are confident all rules relying on side-car actions SO's have been migrated to SO references we should remove this function - */ -export const legacyGetDailyNotificationResult = ( - id = '456', - ruleId = '123' -): LegacyRuleNotificationAlertType => ({ - id, - name: 'Notification for Rule Test', - tags: [], - alertTypeId: 'siem.notifications', - consumer: 'siem', - params: { - ruleAlertId: `${ruleId}`, - }, - schedule: { - interval: '1d', - }, - enabled: true, - actions: [ - { - group: 'default', - params: { - message: 'Rule {{context.rule.name}} generated {{state.signals_count}} alerts', - to: ['test@test.com'], - subject: 'Test Actions', - }, - actionTypeId: '.email', - id: '99403909-ca9b-49ba-9d7a-7e5320e68d05', - }, - ], - throttle: null, - notifyWhen: 'onActiveAlert', - apiKey: null, - apiKeyOwner: 'elastic', - createdBy: 'elastic', - updatedBy: 'elastic', - createdAt: new Date('2020-03-21T11:15:13.530Z'), - muteAll: false, - mutedInstanceIds: [], - scheduledTaskId: '62b3a130-6b70-11ea-9ce9-6b9818c4cbd7', - updatedAt: new Date('2020-03-21T12:37:08.730Z'), - executionStatus: { - status: 'unknown', - lastExecutionDate: new Date('2020-08-20T19:23:38Z'), - }, - revision: 0, -}); - -/** - * @deprecated Once we are confident all rules relying on side-car actions SO's have been migrated to SO references we should remove this function - */ -export const legacyGetWeeklyNotificationResult = ( - id = '456', - ruleId = '123' -): LegacyRuleNotificationAlertType => ({ - id, - name: 'Notification for Rule Test', - tags: [], - alertTypeId: 'siem.notifications', - consumer: 'siem', - params: { - ruleAlertId: `${ruleId}`, - }, - schedule: { - interval: '7d', - }, - enabled: true, - actions: [ - { - group: 'default', - params: { - message: 'Rule {{context.rule.name}} generated {{state.signals_count}} alerts', - to: ['test@test.com'], - subject: 'Test Actions', - }, - actionTypeId: '.email', - id: '99403909-ca9b-49ba-9d7a-7e5320e68d05', - }, - ], - throttle: null, - notifyWhen: 'onActiveAlert', - apiKey: null, - apiKeyOwner: 'elastic', - createdBy: 'elastic', - updatedBy: 'elastic', - createdAt: new Date('2020-03-21T11:15:13.530Z'), - muteAll: false, - mutedInstanceIds: [], - scheduledTaskId: '62b3a130-6b70-11ea-9ce9-6b9818c4cbd7', - updatedAt: new Date('2020-03-21T12:37:08.730Z'), - executionStatus: { - status: 'unknown', - lastExecutionDate: new Date('2020-08-20T19:23:38Z'), - }, - revision: 0, -}); - /** * @deprecated Once we are confident all rules relying on side-car actions SO's have been migrated to SO references we should remove this function */ @@ -714,205 +564,3 @@ export const legacyGetFindNotificationsResultWithSingleHit = ( total: 1, data: [legacyGetNotificationResult({ ruleId })], }); - -/** - * @deprecated Once we are confident all rules relying on side-car actions SO's have been migrated to SO references we should remove this function - */ -export const legacyGetSiemNotificationRuleNoActionsSOResult = ( - ruleId = '123' -): SavedObjectsFindResult => ({ - type: 'siem-detection-engine-rule-actions', - id: 'ID_OF_LEGACY_SIDECAR_NO_ACTIONS', - namespaces: ['default'], - attributes: { - actions: [], - ruleThrottle: 'no_actions', - alertThrottle: null, - }, - references: [{ id: ruleId, type: 'alert', name: 'alert_0' }], - migrationVersion: { - 'siem-detection-engine-rule-actions': '7.11.2', - }, - coreMigrationVersion: '7.15.2', - updated_at: '2022-03-31T19:06:40.473Z', - version: 'WzIzNywxXQ==', - score: 0, -}); - -/** - * @deprecated Once we are confident all rules relying on side-car actions SO's have been migrated to SO references we should remove this function - */ -export const legacyGetSiemNotificationRuleEveryRunSOResult = ( - ruleId = '123' -): SavedObjectsFindResult => ({ - type: 'siem-detection-engine-rule-actions', - id: 'ID_OF_LEGACY_SIDECAR_RULE_RUN_ACTIONS', - namespaces: ['default'], - attributes: { - actions: [ - { - group: 'default', - actionRef: 'action_0', - params: { - message: 'Rule {{context.rule.name}} generated {{state.signals_count}} alerts', - to: ['test@test.com'], - subject: 'Test Actions', - }, - action_type_id: '.email', - }, - ], - ruleThrottle: 'rule', - alertThrottle: null, - }, - references: [{ id: ruleId, type: 'alert', name: 'alert_0' }], - migrationVersion: { - 'siem-detection-engine-rule-actions': '7.11.2', - }, - coreMigrationVersion: '7.15.2', - updated_at: '2022-03-31T19:06:40.473Z', - version: 'WzIzNywxXQ==', - score: 0, -}); - -/** - * @deprecated Once we are confident all rules relying on side-car actions SO's have been migrated to SO references we should remove this function - */ -export const legacyGetSiemNotificationRuleHourlyActionsSOResult = ( - ruleId = '123', - connectorId = '456' -): SavedObjectsFindResult => ({ - type: 'siem-detection-engine-rule-actions', - id: 'ID_OF_LEGACY_SIDECAR_HOURLY_ACTIONS', - namespaces: ['default'], - attributes: { - actions: [ - { - group: 'default', - actionRef: 'action_0', - params: { - message: 'Rule {{context.rule.name}} generated {{state.signals_count}} alerts', - to: ['test@test.com'], - subject: 'Test Actions', - }, - action_type_id: '.email', - }, - ], - ruleThrottle: '1h', - alertThrottle: '1h', - }, - references: [ - { id: ruleId, type: 'alert', name: 'alert_0' }, - { id: connectorId, type: 'action', name: 'action_0' }, - ], - migrationVersion: { - 'siem-detection-engine-rule-actions': '7.11.2', - }, - coreMigrationVersion: '7.15.2', - updated_at: '2022-03-31T19:06:40.473Z', - version: 'WzIzNywxXQ==', - score: 0, -}); - -/** - * @deprecated Once we are confident all rules relying on side-car actions SO's have been migrated to SO references we should remove this function - */ -export const legacyGetSiemNotificationRuleDailyActionsSOResult = ( - ruleId = '123', - connectorId = '456' -): SavedObjectsFindResult => ({ - type: 'siem-detection-engine-rule-actions', - id: 'ID_OF_LEGACY_SIDECAR_DAILY_ACTIONS', - namespaces: ['default'], - attributes: { - actions: [ - { - group: 'default', - actionRef: 'action_0', - params: { - message: 'Rule {{context.rule.name}} generated {{state.signals_count}} alerts', - to: ['test@test.com'], - subject: 'Test Actions', - }, - action_type_id: '.email', - }, - ], - ruleThrottle: '1d', - alertThrottle: '1d', - }, - references: [ - { id: ruleId, type: 'alert', name: 'alert_0' }, - { id: connectorId, type: 'action', name: 'action_0' }, - ], - migrationVersion: { - 'siem-detection-engine-rule-actions': '7.11.2', - }, - coreMigrationVersion: '7.15.2', - updated_at: '2022-03-31T19:06:40.473Z', - version: 'WzIzNywxXQ==', - score: 0, -}); - -/** - * @deprecated Once we are confident all rules relying on side-car actions SO's have been migrated to SO references we should remove this function - */ -export const legacyGetSiemNotificationRuleWeeklyActionsSOResult = ( - ruleId = '123', - connectorId = '456' -): SavedObjectsFindResult => ({ - type: 'siem-detection-engine-rule-actions', - id: 'ID_OF_LEGACY_SIDECAR_WEEKLY_ACTIONS', - namespaces: ['default'], - attributes: { - actions: [ - { - group: 'default', - actionRef: 'action_0', - params: { - message: 'Rule {{context.rule.name}} generated {{state.signals_count}} alerts', - to: ['test@test.com'], - subject: 'Test Actions', - }, - action_type_id: '.email', - }, - ], - ruleThrottle: '7d', - alertThrottle: '7d', - }, - references: [ - { id: ruleId, type: 'alert', name: 'alert_0' }, - { id: connectorId, type: 'action', name: 'action_0' }, - ], - migrationVersion: { - 'siem-detection-engine-rule-actions': '7.11.2', - }, - coreMigrationVersion: '7.15.2', - updated_at: '2022-03-31T19:06:40.473Z', - version: 'WzIzNywxXQ==', - score: 0, -}); - -const getLegacyActionSOs = (ruleId = '123', connectorId = '456') => ({ - none: () => legacyGetSiemNotificationRuleNoActionsSOResult(ruleId), - rule: () => legacyGetSiemNotificationRuleEveryRunSOResult(ruleId), - hourly: () => legacyGetSiemNotificationRuleHourlyActionsSOResult(ruleId, connectorId), - daily: () => legacyGetSiemNotificationRuleDailyActionsSOResult(ruleId, connectorId), - weekly: () => legacyGetSiemNotificationRuleWeeklyActionsSOResult(ruleId, connectorId), -}); - -/** - * @deprecated Once we are confident all rules relying on side-car actions SO's have been migrated to SO references we should remove this function - */ -export const legacyGetSiemNotificationRuleActionsSOResultWithSingleHit = ( - actionTypes: Array<'none' | 'rule' | 'daily' | 'hourly' | 'weekly'>, - ruleId = '123', - connectorId = '456' -): SavedObjectsFindResponse => { - const actions = getLegacyActionSOs(ruleId, connectorId); - - return { - page: 1, - per_page: 1, - total: 1, - saved_objects: actionTypes.map((type) => actions[type]()), - }; -}; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions_legacy/index.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions_legacy/index.ts index 22d3c347eef85..fddf872583040 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions_legacy/index.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions_legacy/index.ts @@ -21,8 +21,6 @@ export { scheduleNotificationActions } from './logic/notifications/schedule_noti export { scheduleThrottledNotificationActions } from './logic/notifications/schedule_throttle_notification_actions'; export { getNotificationResultsLink } from './logic/notifications/utils'; -// eslint-disable-next-line no-restricted-imports -export { legacyGetBulkRuleActionsSavedObject } from './logic/rule_actions/legacy_get_bulk_rule_actions_saved_object'; // eslint-disable-next-line no-restricted-imports export type { LegacyRulesActionsSavedObject } from './logic/rule_actions/legacy_get_rule_actions_saved_object'; // eslint-disable-next-line no-restricted-imports diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions_legacy/logic/rule_actions/legacy_get_bulk_rule_actions_saved_object.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions_legacy/logic/rule_actions/legacy_get_bulk_rule_actions_saved_object.ts deleted file mode 100644 index 300d5307c1773..0000000000000 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions_legacy/logic/rule_actions/legacy_get_bulk_rule_actions_saved_object.ts +++ /dev/null @@ -1,84 +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 { chunk } from 'lodash'; -import type { SavedObjectsFindOptionsReference, Logger } from '@kbn/core/server'; - -import type { RuleExecutorServices } from '@kbn/alerting-plugin/server'; -// eslint-disable-next-line no-restricted-imports -import { legacyRuleActionsSavedObjectType } from './legacy_saved_object_mappings'; -// eslint-disable-next-line no-restricted-imports -import type { LegacyIRuleActionsAttributesSavedObjectAttributes } from './legacy_types'; -// eslint-disable-next-line no-restricted-imports -import { legacyGetRuleActionsFromSavedObject } from './legacy_utils'; -// eslint-disable-next-line no-restricted-imports -import type { LegacyRulesActionsSavedObject } from './legacy_get_rule_actions_saved_object'; -import { initPromisePool } from '../../../../../utils/promise_pool'; - -/** - * @deprecated Once we are confident all rules relying on side-car actions SO's have been migrated to SO references we should remove this function - */ -interface LegacyGetBulkRuleActionsSavedObject { - alertIds: string[]; - savedObjectsClient: RuleExecutorServices['savedObjectsClient']; - logger: Logger; -} - -/** - * @deprecated Once we are confident all rules relying on side-car actions SO's have been migrated to SO references we should remove this function - */ -export const legacyGetBulkRuleActionsSavedObject = async ({ - alertIds, - savedObjectsClient, - logger, -}: LegacyGetBulkRuleActionsSavedObject): Promise> => { - const references = alertIds.map((alertId) => ({ - id: alertId, - type: 'alert', - })); - const { results, errors } = await initPromisePool({ - concurrency: 1, - items: chunk(references, 1000), - executor: (referencesChunk) => - savedObjectsClient - .find({ - type: legacyRuleActionsSavedObjectType, - perPage: 10000, - hasReference: referencesChunk, - }) - .catch((error) => { - logger.error( - `Error fetching rule actions: ${error instanceof Error ? error.message : String(error)}` - ); - throw error; - }), - }); - if (errors.length) { - throw new AggregateError(errors, 'Error fetching rule actions'); - } - - const savedObjects = results.flatMap(({ result }) => result.saved_objects); - return savedObjects.reduce( - (acc: { [key: string]: LegacyRulesActionsSavedObject }, savedObject) => { - const ruleAlertId = savedObject.references.find((reference) => { - // Find the first rule alert and assume that is the one we want since we should only ever have 1. - return reference.type === 'alert'; - }); - // We check to ensure we have found a "ruleAlertId" and hopefully we have. - const ruleAlertIdKey = ruleAlertId != null ? ruleAlertId.id : undefined; - if (ruleAlertIdKey != null) { - acc[ruleAlertIdKey] = legacyGetRuleActionsFromSavedObject(savedObject, logger); - } else { - logger.error( - `Security Solution notification (Legacy) Was expecting to find a reference of type "alert" within ${savedObject.references} but did not. Skipping this notification.` - ); - } - return acc; - }, - {} - ); -}; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/bulk_actions/route.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/bulk_actions/route.test.ts index a7034123e7a96..ea49f53eacdd3 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/bulk_actions/route.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/bulk_actions/route.test.ts @@ -17,7 +17,6 @@ import { getBulkActionEditRequest, getFindResultWithSingleHit, getFindResultWithMultiHits, - getRuleMock, } from '../../../../routes/__mocks__/request_responses'; import { requestContextMock, serverMock, requestMock } from '../../../../routes/__mocks__'; import { performBulkActionRoute } from './route'; @@ -27,21 +26,10 @@ import { } from '../../../../../../../common/detection_engine/rule_management/mocks'; import { loggingSystemMock } from '@kbn/core/server/mocks'; import { readRules } from '../../../logic/crud/read_rules'; -// eslint-disable-next-line no-restricted-imports -import { legacyMigrate } from '../../../logic/rule_actions/legacy_action_migration'; -import { getQueryRuleParams } from '../../../../rule_schema/mocks'; jest.mock('../../../../../machine_learning/authz'); jest.mock('../../../logic/crud/read_rules', () => ({ readRules: jest.fn() })); -jest.mock('../../../logic/rule_actions/legacy_action_migration', () => { - const actual = jest.requireActual('../../../logic/rule_actions/legacy_action_migration'); - return { - ...actual, - legacyMigrate: jest.fn(), - }; -}); - describe('Perform bulk action route', () => { const readRulesMock = readRules as jest.Mock; let server: ReturnType; @@ -55,7 +43,6 @@ describe('Perform bulk action route', () => { logger = loggingSystemMock.createLogger(); ({ clients, context } = requestContextMock.createTools()); ml = mlServicesMock.createSetupContract(); - (legacyMigrate as jest.Mock).mockResolvedValue(getRuleMock(getQueryRuleParams())); clients.rulesClient.find.mockResolvedValue(getFindResultWithSingleHit()); performBulkActionRoute(server.router, ml, logger); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/bulk_actions/route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/bulk_actions/route.ts index aa040d3e94239..27b9111b0434d 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/bulk_actions/route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/bulk_actions/route.ts @@ -8,17 +8,12 @@ import { truncate } from 'lodash'; import moment from 'moment'; import { BadRequestError, transformError } from '@kbn/securitysolution-es-utils'; -import type { - IKibanaResponse, - KibanaResponseFactory, - Logger, - SavedObjectsClientContract, -} from '@kbn/core/server'; +import type { IKibanaResponse, KibanaResponseFactory, Logger } from '@kbn/core/server'; import type { RulesClient, BulkOperationError } from '@kbn/alerting-plugin/server'; -import type { SanitizedRule, BulkActionSkipResult } from '@kbn/alerting-plugin/common'; +import type { BulkActionSkipResult } from '@kbn/alerting-plugin/common'; import { AbortError } from '@kbn/kibana-utils-plugin/common'; -import type { RuleAlertType, RuleParams } from '../../../../rule_schema'; +import type { RuleAlertType } from '../../../../rule_schema'; import type { BulkActionsDryRunErrCode } from '../../../../../../../common/constants'; import { DETECTION_ENGINE_RULES_BULK_ACTION, @@ -52,8 +47,6 @@ import { readRules } from '../../../logic/crud/read_rules'; import { getExportByObjectIds } from '../../../logic/export/get_export_by_object_ids'; import { buildSiemResponse } from '../../../../routes/utils'; import { internalRuleToAPIResponse } from '../../../normalization/rule_converters'; -// eslint-disable-next-line no-restricted-imports -import { legacyMigrate } from '../../../logic/rule_actions/legacy_action_migration'; import { bulkEditRules } from '../../../logic/bulk_actions/bulk_edit_rules'; import type { DryRunError } from '../../../logic/bulk_actions/dry_run'; import { @@ -234,39 +227,6 @@ const fetchRulesByQueryOrIds = async ({ }; }; -/** - * Helper method to migrate any legacy actions a rule may have. If no actions or no legacy actions - * no migration is performed. - * @params rulesClient - * @params savedObjectsClient - * @params rule - rule to be migrated - * @returns The migrated rule - */ -export const migrateRuleActions = async ({ - rulesClient, - savedObjectsClient, - rule, -}: { - rulesClient: RulesClient; - savedObjectsClient: SavedObjectsClientContract; - rule: RuleAlertType; -}): Promise> => { - const migratedRule = await legacyMigrate({ - rulesClient, - savedObjectsClient, - rule, - }); - - // This should only be hit if `rule` passed into `legacyMigrate` - // is `null` or `rule.id` is null which right now, as typed, should not occur - // but catching if does, in which case something upstream would be breaking down - if (migratedRule == null) { - throw new Error(`An error occurred processing rule with id:${rule.id}`); - } - - return migratedRule; -}; - export const performBulkActionRoute = ( router: SecuritySolutionPluginRouter, ml: SetupPlugins['ml'], @@ -359,31 +319,10 @@ export const performBulkActionRoute = ( mlAuthz, }); - // migrate legacy rule actions - const migrationOutcome = await initPromisePool({ - concurrency: MAX_RULES_TO_UPDATE_IN_PARALLEL, - items: rules, - executor: async (rule) => { - // actions only get fired when rule running, so we should be fine to migrate only enabled - if (rule.enabled) { - return migrateRuleActions({ - rulesClient, - savedObjectsClient, - rule, - }); - } else { - return rule; - } - }, - abortSignal: abortController.signal, - }); - return buildBulkResponse(response, { - updated: migrationOutcome.results - .filter(({ result }) => result) - .map(({ result }) => result), + updated: rules, skipped, - errors: [...errors, ...migrationOutcome.errors], + errors, }); } @@ -413,18 +352,12 @@ export const performBulkActionRoute = ( return rule; } - const migratedRule = await migrateRuleActions({ - rulesClient, - savedObjectsClient, - rule, - }); - - if (!migratedRule.enabled) { - await rulesClient.enable({ id: migratedRule.id }); + if (!rule.enabled) { + await rulesClient.enable({ id: rule.id }); } return { - ...migratedRule, + ...rule, enabled: true, }; }, @@ -446,18 +379,12 @@ export const performBulkActionRoute = ( return rule; } - const migratedRule = await migrateRuleActions({ - rulesClient, - savedObjectsClient, - rule, - }); - - if (migratedRule.enabled) { - await rulesClient.disable({ id: migratedRule.id }); + if (rule.enabled) { + await rulesClient.disable({ id: rule.id }); } return { - ...migratedRule, + ...rule, enabled: false, }; }, @@ -478,14 +405,8 @@ export const performBulkActionRoute = ( return null; } - const migratedRule = await migrateRuleActions({ - rulesClient, - savedObjectsClient, - rule, - }); - await deleteRules({ - ruleId: migratedRule.id, + ruleId: rule.id, rulesClient, }); @@ -509,18 +430,14 @@ export const performBulkActionRoute = ( if (isDryRun) { return rule; } - const migratedRule = await migrateRuleActions({ - rulesClient, - savedObjectsClient, - rule, - }); + let shouldDuplicateExceptions = true; if (body.duplicate !== undefined) { shouldDuplicateExceptions = body.duplicate.include_exceptions; } const duplicateRuleToCreate = await duplicateRule({ - rule: migratedRule, + rule, }); const createdRule = await rulesClient.create({ diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/bulk_delete_rules/route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/bulk_delete_rules/route.ts index 7864fb0bbb3d3..f1842e0bb76d4 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/bulk_delete_rules/route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/bulk_delete_rules/route.ts @@ -30,8 +30,6 @@ import { } from '../../../../routes/utils'; import { deleteRules } from '../../../logic/crud/delete_rules'; import { readRules } from '../../../logic/crud/read_rules'; -// eslint-disable-next-line no-restricted-imports -import { legacyMigrate } from '../../../logic/rule_actions/legacy_action_migration'; import { getDeprecatedBulkEndpointHeader, logDeprecatedBulkEndpoint } from '../../deprecation'; type Config = RouteConfig; @@ -64,7 +62,6 @@ export const bulkDeleteRulesRoute = (router: SecuritySolutionPluginRouter, logge const ctx = await context.resolve(['core', 'securitySolution', 'alerting']); const rulesClient = ctx.alerting.getRulesClient(); - const savedObjectsClient = ctx.core.savedObjects.client; const rules = await Promise.all( request.body.map(async (payloadRule) => { @@ -81,21 +78,16 @@ export const bulkDeleteRulesRoute = (router: SecuritySolutionPluginRouter, logge try { const rule = await readRules({ rulesClient, id, ruleId }); - const migratedRule = await legacyMigrate({ - rulesClient, - savedObjectsClient, - rule, - }); - if (!migratedRule) { + if (!rule) { return getIdBulkError({ id, ruleId }); } await deleteRules({ - ruleId: migratedRule.id, + ruleId: rule.id, rulesClient, }); - return transformValidateBulkError(idOrRuleIdOrUnknown, migratedRule); + return transformValidateBulkError(idOrRuleIdOrUnknown, rule); } catch (err) { return transformBulkError(idOrRuleIdOrUnknown, err); } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/bulk_patch_rules/route.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/bulk_patch_rules/route.test.ts index 2082aa51815fd..802763872cbd3 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/bulk_patch_rules/route.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/bulk_patch_rules/route.test.ts @@ -23,19 +23,9 @@ import { bulkPatchRulesRoute } from './route'; import { getCreateRulesSchemaMock } from '../../../../../../../common/detection_engine/rule_schema/mocks'; import { getMlRuleParams, getQueryRuleParams } from '../../../../rule_schema/mocks'; import { loggingSystemMock } from '@kbn/core/server/mocks'; -// eslint-disable-next-line no-restricted-imports -import { legacyMigrate } from '../../../logic/rule_actions/legacy_action_migration'; jest.mock('../../../../../machine_learning/authz'); -jest.mock('../../../logic/rule_actions/legacy_action_migration', () => { - const actual = jest.requireActual('../../../logic/rule_actions/legacy_action_migration'); - return { - ...actual, - legacyMigrate: jest.fn(), - }; -}); - describe('Bulk patch rules route', () => { let server: ReturnType; let { clients, context } = requestContextMock.createTools(); @@ -50,8 +40,6 @@ describe('Bulk patch rules route', () => { clients.rulesClient.find.mockResolvedValue(getFindResultWithSingleHit()); // rule exists clients.rulesClient.update.mockResolvedValue(getRuleMock(getQueryRuleParams())); // update succeeds - (legacyMigrate as jest.Mock).mockResolvedValue(getRuleMock(getQueryRuleParams())); - bulkPatchRulesRoute(server.router, ml, logger); }); @@ -66,7 +54,6 @@ describe('Bulk patch rules route', () => { test('returns an error in the response when updating a single rule that does not exist', async () => { clients.rulesClient.find.mockResolvedValue(getEmptyFindResult()); - (legacyMigrate as jest.Mock).mockResolvedValue(null); const response = await server.inject( getPatchBulkRequest(), requestContextMock.convertContext(context) @@ -86,7 +73,6 @@ describe('Bulk patch rules route', () => { ...getFindResultWithSingleHit(), data: [getRuleMock(getMlRuleParams())], }); - (legacyMigrate as jest.Mock).mockResolvedValueOnce(getRuleMock(getMlRuleParams())); const request = requestMock.create({ method: 'patch', path: `${DETECTION_ENGINE_RULES_URL}/bulk_update`, @@ -167,8 +153,6 @@ describe('Bulk patch rules route', () => { describe('request validation', () => { test('rejects payloads with no ID', async () => { - (legacyMigrate as jest.Mock).mockResolvedValue(null); - const request = requestMock.create({ method: 'patch', path: DETECTION_ENGINE_RULES_BULK_UPDATE, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/bulk_patch_rules/route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/bulk_patch_rules/route.ts index 2aa1f46c9b9d6..39507d050987c 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/bulk_patch_rules/route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/bulk_patch_rules/route.ts @@ -24,8 +24,6 @@ import { getIdBulkError } from '../../../utils/utils'; import { transformValidateBulkError } from '../../../utils/validate'; import { patchRules } from '../../../logic/crud/patch_rules'; import { readRules } from '../../../logic/crud/read_rules'; -// eslint-disable-next-line no-restricted-imports -import { legacyMigrate } from '../../../logic/rule_actions/legacy_action_migration'; import { getDeprecatedBulkEndpointHeader, logDeprecatedBulkEndpoint } from '../../deprecation'; import { validateRuleDefaultExceptionList } from '../../../logic/exceptions/validate_rule_default_exception_list'; import { validateRulesWithDuplicatedDefaultExceptionsList } from '../../../logic/exceptions/validate_rules_with_duplicated_default_exceptions_list'; @@ -98,14 +96,8 @@ export const bulkPatchRulesRoute = ( ruleId: payloadRule.id, }); - const migratedRule = await legacyMigrate({ - rulesClient, - savedObjectsClient, - rule: existingRule, - }); - const rule = await patchRules({ - existingRule: migratedRule, + existingRule, rulesClient, nextParams: payloadRule, }); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/bulk_update_rules/route.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/bulk_update_rules/route.test.ts index 5c8433b3e87fb..54fd9671078da 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/bulk_update_rules/route.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/bulk_update_rules/route.test.ts @@ -21,19 +21,9 @@ import type { BulkError } from '../../../../routes/utils'; import { getCreateRulesSchemaMock } from '../../../../../../../common/detection_engine/rule_schema/mocks'; import { getQueryRuleParams } from '../../../../rule_schema/mocks'; import { loggingSystemMock } from '@kbn/core/server/mocks'; -// eslint-disable-next-line no-restricted-imports -import { legacyMigrate } from '../../../logic/rule_actions/legacy_action_migration'; jest.mock('../../../../../machine_learning/authz'); -jest.mock('../../../logic/rule_actions/legacy_action_migration', () => { - const actual = jest.requireActual('../../../logic/rule_actions/legacy_action_migration'); - return { - ...actual, - legacyMigrate: jest.fn(), - }; -}); - describe('Bulk update rules route', () => { let server: ReturnType; let { clients, context } = requestContextMock.createTools(); @@ -50,8 +40,6 @@ describe('Bulk update rules route', () => { clients.appClient.getSignalsIndex.mockReturnValue('.siem-signals-test-index'); - (legacyMigrate as jest.Mock).mockResolvedValue(getRuleMock(getQueryRuleParams())); - bulkUpdateRulesRoute(server.router, ml, logger); }); @@ -66,7 +54,6 @@ describe('Bulk update rules route', () => { test('returns 200 as a response when updating a single rule that does not exist', async () => { clients.rulesClient.find.mockResolvedValue(getEmptyFindResult()); - (legacyMigrate as jest.Mock).mockResolvedValue(null); const expected: BulkError[] = [ { @@ -130,8 +117,6 @@ describe('Bulk update rules route', () => { describe('request validation', () => { test('rejects payloads with no ID', async () => { - (legacyMigrate as jest.Mock).mockResolvedValue(null); - const noIdRequest = requestMock.create({ method: 'put', path: DETECTION_ENGINE_RULES_BULK_UPDATE, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/bulk_update_rules/route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/bulk_update_rules/route.ts index d186abecf7b38..9a8ce4a2cd595 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/bulk_update_rules/route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/bulk_update_rules/route.ts @@ -28,8 +28,6 @@ import { createBulkErrorObject, } from '../../../../routes/utils'; import { updateRules } from '../../../logic/crud/update_rules'; -// eslint-disable-next-line no-restricted-imports -import { legacyMigrate } from '../../../logic/rule_actions/legacy_action_migration'; import { readRules } from '../../../logic/crud/read_rules'; import { getDeprecatedBulkEndpointHeader, logDeprecatedBulkEndpoint } from '../../deprecation'; import { validateRuleDefaultExceptionList } from '../../../logic/exceptions/validate_rule_default_exception_list'; @@ -103,15 +101,9 @@ export const bulkUpdateRulesRoute = ( ruleId: payloadRule.id, }); - const migratedRule = await legacyMigrate({ - rulesClient, - savedObjectsClient, - rule: existingRule, - }); - const rule = await updateRules({ rulesClient, - existingRule: migratedRule, + existingRule, ruleUpdate: payloadRule, }); if (rule != null) { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/delete_rule/route.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/delete_rule/route.test.ts index 5f5e81a6c3960..11dd8946419f9 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/delete_rule/route.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/delete_rule/route.test.ts @@ -13,21 +13,10 @@ import { getFindResultWithSingleHit, getDeleteRequestById, getEmptySavedObjectsResponse, - getRuleMock, } from '../../../../routes/__mocks__/request_responses'; import { requestContextMock, serverMock, requestMock } from '../../../../routes/__mocks__'; import { deleteRuleRoute } from './route'; import { getQueryRuleParams } from '../../../../rule_schema/mocks'; -// eslint-disable-next-line no-restricted-imports -import { legacyMigrate } from '../../../logic/rule_actions/legacy_action_migration'; - -jest.mock('../../../logic/rule_actions/legacy_action_migration', () => { - const actual = jest.requireActual('../../../logic/rule_actions/legacy_action_migration'); - return { - ...actual, - legacyMigrate: jest.fn(), - }; -}); describe('Delete rule route', () => { let server: ReturnType; @@ -40,8 +29,6 @@ describe('Delete rule route', () => { clients.rulesClient.find.mockResolvedValue(getFindResultWithSingleHit()); clients.savedObjectsClient.find.mockResolvedValue(getEmptySavedObjectsResponse()); - (legacyMigrate as jest.Mock).mockResolvedValue(getRuleMock(getQueryRuleParams())); - deleteRuleRoute(server.router); }); @@ -67,7 +54,7 @@ describe('Delete rule route', () => { test('returns 404 when deleting a single rule that does not exist with a valid actionClient and alertClient', async () => { clients.rulesClient.find.mockResolvedValue(getEmptyFindResult()); - (legacyMigrate as jest.Mock).mockResolvedValue(null); + const response = await server.inject( getDeleteRequest(), requestContextMock.convertContext(context) diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/delete_rule/route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/delete_rule/route.ts index b4044e3dd9914..4c8a61d91543c 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/delete_rule/route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/delete_rule/route.ts @@ -19,8 +19,6 @@ import { buildSiemResponse } from '../../../../routes/utils'; import { deleteRules } from '../../../logic/crud/delete_rules'; import { readRules } from '../../../logic/crud/read_rules'; -// eslint-disable-next-line no-restricted-imports -import { legacyMigrate } from '../../../logic/rule_actions/legacy_action_migration'; import { getIdError, transform } from '../../../utils/utils'; export const deleteRuleRoute = (router: SecuritySolutionPluginRouter) => { @@ -46,16 +44,10 @@ export const deleteRuleRoute = (router: SecuritySolutionPluginRouter) => { const ctx = await context.resolve(['core', 'securitySolution', 'alerting']); const rulesClient = ctx.alerting.getRulesClient(); - const savedObjectsClient = ctx.core.savedObjects.client; const rule = await readRules({ rulesClient, id, ruleId }); - const migratedRule = await legacyMigrate({ - rulesClient, - savedObjectsClient, - rule, - }); - if (!migratedRule) { + if (!rule) { const error = getIdError({ id, ruleId }); return siemResponse.error({ body: error.message, @@ -64,11 +56,11 @@ export const deleteRuleRoute = (router: SecuritySolutionPluginRouter) => { } await deleteRules({ - ruleId: migratedRule.id, + ruleId: rule.id, rulesClient, }); - const transformed = transform(migratedRule); + const transformed = transform(rule); if (transformed == null) { return siemResponse.error({ statusCode: 500, body: 'failed to transform alert' }); } else { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/find_rules/route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/find_rules/route.ts index 8654a9dafd245..59b1842f4ee62 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/find_rules/route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/find_rules/route.ts @@ -21,9 +21,6 @@ import { buildSiemResponse } from '../../../../routes/utils'; import { buildRouteValidation } from '../../../../../../utils/build_validation/route_validation'; import { transformFindAlerts } from '../../../utils/utils'; -// eslint-disable-next-line no-restricted-imports -import { legacyGetBulkRuleActionsSavedObject } from '../../../../rule_actions_legacy'; - export const findRulesRoute = (router: SecuritySolutionPluginRouter, logger: Logger) => { router.get( { @@ -49,7 +46,6 @@ export const findRulesRoute = (router: SecuritySolutionPluginRouter, logger: Log const { query } = request; const ctx = await context.resolve(['core', 'securitySolution', 'alerting']); const rulesClient = ctx.alerting.getRulesClient(); - const savedObjectsClient = ctx.core.savedObjects.client; const rules = await findRules({ rulesClient, @@ -61,15 +57,7 @@ export const findRulesRoute = (router: SecuritySolutionPluginRouter, logger: Log fields: query.fields, }); - const ruleIds = rules.data.map((rule) => rule.id); - - const ruleActions = await legacyGetBulkRuleActionsSavedObject({ - alertIds: ruleIds, - savedObjectsClient, - logger, - }); - - const transformed = transformFindAlerts(rules, ruleActions); + const transformed = transformFindAlerts(rules); if (transformed == null) { return siemResponse.error({ statusCode: 500, body: 'Internal error transforming' }); } else { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/patch_rule/route.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/patch_rule/route.test.ts index 07ae6a84df003..f2786446056d5 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/patch_rule/route.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/patch_rule/route.test.ts @@ -22,21 +22,11 @@ import { } from '../../../../routes/__mocks__/request_responses'; import { getMlRuleParams, getQueryRuleParams } from '../../../../rule_schema/mocks'; -// eslint-disable-next-line no-restricted-imports -import { legacyMigrate } from '../../../logic/rule_actions/legacy_action_migration'; import { patchRuleRoute } from './route'; jest.mock('../../../../../machine_learning/authz'); -jest.mock('../../../logic/rule_actions/legacy_action_migration', () => { - const actual = jest.requireActual('../../../logic/rule_actions/legacy_action_migration'); - return { - ...actual, - legacyMigrate: jest.fn(), - }; -}); - describe('Patch rule route', () => { let server: ReturnType; let { clients, context } = requestContextMock.createTools(); @@ -51,8 +41,6 @@ describe('Patch rule route', () => { clients.rulesClient.find.mockResolvedValue(getFindResultWithSingleHit()); // existing rule clients.rulesClient.update.mockResolvedValue(getRuleMock(getQueryRuleParams())); // successful update - (legacyMigrate as jest.Mock).mockResolvedValue(getRuleMock(getQueryRuleParams())); - patchRuleRoute(server.router, ml); }); @@ -67,7 +55,6 @@ describe('Patch rule route', () => { test('returns 404 when updating a single rule that does not exist', async () => { clients.rulesClient.find.mockResolvedValue(getEmptyFindResult()); - (legacyMigrate as jest.Mock).mockResolvedValue(null); const response = await server.inject( getPatchRequest(), requestContextMock.convertContext(context) @@ -80,7 +67,6 @@ describe('Patch rule route', () => { }); test('returns error if requesting a non-rule', async () => { - (legacyMigrate as jest.Mock).mockResolvedValue(null); clients.rulesClient.find.mockResolvedValue(nonRuleFindResult()); const response = await server.inject( getPatchRequest(), @@ -114,7 +100,6 @@ describe('Patch rule route', () => { ...getFindResultWithSingleHit(), data: [getRuleMock(getMlRuleParams())], }); - (legacyMigrate as jest.Mock).mockResolvedValueOnce(getRuleMock(getMlRuleParams())); const request = requestMock.create({ method: 'patch', path: DETECTION_ENGINE_RULES_URL, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/patch_rule/route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/patch_rule/route.ts index a06c36a1502ea..caf9ea5ce5e17 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/patch_rule/route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/patch_rule/route.ts @@ -24,8 +24,6 @@ import { readRules } from '../../../logic/crud/read_rules'; import { patchRules } from '../../../logic/crud/patch_rules'; import { checkDefaultRuleExceptionListReferences } from '../../../logic/exceptions/check_for_default_rule_exception_list'; import { validateRuleDefaultExceptionList } from '../../../logic/exceptions/validate_rule_default_exception_list'; -// eslint-disable-next-line no-restricted-imports -import { legacyMigrate } from '../../../logic/rule_actions/legacy_action_migration'; import { getIdError } from '../../../utils/utils'; import { transformValidate } from '../../../utils/validate'; @@ -83,15 +81,9 @@ export const patchRuleRoute = (router: SecuritySolutionPluginRouter, ml: SetupPl ruleId: params.id, }); - const migratedRule = await legacyMigrate({ - rulesClient, - savedObjectsClient, - rule: existingRule, - }); - const rule = await patchRules({ rulesClient, - existingRule: migratedRule, + existingRule, nextParams: params, }); if (rule != null && rule.enabled != null && rule.name != null) { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/read_rule/route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/read_rule/route.ts index eca9636842074..64e1daec632ba 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/read_rule/route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/read_rule/route.ts @@ -20,8 +20,6 @@ import { buildSiemResponse } from '../../../../routes/utils'; import { getIdError, transform } from '../../../utils/utils'; import { readRules } from '../../../logic/crud/read_rules'; -// eslint-disable-next-line no-restricted-imports -import { legacyGetRuleActionsSavedObject } from '../../../../rule_actions_legacy'; export const readRuleRoute = (router: SecuritySolutionPluginRouter, logger: Logger) => { router.get( @@ -45,7 +43,6 @@ export const readRuleRoute = (router: SecuritySolutionPluginRouter, logger: Logg try { const rulesClient = (await context.alerting).getRulesClient(); - const savedObjectsClient = (await context.core).savedObjects.client; const rule = await readRules({ id, @@ -53,13 +50,7 @@ export const readRuleRoute = (router: SecuritySolutionPluginRouter, logger: Logg ruleId, }); if (rule != null) { - const legacyRuleActions = await legacyGetRuleActionsSavedObject({ - savedObjectsClient, - ruleAlertId: rule.id, - logger, - }); - - const transformed = transform(rule, legacyRuleActions); + const transformed = transform(rule); if (transformed == null) { return siemResponse.error({ statusCode: 500, body: 'Internal error transforming' }); } else { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/update_rule/route.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/update_rule/route.test.ts index 4502e08fb950d..627786be080d9 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/update_rule/route.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/update_rule/route.test.ts @@ -20,19 +20,9 @@ import { DETECTION_ENGINE_RULES_URL } from '../../../../../../../common/constant import { updateRuleRoute } from './route'; import { getUpdateRulesSchemaMock } from '../../../../../../../common/detection_engine/rule_schema/mocks'; import { getQueryRuleParams } from '../../../../rule_schema/mocks'; -// eslint-disable-next-line no-restricted-imports -import { legacyMigrate } from '../../../logic/rule_actions/legacy_action_migration'; jest.mock('../../../../../machine_learning/authz'); -jest.mock('../../../logic/rule_actions/legacy_action_migration', () => { - const actual = jest.requireActual('../../../logic/rule_actions/legacy_action_migration'); - return { - ...actual, - legacyMigrate: jest.fn(), - }; -}); - describe('Update rule route', () => { let server: ReturnType; let { clients, context } = requestContextMock.createTools(); @@ -48,8 +38,6 @@ describe('Update rule route', () => { clients.rulesClient.update.mockResolvedValue(getRuleMock(getQueryRuleParams())); // successful update clients.appClient.getSignalsIndex.mockReturnValue('.siem-signals-test-index'); - (legacyMigrate as jest.Mock).mockResolvedValue(getRuleMock(getQueryRuleParams())); - updateRuleRoute(server.router, ml); }); @@ -64,7 +52,7 @@ describe('Update rule route', () => { test('returns 404 when updating a single rule that does not exist', async () => { clients.rulesClient.find.mockResolvedValue(getEmptyFindResult()); - (legacyMigrate as jest.Mock).mockResolvedValue(null); + const response = await server.inject( getUpdateRequest(), requestContextMock.convertContext(context) @@ -78,7 +66,6 @@ describe('Update rule route', () => { }); test('returns error when updating non-rule', async () => { - (legacyMigrate as jest.Mock).mockResolvedValue(null); clients.rulesClient.find.mockResolvedValue(nonRuleFindResult()); const response = await server.inject( getUpdateRequest(), diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/update_rule/route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/update_rule/route.ts index 1f171b15df6e7..ceef8ab9d6e02 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/update_rule/route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/update_rule/route.ts @@ -20,8 +20,7 @@ import { getIdError } from '../../../utils/utils'; import { transformValidate } from '../../../utils/validate'; import { updateRules } from '../../../logic/crud/update_rules'; import { buildRouteValidation } from '../../../../../../utils/build_validation/route_validation'; -// eslint-disable-next-line no-restricted-imports -import { legacyMigrate } from '../../../logic/rule_actions/legacy_action_migration'; + import { readRules } from '../../../logic/crud/read_rules'; import { checkDefaultRuleExceptionListReferences } from '../../../logic/exceptions/check_for_default_rule_exception_list'; import { validateRuleDefaultExceptionList } from '../../../logic/exceptions/validate_rule_default_exception_list'; @@ -72,14 +71,9 @@ export const updateRuleRoute = (router: SecuritySolutionPluginRouter, ml: SetupP id: request.body.id, }); - const migratedRule = await legacyMigrate({ - rulesClient, - savedObjectsClient, - rule: existingRule, - }); const rule = await updateRules({ rulesClient, - existingRule: migratedRule, + existingRule, ruleUpdate: request.body, }); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/index.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/index.ts index f2a694c8df046..7e379651b2faf 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/index.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/index.ts @@ -7,10 +7,6 @@ export * from './api/register_routes'; -// TODO: https://github.com/elastic/kibana/pull/142950 -// eslint-disable-next-line no-restricted-imports -export { legacyMigrate } from './logic/rule_actions/legacy_action_migration'; - // TODO: https://github.com/elastic/kibana/pull/142950 // TODO: Revisit and consider moving to the rule_schema subdomain export { @@ -18,3 +14,5 @@ export { typeSpecificCamelToSnake, convertCreateAPIToInternalSchema, } from './normalization/rule_converters'; + +export { transformFromAlertThrottle, transformToNotifyWhen } from './normalization/rule_actions'; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/export/get_export_all.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/export/get_export_all.ts index 8346359156508..0962e9bf2f313 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/export/get_export_all.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/export/get_export_all.ts @@ -17,9 +17,6 @@ import { transformAlertsToRules, transformRuleToExportableFormat } from '../../u import { getRuleExceptionsForExport } from './get_export_rule_exceptions'; import { getRuleActionConnectorsForExport } from './get_export_rule_action_connectors'; -// eslint-disable-next-line no-restricted-imports -import { legacyGetBulkRuleActionsSavedObject } from '../../../rule_actions_legacy'; - export const getExportAll = async ( rulesClient: RulesClient, exceptionsClient: ExceptionListClient | undefined, @@ -35,15 +32,8 @@ export const getExportAll = async ( actionConnectors: string; }> => { const ruleAlertTypes = await getNonPackagedRules({ rulesClient }); - const alertIds = ruleAlertTypes.map((rule) => rule.id); + const rules = transformAlertsToRules(ruleAlertTypes); - // Gather actions - const legacyActions = await legacyGetBulkRuleActionsSavedObject({ - alertIds, - savedObjectsClient, - logger, - }); - const rules = transformAlertsToRules(ruleAlertTypes, legacyActions); const exportRules = rules.map((r) => transformRuleToExportableFormat(r)); // Gather exceptions diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/export/get_export_by_object_ids.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/export/get_export_by_object_ids.ts index 9672312fc3ed8..492fe15b8c228 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/export/get_export_by_object_ids.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/export/get_export_by_object_ids.ts @@ -22,8 +22,6 @@ import { transformRuleToExportableFormat } from '../../utils/utils'; import { getRuleExceptionsForExport } from './get_export_rule_exceptions'; import { getRuleActionConnectorsForExport } from './get_export_rule_action_connectors'; -// eslint-disable-next-line no-restricted-imports -import { legacyGetBulkRuleActionsSavedObject } from '../../../rule_actions_legacy'; import { internalRuleToAPIResponse } from '../../normalization/rule_converters'; import type { RuleResponse } from '../../../../../../common/detection_engine/rule_schema'; @@ -123,12 +121,6 @@ export const getRulesFromObjects = async ( sortField: undefined, sortOrder: undefined, }); - const alertIds = rules.data.map((rule) => rule.id); - const legacyActions = await legacyGetBulkRuleActionsSavedObject({ - alertIds, - savedObjectsClient, - logger, - }); const alertsAndErrors = objects.map(({ rule_id: ruleId }) => { const matchingRule = rules.data.find((rule) => rule.params.ruleId === ruleId); @@ -139,9 +131,7 @@ export const getRulesFromObjects = async ( ) { return { statusCode: 200, - rule: transformRuleToExportableFormat( - internalRuleToAPIResponse(matchingRule, legacyActions[matchingRule.id]) - ), + rule: transformRuleToExportableFormat(internalRuleToAPIResponse(matchingRule)), }; } else { return { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/import/import_rules_utils.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/import/import_rules_utils.ts index 89ec68ff79bb1..d38b48bf9181f 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/import/import_rules_utils.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/import/import_rules_utils.ts @@ -16,8 +16,6 @@ import type { RulesClient } from '@kbn/alerting-plugin/server'; import type { ExceptionListClient } from '@kbn/lists-plugin/server'; import type { RuleToImport } from '../../../../../../common/detection_engine/rule_management'; -// eslint-disable-next-line no-restricted-imports -import { legacyMigrate } from '../rule_actions/legacy_action_migration'; import type { ImportRuleResponse } from '../../../routes/utils'; import { createBulkErrorObject } from '../../../routes/utils'; import { createRules } from '../crud/create_rules'; @@ -128,14 +126,9 @@ export const importRules = async ({ status_code: 200, }); } else if (rule != null && overwriteRules) { - const migratedRule = await legacyMigrate({ - rulesClient, - savedObjectsClient, - rule, - }); await patchRules({ rulesClient, - existingRule: migratedRule, + existingRule: rule, nextParams: { ...parsedRule, exceptions_list: [...exceptions], diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/rule_actions/legacy_action_migration.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/rule_actions/legacy_action_migration.test.ts deleted file mode 100644 index f8332d5c3f3b1..0000000000000 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/rule_actions/legacy_action_migration.test.ts +++ /dev/null @@ -1,486 +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 { requestContextMock } from '../../../routes/__mocks__'; -import { - getEmptyFindResult, - legacyGetDailyNotificationResult, - legacyGetHourlyNotificationResult, - legacyGetSiemNotificationRuleActionsSOResultWithSingleHit, - legacyGetWeeklyNotificationResult, -} from '../../../routes/__mocks__/request_responses'; - -import type { RuleAlertType } from '../../../rule_schema'; - -// eslint-disable-next-line no-restricted-imports -import { legacyMigrate, getUpdatedActionsParams } from './legacy_action_migration'; - -const getRuleLegacyActions = (): RuleAlertType => - ({ - id: '123', - notifyWhen: 'onThrottleInterval', - name: 'Simple Rule Query', - tags: ['__internal_rule_id:ruleId', '__internal_immutable:false'], - alertTypeId: 'siem.queryRule', - consumer: 'siem', - enabled: true, - throttle: '1h', - apiKeyOwner: 'elastic', - createdBy: 'elastic', - updatedBy: 'elastic', - muteAll: false, - mutedInstanceIds: [], - monitoring: { execution: { history: [], calculated_metrics: { success_ratio: 0 } } }, - mapped_params: { risk_score: 1, severity: '60-high' }, - schedule: { interval: '5m' }, - actions: [], - params: { - author: [], - description: 'Simple Rule Query', - ruleId: 'ruleId', - falsePositives: [], - from: 'now-6m', - immutable: false, - outputIndex: '.siem-signals-default', - maxSignals: 100, - riskScore: 1, - riskScoreMapping: [], - severity: 'high', - severityMapping: [], - threat: [], - to: 'now', - references: [], - version: 1, - exceptionsList: [], - type: 'query', - language: 'kuery', - index: ['auditbeat-*'], - query: 'user.name: root or user.name: admin', - }, - snoozeEndTime: null, - updatedAt: '2022-03-31T21:47:25.695Z', - createdAt: '2022-03-31T21:47:16.379Z', - scheduledTaskId: '21bb9b60-b13c-11ec-99d0-asdfasdfasf', - executionStatus: { - status: 'pending', - lastExecutionDate: '2022-03-31T21:47:25.695Z', - lastDuration: 0, - }, - } as unknown as RuleAlertType); - -describe('Legacy rule action migration logic', () => { - describe('legacyMigrate', () => { - const ruleId = '123'; - const connectorId = '456'; - const { clients } = requestContextMock.createTools(); - - beforeEach(() => { - jest.resetAllMocks(); - }); - - test('it does no cleanup or migration if no legacy reminants found', async () => { - clients.rulesClient.find.mockResolvedValueOnce(getEmptyFindResult()); - clients.savedObjectsClient.find.mockResolvedValueOnce({ - page: 0, - per_page: 0, - total: 0, - saved_objects: [], - }); - - const rule = { - ...getRuleLegacyActions(), - id: ruleId, - actions: [], - throttle: null, - notifyWhen: 'onActiveAlert', - muteAll: true, - } as RuleAlertType; - - const migratedRule = await legacyMigrate({ - rulesClient: clients.rulesClient, - savedObjectsClient: clients.savedObjectsClient, - rule, - }); - - expect(clients.rulesClient.delete).not.toHaveBeenCalled(); - expect(clients.savedObjectsClient.delete).not.toHaveBeenCalled(); - expect(migratedRule).toEqual(rule); - }); - - // Even if a rule is created with no actions pre 7.16, a - // siem-detection-engine-rule-actions SO is still created - test('it migrates a rule with no actions', async () => { - // siem.notifications is not created for a rule with no actions - clients.rulesClient.find.mockResolvedValueOnce(getEmptyFindResult()); - // siem-detection-engine-rule-actions SO is still created - clients.savedObjectsClient.find.mockResolvedValueOnce( - legacyGetSiemNotificationRuleActionsSOResultWithSingleHit(['none'], ruleId, connectorId) - ); - - const migratedRule = await legacyMigrate({ - rulesClient: clients.rulesClient, - savedObjectsClient: clients.savedObjectsClient, - rule: { - ...getRuleLegacyActions(), - id: ruleId, - actions: [], - throttle: null, - notifyWhen: 'onActiveAlert', - muteAll: true, - }, - }); - - expect(clients.rulesClient.delete).not.toHaveBeenCalled(); - expect(clients.savedObjectsClient.delete).toHaveBeenCalledWith( - 'siem-detection-engine-rule-actions', - 'ID_OF_LEGACY_SIDECAR_NO_ACTIONS' - ); - expect(migratedRule?.actions).toEqual([]); - expect(migratedRule?.throttle).toBeNull(); - expect(migratedRule?.muteAll).toBeTruthy(); - expect(migratedRule?.notifyWhen).toEqual('onActiveAlert'); - }); - - test('it migrates a rule with every rule run action', async () => { - // siem.notifications is not created for a rule with actions run every rule run - clients.rulesClient.find.mockResolvedValueOnce(getEmptyFindResult()); - // siem-detection-engine-rule-actions SO is still created - clients.savedObjectsClient.find.mockResolvedValueOnce( - legacyGetSiemNotificationRuleActionsSOResultWithSingleHit(['rule'], ruleId, connectorId) - ); - - const migratedRule = await legacyMigrate({ - rulesClient: clients.rulesClient, - savedObjectsClient: clients.savedObjectsClient, - rule: { - ...getRuleLegacyActions(), - id: ruleId, - actions: [ - { - actionTypeId: '.email', - params: { - subject: 'Test Actions', - to: ['test@test.com'], - message: 'Rule {{context.rule.name}} generated {{state.signals_count}} alerts', - }, - id: connectorId, - group: 'default', - }, - ], - throttle: null, - notifyWhen: 'onActiveAlert', - muteAll: false, - }, - }); - - expect(clients.rulesClient.delete).not.toHaveBeenCalled(); - expect(clients.savedObjectsClient.delete).toHaveBeenCalledWith( - 'siem-detection-engine-rule-actions', - 'ID_OF_LEGACY_SIDECAR_RULE_RUN_ACTIONS' - ); - expect(migratedRule?.actions).toEqual([ - { - id: connectorId, - actionTypeId: '.email', - group: 'default', - params: { - message: 'Rule {{context.rule.name}} generated {{state.signals_count}} alerts', - subject: 'Test Actions', - to: ['test@test.com'], - }, - }, - ]); - expect(migratedRule?.notifyWhen).toEqual('onActiveAlert'); - expect(migratedRule?.throttle).toBeNull(); - expect(migratedRule?.muteAll).toBeFalsy(); - }); - - test('it migrates a rule with daily legacy actions', async () => { - // siem.notifications is not created for a rule with no actions - clients.rulesClient.find.mockResolvedValueOnce({ - page: 1, - perPage: 1, - total: 1, - data: [legacyGetDailyNotificationResult(connectorId, ruleId)], - }); - // siem-detection-engine-rule-actions SO is still created - clients.savedObjectsClient.find.mockResolvedValueOnce( - legacyGetSiemNotificationRuleActionsSOResultWithSingleHit(['daily'], ruleId, connectorId) - ); - - const migratedRule = await legacyMigrate({ - rulesClient: clients.rulesClient, - savedObjectsClient: clients.savedObjectsClient, - rule: { - ...getRuleLegacyActions(), - id: ruleId, - actions: [], - throttle: null, - notifyWhen: 'onActiveAlert', - }, - }); - - expect(clients.rulesClient.delete).toHaveBeenCalledWith({ id: '456' }); - expect(clients.savedObjectsClient.delete).toHaveBeenCalledWith( - 'siem-detection-engine-rule-actions', - 'ID_OF_LEGACY_SIDECAR_DAILY_ACTIONS' - ); - expect(migratedRule?.actions).toEqual([ - { - actionTypeId: '.email', - group: 'default', - id: connectorId, - params: { - message: 'Rule {{context.rule.name}} generated {{state.signals_count}} alerts', - to: ['test@test.com'], - subject: 'Test Actions', - }, - }, - ]); - expect(migratedRule?.throttle).toEqual('1d'); - expect(migratedRule?.notifyWhen).toEqual('onThrottleInterval'); - expect(migratedRule?.muteAll).toBeFalsy(); - }); - - test('it migrates a rule with hourly legacy actions', async () => { - // siem.notifications is not created for a rule with no actions - clients.rulesClient.find.mockResolvedValueOnce({ - page: 1, - perPage: 1, - total: 1, - data: [legacyGetHourlyNotificationResult(connectorId, ruleId)], - }); - // siem-detection-engine-rule-actions SO is still created - clients.savedObjectsClient.find.mockResolvedValueOnce( - legacyGetSiemNotificationRuleActionsSOResultWithSingleHit(['hourly'], ruleId, connectorId) - ); - - const migratedRule = await legacyMigrate({ - rulesClient: clients.rulesClient, - savedObjectsClient: clients.savedObjectsClient, - rule: { - ...getRuleLegacyActions(), - id: ruleId, - actions: [], - throttle: null, - notifyWhen: 'onActiveAlert', - }, - }); - - expect(clients.rulesClient.delete).toHaveBeenCalledWith({ id: '456' }); - expect(clients.savedObjectsClient.delete).toHaveBeenCalledWith( - 'siem-detection-engine-rule-actions', - 'ID_OF_LEGACY_SIDECAR_HOURLY_ACTIONS' - ); - expect(migratedRule?.actions).toEqual([ - { - actionTypeId: '.email', - group: 'default', - id: connectorId, - params: { - message: 'Rule {{context.rule.name}} generated {{state.signals_count}} alerts', - to: ['test@test.com'], - subject: 'Test Actions', - }, - }, - ]); - expect(migratedRule?.throttle).toEqual('1h'); - expect(migratedRule?.notifyWhen).toEqual('onThrottleInterval'); - expect(migratedRule?.muteAll).toBeFalsy(); - }); - - test('it migrates a rule with weekly legacy actions', async () => { - // siem.notifications is not created for a rule with no actions - clients.rulesClient.find.mockResolvedValueOnce({ - page: 1, - perPage: 1, - total: 1, - data: [legacyGetWeeklyNotificationResult(connectorId, ruleId)], - }); - // siem-detection-engine-rule-actions SO is still created - clients.savedObjectsClient.find.mockResolvedValueOnce( - legacyGetSiemNotificationRuleActionsSOResultWithSingleHit(['weekly'], ruleId, connectorId) - ); - - const migratedRule = await legacyMigrate({ - rulesClient: clients.rulesClient, - savedObjectsClient: clients.savedObjectsClient, - rule: { - ...getRuleLegacyActions(), - id: ruleId, - actions: [], - throttle: null, - notifyWhen: 'onActiveAlert', - }, - }); - - expect(clients.rulesClient.delete).toHaveBeenCalledWith({ id: '456' }); - expect(clients.savedObjectsClient.delete).toHaveBeenCalledWith( - 'siem-detection-engine-rule-actions', - 'ID_OF_LEGACY_SIDECAR_WEEKLY_ACTIONS' - ); - expect(migratedRule?.actions).toEqual([ - { - actionTypeId: '.email', - group: 'default', - id: connectorId, - params: { - message: 'Rule {{context.rule.name}} generated {{state.signals_count}} alerts', - to: ['test@test.com'], - subject: 'Test Actions', - }, - }, - ]); - expect(migratedRule?.throttle).toEqual('7d'); - expect(migratedRule?.notifyWhen).toEqual('onThrottleInterval'); - expect(migratedRule?.muteAll).toBeFalsy(); - }); - }); - - describe('getUpdatedActionsParams', () => { - it('updates one action', () => { - const { id, ...rule } = { - ...getRuleLegacyActions(), - id: '123', - actions: [], - throttle: null, - notifyWhen: 'onActiveAlert', - } as RuleAlertType; - - expect( - getUpdatedActionsParams({ - rule: { - ...rule, - id, - }, - ruleThrottle: '1h', - actions: [ - { - actionRef: 'action_0', - group: 'default', - params: { - message: 'Rule {{context.rule.name}} generated {{state.signals_count}} alerts', - to: ['a@a.com'], - subject: 'Test Actions', - }, - action_type_id: '.email', - }, - ], - references: [ - { - id: '61ec7a40-b076-11ec-bb3f-1f063f8e06cf', - type: 'alert', - name: 'alert_0', - }, - { - id: '1234', - type: 'action', - name: 'action_0', - }, - ], - }) - ).toEqual({ - ...rule, - actions: [ - { - actionTypeId: '.email', - group: 'default', - id: '1234', - params: { - message: 'Rule {{context.rule.name}} generated {{state.signals_count}} alerts', - subject: 'Test Actions', - to: ['a@a.com'], - }, - }, - ], - throttle: '1h', - notifyWhen: 'onThrottleInterval', - }); - }); - - it('updates multiple actions', () => { - const { id, ...rule } = { - ...getRuleLegacyActions(), - id: '123', - actions: [], - throttle: null, - notifyWhen: 'onActiveAlert', - } as RuleAlertType; - - expect( - getUpdatedActionsParams({ - rule: { - ...rule, - id, - }, - ruleThrottle: '1h', - actions: [ - { - actionRef: 'action_0', - group: 'default', - params: { - message: 'Rule {{context.rule.name}} generated {{state.signals_count}} alerts', - to: ['test@test.com'], - subject: 'Rule email', - }, - action_type_id: '.email', - }, - { - actionRef: 'action_1', - group: 'default', - params: { - message: 'Rule {{context.rule.name}} generated {{state.signals_count}} alerts', - }, - action_type_id: '.slack', - }, - ], - references: [ - { - id: '064e3160-b076-11ec-bb3f-1f063f8e06cf', - type: 'alert', - name: 'alert_0', - }, - { - id: 'c95cb100-b075-11ec-bb3f-1f063f8e06cf', - type: 'action', - name: 'action_0', - }, - { - id: '207fa0e0-c04e-11ec-8a52-4fb92379525a', - type: 'action', - name: 'action_1', - }, - ], - }) - ).toEqual({ - ...rule, - actions: [ - { - actionTypeId: '.email', - group: 'default', - id: 'c95cb100-b075-11ec-bb3f-1f063f8e06cf', - params: { - message: 'Rule {{context.rule.name}} generated {{state.signals_count}} alerts', - subject: 'Rule email', - to: ['test@test.com'], - }, - }, - { - actionTypeId: '.slack', - group: 'default', - id: '207fa0e0-c04e-11ec-8a52-4fb92379525a', - params: { - message: 'Rule {{context.rule.name}} generated {{state.signals_count}} alerts', - }, - }, - ], - throttle: '1h', - notifyWhen: 'onThrottleInterval', - }); - }); - }); -}); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/rule_actions/legacy_action_migration.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/rule_actions/legacy_action_migration.ts deleted file mode 100644 index 8c69edc33064b..0000000000000 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/rule_actions/legacy_action_migration.ts +++ /dev/null @@ -1,178 +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 { isEmpty } from 'lodash/fp'; - -import type { RuleAction } from '@kbn/alerting-plugin/common'; -import type { RulesClient } from '@kbn/alerting-plugin/server'; -import type { SavedObjectReference, SavedObjectsClientContract } from '@kbn/core/server'; - -import { withSecuritySpan } from '../../../../../utils/with_security_span'; -import type { RuleAlertType } from '../../../rule_schema'; - -// eslint-disable-next-line no-restricted-imports -import { legacyRuleActionsSavedObjectType } from '../../../rule_actions_legacy'; -// eslint-disable-next-line no-restricted-imports -import type { - LegacyIRuleActionsAttributes, - LegacyRuleAlertSavedObjectAction, -} from '../../../rule_actions_legacy'; - -import { transformToAlertThrottle, transformToNotifyWhen } from '../../normalization/rule_actions'; - -export interface LegacyMigrateParams { - rulesClient: RulesClient; - savedObjectsClient: SavedObjectsClientContract; - rule: RuleAlertType | null | undefined; -} - -/** - * Determines if rule needs to be migrated from legacy actions - * and returns necessary pieces for the updated rule - */ -export const legacyMigrate = async ({ - rulesClient, - savedObjectsClient, - rule, -}: LegacyMigrateParams): Promise => - withSecuritySpan('legacyMigrate', async () => { - if (rule == null || rule.id == null) { - return rule; - } - /** - * On update / patch I'm going to take the actions as they are, better off taking rules client.find (siem.notification) result - * and putting that into the actions array of the rule, then set the rules onThrottle property, notifyWhen and throttle from null -> actual value (1hr etc..) - * Then use the rules client to delete the siem.notification - * Then with the legacy Rule Actions saved object type, just delete it. - */ - // find it using the references array, not params.ruleAlertId - const [siemNotification, legacyRuleActionsSO] = await Promise.all([ - rulesClient.find({ - options: { - filter: 'alert.attributes.alertTypeId:(siem.notifications)', - hasReference: { - type: 'alert', - id: rule.id, - }, - }, - }), - savedObjectsClient.find({ - type: legacyRuleActionsSavedObjectType, - hasReference: { - type: 'alert', - id: rule.id, - }, - }), - ]); - - const siemNotificationsExist = siemNotification != null && siemNotification.data.length > 0; - const legacyRuleNotificationSOsExist = - legacyRuleActionsSO != null && legacyRuleActionsSO.saved_objects.length > 0; - - // Assumption: if no legacy sidecar SO or notification rule types exist - // that reference the rule in question, assume rule actions are not legacy - if (!siemNotificationsExist && !legacyRuleNotificationSOsExist) { - return rule; - } - // If the legacy notification rule type ("siem.notification") exist, - // migration and cleanup are needed - if (siemNotificationsExist) { - await rulesClient.delete({ id: siemNotification.data[0].id }); - } - // If legacy notification sidecar ("siem-detection-engine-rule-actions") - // exist, migration and cleanup are needed - if (legacyRuleNotificationSOsExist) { - // Delete the legacy sidecar SO - await savedObjectsClient.delete( - legacyRuleActionsSavedObjectType, - legacyRuleActionsSO.saved_objects[0].id - ); - - // If "siem-detection-engine-rule-actions" notes that `ruleThrottle` is - // "no_actions" or "rule", rule has no actions or rule is set to run - // action on every rule run. In these cases, sidecar deletion is the only - // cleanup needed and updates to the "throttle" and "notifyWhen". "siem.notification" are - // not created for these action types - if ( - legacyRuleActionsSO.saved_objects[0].attributes.ruleThrottle === 'no_actions' || - legacyRuleActionsSO.saved_objects[0].attributes.ruleThrottle === 'rule' - ) { - return rule; - } - - // Use "legacyRuleActionsSO" instead of "siemNotification" as "siemNotification" is not created - // until a rule is run and added to task manager. That means that if by chance a user has a rule - // with actions which they have yet to enable, the actions would be lost. Instead, - // "legacyRuleActionsSO" is created on rule creation (pre 7.15) and we can rely on it to be there - const migratedRule = getUpdatedActionsParams({ - rule, - ruleThrottle: legacyRuleActionsSO.saved_objects[0].attributes.ruleThrottle, - actions: legacyRuleActionsSO.saved_objects[0].attributes.actions, - references: legacyRuleActionsSO.saved_objects[0].references, - }); - - await rulesClient.update({ - id: rule.id, - data: migratedRule, - }); - - return { id: rule.id, ...migratedRule }; - } - }); - -/** - * Translate legacy action sidecar action to rule action - */ -export const getUpdatedActionsParams = ({ - rule, - ruleThrottle, - actions, - references, -}: { - rule: RuleAlertType; - ruleThrottle: string | null; - actions: LegacyRuleAlertSavedObjectAction[]; - references: SavedObjectReference[]; -}): Omit => { - const { id, ...restOfRule } = rule; - - const actionReference = references.reduce>( - (acc, reference) => { - acc[reference.name] = reference; - return acc; - }, - {} - ); - - if (isEmpty(actionReference)) { - throw new Error( - `An error occurred migrating legacy action for rule with id:${id}. Connector reference id not found.` - ); - } - // If rule has an action on any other interval (other than on every - // rule run), need to move the action info from the sidecar/legacy action - // into the rule itself - return { - ...restOfRule, - actions: actions.reduce((acc, action) => { - const { actionRef, action_type_id: actionTypeId, ...resOfAction } = action; - if (!actionReference[actionRef]) { - return acc; - } - return [ - ...acc, - { - ...resOfAction, - id: actionReference[actionRef].id, - actionTypeId, - }, - ]; - }, []), - throttle: transformToAlertThrottle(ruleThrottle), - notifyWhen: transformToNotifyWhen(ruleThrottle), - }; -}; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/normalization/rule_actions.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/normalization/rule_actions.test.ts index 419e84d4d8373..d0d5edad970f0 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/normalization/rule_actions.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/normalization/rule_actions.test.ts @@ -5,20 +5,14 @@ * 2.0. */ -import type { RuleAction } from '@kbn/alerting-plugin/common'; - import { NOTIFICATION_THROTTLE_NO_ACTIONS, NOTIFICATION_THROTTLE_RULE, } from '../../../../../common/constants'; -import type { RuleResponse } from '../../../../../common/detection_engine/rule_schema'; -// eslint-disable-next-line no-restricted-imports -import type { LegacyRuleActions } from '../../rule_actions_legacy'; import type { RuleAlertType } from '../../rule_schema'; import { - transformActions, transformFromAlertThrottle, transformToAlertThrottle, transformToNotifyWhen, @@ -88,307 +82,73 @@ describe('Rule actions normalization', () => { describe('transformFromAlertThrottle', () => { test('muteAll returns "NOTIFICATION_THROTTLE_NO_ACTIONS" even with notifyWhen set and actions has an array element', () => { expect( - transformFromAlertThrottle( - { - muteAll: true, - notifyWhen: 'onActiveAlert', - actions: [ - { - group: 'group', - id: 'id-123', - actionTypeId: 'id-456', - params: {}, - }, - ], - } as RuleAlertType, - undefined - ) + transformFromAlertThrottle({ + muteAll: true, + notifyWhen: 'onActiveAlert', + actions: [ + { + group: 'group', + id: 'id-123', + actionTypeId: 'id-456', + params: {}, + }, + ], + } as RuleAlertType) ).toEqual(NOTIFICATION_THROTTLE_NO_ACTIONS); }); test('returns "NOTIFICATION_THROTTLE_NO_ACTIONS" if actions is an empty array and we do not have a throttle', () => { expect( - transformFromAlertThrottle( - { - muteAll: false, - notifyWhen: 'onActiveAlert', - actions: [], - } as unknown as RuleAlertType, - undefined - ) + transformFromAlertThrottle({ + muteAll: false, + notifyWhen: 'onActiveAlert', + actions: [], + } as unknown as RuleAlertType) ).toEqual(NOTIFICATION_THROTTLE_NO_ACTIONS); }); test('returns "NOTIFICATION_THROTTLE_NO_ACTIONS" if actions is an empty array and we have a throttle', () => { expect( - transformFromAlertThrottle( - { - muteAll: false, - notifyWhen: 'onThrottleInterval', - actions: [], - throttle: '1d', - } as unknown as RuleAlertType, - undefined - ) + transformFromAlertThrottle({ + muteAll: false, + notifyWhen: 'onThrottleInterval', + actions: [], + throttle: '1d', + } as unknown as RuleAlertType) ).toEqual(NOTIFICATION_THROTTLE_NO_ACTIONS); }); test('it returns "NOTIFICATION_THROTTLE_RULE" if "notifyWhen" is set, muteAll is false and we have an actions array', () => { expect( - transformFromAlertThrottle( - { - muteAll: false, - notifyWhen: 'onActiveAlert', - actions: [ - { - group: 'group', - id: 'id-123', - actionTypeId: 'id-456', - params: {}, - }, - ], - } as RuleAlertType, - undefined - ) + transformFromAlertThrottle({ + muteAll: false, + notifyWhen: 'onActiveAlert', + actions: [ + { + group: 'group', + id: 'id-123', + actionTypeId: 'id-456', + params: {}, + }, + ], + } as RuleAlertType) ).toEqual(NOTIFICATION_THROTTLE_RULE); }); test('it returns "NOTIFICATION_THROTTLE_RULE" if "notifyWhen" and "throttle" are not set, but we have an actions array', () => { expect( - transformFromAlertThrottle( - { - muteAll: false, - actions: [ - { - group: 'group', - id: 'id-123', - actionTypeId: 'id-456', - params: {}, - }, - ], - } as RuleAlertType, - undefined - ) - ).toEqual(NOTIFICATION_THROTTLE_RULE); - }); - - test('it will use the "rule" and not the "legacyRuleActions" if the rule and actions is defined', () => { - const legacyRuleActions: LegacyRuleActions = { - id: 'id_1', - ruleThrottle: '', - alertThrottle: '', - actions: [ - { - id: 'id_2', - group: 'group', - action_type_id: 'actionTypeId', - params: {}, - }, - ], - }; - - expect( - transformFromAlertThrottle( - { - muteAll: true, - notifyWhen: 'onActiveAlert', - actions: [ - { - group: 'group', - id: 'id-123', - actionTypeId: 'id-456', - params: {}, - }, - ], - } as RuleAlertType, - legacyRuleActions - ) - ).toEqual(NOTIFICATION_THROTTLE_NO_ACTIONS); - }); - - test('it will use the "legacyRuleActions" and not the "rule" if the rule actions is an empty array', () => { - const legacyRuleActions: LegacyRuleActions = { - id: 'id_1', - ruleThrottle: NOTIFICATION_THROTTLE_RULE, - alertThrottle: null, - actions: [ - { - id: 'id_2', - group: 'group', - action_type_id: 'actionTypeId', - params: {}, - }, - ], - }; - - expect( - transformFromAlertThrottle( - { - muteAll: true, - notifyWhen: 'onActiveAlert', - actions: [], - } as unknown as RuleAlertType, - legacyRuleActions - ) - ).toEqual(NOTIFICATION_THROTTLE_RULE); - }); - - test('it will use the "legacyRuleActions" and not the "rule" if the rule actions is a null', () => { - const legacyRuleActions: LegacyRuleActions = { - id: 'id_1', - ruleThrottle: NOTIFICATION_THROTTLE_RULE, - alertThrottle: null, - actions: [ - { - id: 'id_2', - group: 'group', - action_type_id: 'actionTypeId', - params: {}, - }, - ], - }; - - expect( - transformFromAlertThrottle( - { - muteAll: true, - notifyWhen: 'onActiveAlert', - actions: null, - } as unknown as RuleAlertType, - legacyRuleActions - ) + transformFromAlertThrottle({ + muteAll: false, + actions: [ + { + group: 'group', + id: 'id-123', + actionTypeId: 'id-456', + params: {}, + }, + ], + } as RuleAlertType) ).toEqual(NOTIFICATION_THROTTLE_RULE); }); }); - - describe('transformActions', () => { - test('It transforms two alert actions', () => { - const alertAction: RuleAction[] = [ - { - id: 'id_1', - group: 'group', - actionTypeId: 'actionTypeId', - params: {}, - }, - { - id: 'id_2', - group: 'group', - actionTypeId: 'actionTypeId', - params: {}, - }, - ]; - - const transformed = transformActions(alertAction, null); - expect(transformed).toEqual([ - { - id: 'id_1', - group: 'group', - action_type_id: 'actionTypeId', - params: {}, - }, - { - id: 'id_2', - group: 'group', - action_type_id: 'actionTypeId', - params: {}, - }, - ]); - }); - - test('It transforms two alert actions but not a legacyRuleActions if this is also passed in', () => { - const alertAction: RuleAction[] = [ - { - id: 'id_1', - group: 'group', - actionTypeId: 'actionTypeId', - params: {}, - }, - { - id: 'id_2', - group: 'group', - actionTypeId: 'actionTypeId', - params: {}, - }, - ]; - const legacyRuleActions: LegacyRuleActions = { - id: 'id_1', - ruleThrottle: '', - alertThrottle: '', - actions: [ - { - id: 'id_2', - group: 'group', - action_type_id: 'actionTypeId', - params: {}, - }, - ], - }; - const transformed = transformActions(alertAction, legacyRuleActions); - expect(transformed).toEqual([ - { - id: 'id_1', - group: 'group', - action_type_id: 'actionTypeId', - params: {}, - }, - { - id: 'id_2', - group: 'group', - action_type_id: 'actionTypeId', - params: {}, - }, - ]); - }); - - test('It will transform the legacyRuleActions if the alertAction is an empty array', () => { - const alertAction: RuleAction[] = []; - const legacyRuleActions: LegacyRuleActions = { - id: 'id_1', - ruleThrottle: '', - alertThrottle: '', - actions: [ - { - id: 'id_2', - group: 'group', - action_type_id: 'actionTypeId', - params: {}, - }, - ], - }; - const transformed = transformActions(alertAction, legacyRuleActions); - expect(transformed).toEqual([ - { - id: 'id_2', - group: 'group', - action_type_id: 'actionTypeId', - params: {}, - }, - ]); - }); - - test('It will transform the legacyRuleActions if the alertAction is undefined', () => { - const legacyRuleActions: LegacyRuleActions = { - id: 'id_1', - ruleThrottle: '', - alertThrottle: '', - actions: [ - { - id: 'id_2', - group: 'group', - action_type_id: 'actionTypeId', - params: {}, - }, - ], - }; - const transformed = transformActions(undefined, legacyRuleActions); - expect(transformed).toEqual([ - { - id: 'id_2', - group: 'group', - action_type_id: 'actionTypeId', - params: {}, - }, - ]); - }); - }); }); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/normalization/rule_actions.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/normalization/rule_actions.ts index bc2cb970ba7f3..51dbe9d80f986 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/normalization/rule_actions.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/normalization/rule_actions.ts @@ -5,17 +5,13 @@ * 2.0. */ -import type { RuleAction, RuleNotifyWhenType } from '@kbn/alerting-plugin/common'; +import type { RuleNotifyWhenType } from '@kbn/alerting-plugin/common'; import { NOTIFICATION_THROTTLE_NO_ACTIONS, NOTIFICATION_THROTTLE_RULE, } from '../../../../../common/constants'; -import type { RuleResponse } from '../../../../../common/detection_engine/rule_schema'; -import { transformAlertToRuleAction } from '../../../../../common/detection_engine/transform_actions'; -// eslint-disable-next-line no-restricted-imports -import type { LegacyRuleActions } from '../../rule_actions_legacy'; import type { RuleAlertType } from '../../rule_schema'; /** @@ -60,28 +56,18 @@ export const transformToAlertThrottle = (throttle: string | null | undefined): s * on it to which should not be typical but possible due to the split nature of the API's, this will prefer the * usage of the non-legacy version. Eventually the "legacyRuleActions" should be removed. * @param throttle The throttle from a "alerting" Saved Object (SO) - * @param legacyRuleActions Legacy "side car" rule actions that if it detects it being passed it in will transform using it. * @returns The "security_solution" throttle */ -export const transformFromAlertThrottle = ( - rule: RuleAlertType, - legacyRuleActions: LegacyRuleActions | null | undefined -): string => { - if (legacyRuleActions == null || (rule.actions != null && rule.actions.length > 0)) { - if (rule.muteAll || rule.actions.length === 0) { - return NOTIFICATION_THROTTLE_NO_ACTIONS; - } else if (rule.notifyWhen == null) { - return transformFromFirstActionThrottle(rule); - } else if (rule.notifyWhen === 'onActiveAlert') { - return NOTIFICATION_THROTTLE_RULE; - } else if (rule.throttle == null) { - return NOTIFICATION_THROTTLE_NO_ACTIONS; - } else { - return rule.throttle; - } - } else { - return legacyRuleActions.ruleThrottle; +export const transformFromAlertThrottle = (rule: RuleAlertType): string => { + if (rule.muteAll || rule.actions.length === 0) { + return NOTIFICATION_THROTTLE_NO_ACTIONS; + } else if (rule.notifyWhen == null) { + return transformFromFirstActionThrottle(rule); + } else if (rule.notifyWhen === 'onActiveAlert') { + return NOTIFICATION_THROTTLE_RULE; } + + return rule.throttle ?? NOTIFICATION_THROTTLE_NO_ACTIONS; }; function transformFromFirstActionThrottle(rule: RuleAlertType) { @@ -90,25 +76,3 @@ function transformFromFirstActionThrottle(rule: RuleAlertType) { return NOTIFICATION_THROTTLE_RULE; return frequency.throttle; } - -/** - * Given a set of actions from an "alerting" Saved Object (SO) this will transform it into a "security_solution" alert action. - * If this detects any legacy rule actions it will transform it. If both are sent in which is not typical but possible due to - * the split nature of the API's this will prefer the usage of the non-legacy version. Eventually the "legacyRuleActions" should - * be removed. - * @param alertAction The alert action form a "alerting" Saved Object (SO). - * @param legacyRuleActions Legacy "side car" rule actions that if it detects it being passed it in will transform using it. - * @returns The actions of the RuleResponse - */ -export const transformActions = ( - alertAction: RuleAction[] | undefined, - legacyRuleActions: LegacyRuleActions | null | undefined -): RuleResponse['actions'] => { - if (alertAction != null && alertAction.length !== 0) { - return alertAction.map((action) => transformAlertToRuleAction(action)); - } else if (legacyRuleActions != null) { - return legacyRuleActions.actions; - } else { - return []; - } -}; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/normalization/rule_converters.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/normalization/rule_converters.ts index 618514dfbab4a..7296e90bf73e7 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/normalization/rule_converters.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/normalization/rule_converters.ts @@ -42,6 +42,7 @@ import { transformAlertToRuleResponseAction, transformRuleToAlertAction, transformRuleToAlertResponseAction, + transformAlertToRuleAction, } from '../../../../../common/detection_engine/transform_actions'; import { @@ -51,8 +52,6 @@ import { import { assertUnreachable } from '../../../../../common/utility_types'; -// eslint-disable-next-line no-restricted-imports -import type { LegacyRuleActions } from '../../rule_actions_legacy'; import type { InternalRuleCreate, RuleParams, @@ -75,7 +74,6 @@ import type { NewTermsSpecificRuleParams, } from '../../rule_schema'; import { - transformActions, transformFromAlertThrottle, transformToAlertThrottle, transformToNotifyWhen, @@ -646,8 +644,7 @@ export const commonParamsCamelToSnake = (params: BaseRuleParams) => { }; export const internalRuleToAPIResponse = ( - rule: SanitizedRule | ResolvedSanitizedRule, - legacyRuleActions?: LegacyRuleActions | null + rule: SanitizedRule | ResolvedSanitizedRule ): RuleResponse => { const executionSummary = createRuleExecutionSummary(rule); @@ -675,8 +672,8 @@ export const internalRuleToAPIResponse = ( // Type specific security solution rule params ...typeSpecificCamelToSnake(rule.params), // Actions - throttle: transformFromAlertThrottle(rule, legacyRuleActions), - actions: transformActions(rule.actions, legacyRuleActions), + throttle: transformFromAlertThrottle(rule), + actions: rule.actions.map(transformAlertToRuleAction), // Execution summary execution_summary: executionSummary ?? undefined, }; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/utils/utils.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/utils/utils.test.ts index 91cdfc0e8a37b..8e82965f2b5f9 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/utils/utils.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/utils/utils.test.ts @@ -35,12 +35,6 @@ import { createBulkErrorObject } from '../../routes/utils'; import type { RuleAlertType } from '../../rule_schema'; import { getMlRuleParams, getQueryRuleParams, getThreatRuleParams } from '../../rule_schema/mocks'; -// eslint-disable-next-line no-restricted-imports -import type { - LegacyRuleAlertAction, - LegacyRulesActionsSavedObject, -} from '../../rule_actions_legacy'; - import { createRulesAndExceptionsStreamFromNdJson } from '../logic/import/create_rules_stream_from_ndjson'; import type { RuleExceptionsPromiseFromStreams } from '../logic/import/import_rules_utils'; import { internalRuleToAPIResponse } from '../normalization/rule_converters'; @@ -277,41 +271,17 @@ describe('utils', () => { describe('transformFindAlerts', () => { test('outputs empty data set when data set is empty correct', () => { - const output = transformFindAlerts({ data: [], page: 1, perPage: 0, total: 0 }, {}); + const output = transformFindAlerts({ data: [], page: 1, perPage: 0, total: 0 }); expect(output).toEqual({ data: [], page: 1, perPage: 0, total: 0 }); }); test('outputs 200 if the data is of type siem alert', () => { - const output = transformFindAlerts( - { - page: 1, - perPage: 0, - total: 0, - data: [getRuleMock(getQueryRuleParams())], - }, - {} - ); - const expected = getOutputRuleAlertForRest(); - expect(output).toEqual({ + const output = transformFindAlerts({ page: 1, perPage: 0, total: 0, - data: [expected], + data: [getRuleMock(getQueryRuleParams())], }); - }); - - test('outputs 200 if the data is of type siem alert and has undefined for the legacyRuleActions', () => { - const output = transformFindAlerts( - { - page: 1, - perPage: 0, - total: 0, - data: [getRuleMock(getQueryRuleParams())], - }, - { - '123': undefined, - } - ); const expected = getOutputRuleAlertForRest(); expect(output).toEqual({ page: 1, @@ -320,58 +290,18 @@ describe('utils', () => { data: [expected], }); }); - - test('outputs 200 if the data is of type siem alert and has a legacy rule action', () => { - const actions: LegacyRuleAlertAction[] = [ - { - id: '456', - params: {}, - group: '', - action_type_id: 'action_123', - }, - ]; - - const legacyRuleActions: Record = { - [getRuleMock(getQueryRuleParams()).id]: { - id: '123', - actions, - alertThrottle: '1h', - ruleThrottle: '1h', - }, - }; - const output = transformFindAlerts( - { - page: 1, - perPage: 0, - total: 0, - data: [getRuleMock(getQueryRuleParams())], - }, - legacyRuleActions - ); - const expected = { - ...getOutputRuleAlertForRest(), - throttle: '1h', - actions, - }; - expect(output).toEqual({ - page: 1, - perPage: 0, - total: 0, - data: [expected], - }); - }); }); describe('transform', () => { test('outputs 200 if the data is of type siem alert', () => { - const output = transform(getRuleMock(getQueryRuleParams()), undefined); + const output = transform(getRuleMock(getQueryRuleParams())); const expected = getOutputRuleAlertForRest(); expect(output).toEqual(expected); }); test('returns 500 if the data is not of type siem alert', () => { const unsafeCast = { data: [{ random: 1 }] } as unknown as PartialRule; - const output = transform(unsafeCast, undefined); + const output = transform(unsafeCast); expect(output).toBeNull(); }); }); @@ -462,12 +392,12 @@ describe('utils', () => { describe('transformAlertsToRules', () => { test('given an empty array returns an empty array', () => { - expect(transformAlertsToRules([], {})).toEqual([]); + expect(transformAlertsToRules([])).toEqual([]); }); test('given single alert will return the alert transformed', () => { const result1 = getRuleMock(getQueryRuleParams()); - const transformed = transformAlertsToRules([result1], {}); + const transformed = transformAlertsToRules([result1]); const expected = getOutputRuleAlertForRest(); expect(transformed).toEqual([expected]); }); @@ -478,7 +408,7 @@ describe('utils', () => { result2.id = 'some other id'; result2.params.ruleId = 'some other id'; - const transformed = transformAlertsToRules([result1, result2], {}); + const transformed = transformAlertsToRules([result1, result2]); const expected1 = getOutputRuleAlertForRest(); const expected2 = getOutputRuleAlertForRest(); expected2.id = 'some other id'; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/utils/utils.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/utils/utils.ts index 3f65a49795d75..9d455e0fad519 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/utils/utils.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/utils/utils.ts @@ -21,8 +21,6 @@ import type { AlertSuppressionCamel, } from '../../../../../common/detection_engine/rule_schema'; -// eslint-disable-next-line no-restricted-imports -import type { LegacyRulesActionsSavedObject } from '../../rule_actions_legacy'; import type { RuleAlertType, RuleParams } from '../../rule_schema'; import { isAlertType } from '../../rule_schema'; import type { BulkError, OutputError } from '../../routes/utils'; @@ -91,11 +89,8 @@ export const getIdBulkError = ({ } }; -export const transformAlertsToRules = ( - rules: RuleAlertType[], - legacyRuleActions: Record -): RuleResponse[] => { - return rules.map((rule) => internalRuleToAPIResponse(rule, legacyRuleActions[rule.id])); +export const transformAlertsToRules = (rules: RuleAlertType[]): RuleResponse[] => { + return rules.map((rule) => internalRuleToAPIResponse(rule)); }; /** @@ -116,8 +111,7 @@ export const transformRuleToExportableFormat = ( }; export const transformFindAlerts = ( - ruleFindResults: FindResult, - legacyRuleActions: Record + ruleFindResults: FindResult ): { page: number; perPage: number; @@ -129,17 +123,14 @@ export const transformFindAlerts = ( perPage: ruleFindResults.perPage, total: ruleFindResults.total, data: ruleFindResults.data.map((rule) => { - return internalRuleToAPIResponse(rule, legacyRuleActions[rule.id]); + return internalRuleToAPIResponse(rule); }), }; }; -export const transform = ( - rule: PartialRule, - legacyRuleActions?: LegacyRulesActionsSavedObject | null -): RuleResponse | null => { +export const transform = (rule: PartialRule): RuleResponse | null => { if (isAlertType(rule)) { - return internalRuleToAPIResponse(rule, legacyRuleActions); + return internalRuleToAPIResponse(rule); } return null; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/utils/validate.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/utils/validate.test.ts index 71ccb07d03cb3..bae75363ff49a 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/utils/validate.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/utils/validate.test.ts @@ -84,7 +84,7 @@ describe('validate', () => { describe('transformValidate', () => { test('it should do a validation correctly of a partial alert', () => { const ruleAlert = getRuleMock(getQueryRuleParams()); - const [validated, errors] = transformValidate(ruleAlert, null); + const [validated, errors] = transformValidate(ruleAlert); expect(validated).toEqual(ruleOutput()); expect(errors).toEqual(null); }); @@ -93,7 +93,7 @@ describe('validate', () => { const ruleAlert = getRuleMock(getQueryRuleParams()); // @ts-expect-error delete ruleAlert.name; - const [validated, errors] = transformValidate(ruleAlert, null); + const [validated, errors] = transformValidate(ruleAlert); expect(validated).toEqual(null); expect(errors).toEqual('Invalid value "undefined" supplied to "name"'); }); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/utils/validate.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/utils/validate.ts index 5ae9efb4501d5..f7bf2f7413a46 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/utils/validate.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/utils/validate.ts @@ -14,15 +14,12 @@ import { isAlertType } from '../../rule_schema'; import type { BulkError } from '../../routes/utils'; import { createBulkErrorObject } from '../../routes/utils'; import { transform } from './utils'; -// eslint-disable-next-line no-restricted-imports -import type { LegacyRulesActionsSavedObject } from '../../rule_actions_legacy'; import { internalRuleToAPIResponse } from '../normalization/rule_converters'; export const transformValidate = ( - rule: PartialRule, - legacyRuleActions?: LegacyRulesActionsSavedObject | null + rule: PartialRule ): [RuleResponse | null, string | null] => { - const transformed = transform(rule, legacyRuleActions); + const transformed = transform(rule); if (transformed == null) { return [null, 'Internal error transforming']; } else { diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/group1/delete_rules.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/group1/delete_rules.ts index 2c39672b0956c..a394bef16cd22 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/group1/delete_rules.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/group1/delete_rules.ts @@ -24,12 +24,14 @@ import { getWebHookAction, removeServerGeneratedProperties, removeServerGeneratedPropertiesIncludingRuleId, + getLegacyActionSO, } from '../../utils'; // 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'); describe('delete_rules', () => { describe('deleting rules', () => { @@ -195,6 +197,13 @@ export default ({ getService }: FtrProviderContext): void => { // Add a legacy rule action to the body of the rule await createLegacyRuleAction(supertest, createRuleBody.id, hookAction.id); + // check for legacy sidecar action + const sidecarActionsResults = await getLegacyActionSO(es); + expect(sidecarActionsResults.hits.hits.length).to.eql(1); + expect(sidecarActionsResults.hits.hits[0]?._source?.references[0].id).to.eql( + createRuleBody.id + ); + await supertest .delete(`${DETECTION_ENGINE_RULES_URL}?id=${createRuleBody.id}`) .set('kbn-xsrf', 'true') @@ -216,6 +225,10 @@ export default ({ getService }: FtrProviderContext): void => { // Expect that we have exactly 0 legacy rules after the deletion expect(bodyAfterDelete.total).to.eql(0); + + // legacy sidecar action should be gone + const sidecarActionsPostResults = await getLegacyActionSO(es); + expect(sidecarActionsPostResults.hits.hits.length).to.eql(0); }); }); }); diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/group1/delete_rules_bulk.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/group1/delete_rules_bulk.ts index 8b06e70a5fd63..fa83ceb9a19b1 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/group1/delete_rules_bulk.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/group1/delete_rules_bulk.ts @@ -24,12 +24,14 @@ import { getWebHookAction, removeServerGeneratedProperties, removeServerGeneratedPropertiesIncludingRuleId, + getLegacyActionSO, } from '../../utils'; // 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'); describe('delete_rules_bulk', () => { describe('deprecations', () => { @@ -387,6 +389,13 @@ export default ({ getService }: FtrProviderContext): void => { // Add a legacy rule action to the body of the rule await createLegacyRuleAction(supertest, createRuleBody.id, hookAction.id); + // check for legacy sidecar action + const sidecarActionsResults = await getLegacyActionSO(es); + expect(sidecarActionsResults.hits.hits.length).to.eql(1); + expect(sidecarActionsResults.hits.hits[0]?._source?.references[0].id).to.eql( + createRuleBody.id + ); + // bulk delete the rule await supertest .delete(DETECTION_ENGINE_RULES_BULK_DELETE) @@ -410,6 +419,10 @@ export default ({ getService }: FtrProviderContext): void => { // Expect that we have exactly 0 legacy rules after the deletion expect(bodyAfterDelete.total).to.eql(0); + + // legacy sidecar action should be gone + const sidecarActionsPostResults = await getLegacyActionSO(es); + expect(sidecarActionsPostResults.hits.hits.length).to.eql(0); }); }); }); diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/import_rules.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/import_rules.ts index 7672153845e0a..3e00904c66b93 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/import_rules.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/import_rules.ts @@ -29,6 +29,9 @@ import { getWebHookAction, removeServerGeneratedProperties, ruleToNdjson, + createLegacyRuleAction, + getLegacyActionSO, + createRule, } from '../../utils'; import { deleteAllExceptions } from '../../../lists_api_integration/utils'; import { createUserAndRole, deleteUserAndRole } from '../../../common/services/security_solution'; @@ -178,6 +181,7 @@ export default ({ getService }: FtrProviderContext): void => { const log = getService('log'); const esArchiver = getService('esArchiver'); const supertestWithoutAuth = getService('supertestWithoutAuth'); + const es = getService('es'); describe('import_rules', () => { describe('importing rules with different roles', () => { @@ -718,6 +722,45 @@ export default ({ getService }: FtrProviderContext): void => { expect(bodyToCompare).to.eql(ruleOutput); }); + it('should migrate legacy actions in existing rule if overwrite is set to true', async () => { + const simpleRule = getSimpleRule('rule-1'); + + const [connector, createdRule] = await Promise.all([ + supertest + .post(`/api/actions/connector`) + .set('kbn-xsrf', 'foo') + .send({ + name: 'My action', + connector_type_id: '.slack', + secrets: { + webhookUrl: 'http://localhost:1234', + }, + }), + createRule(supertest, log, simpleRule), + ]); + await createLegacyRuleAction(supertest, createdRule.id, connector.body.id); + + // check for legacy sidecar action + const sidecarActionsResults = await getLegacyActionSO(es); + expect(sidecarActionsResults.hits.hits.length).to.eql(1); + expect(sidecarActionsResults.hits.hits[0]?._source?.references[0].id).to.eql( + createdRule.id + ); + + simpleRule.name = 'some other name'; + const ndjson = ruleToNdjson(simpleRule); + + await supertest + .post(`${DETECTION_ENGINE_RULES_URL}/_import?overwrite=true`) + .set('kbn-xsrf', 'true') + .attach('file', ndjson, 'rules.ndjson') + .expect(200); + + // legacy sidecar action should be gone + const sidecarActionsPostResults = await getLegacyActionSO(es); + expect(sidecarActionsPostResults.hits.hits.length).to.eql(0); + }); + 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 () => { await supertest .post(`${DETECTION_ENGINE_RULES_URL}/_import`) diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/legacy_actions_migrations.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/legacy_actions_migrations.ts index ac187485c4509..6ba7871b1dbf5 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/legacy_actions_migrations.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/legacy_actions_migrations.ts @@ -75,8 +75,8 @@ export default ({ getService }: FtrProviderContext) => { expect(sidecarActionsSOAfterMigration.hits.hits.length).to.eql(0); expect(ruleSO?.alert.actions).to.eql([]); - expect(ruleSO?.alert.throttle).to.eql(null); - expect(ruleSO?.alert.notifyWhen).to.eql('onActiveAlert'); + expect(ruleSO?.alert.throttle).to.eql('no_actions'); + expect(ruleSO?.alert.notifyWhen).to.eql('onThrottleInterval'); }); it('migrates legacy actions for rule with action run on every run', async () => { @@ -123,8 +123,19 @@ export default ({ getService }: FtrProviderContext) => { }, uuid: ruleSO?.alert.actions[0].uuid, }, + { + actionRef: 'action_1', + actionTypeId: '.email', + group: 'default', + params: { + message: 'Rule {{context.rule.name}} generated {{state.signals_count}} alerts', + subject: 'Test Actions', + to: ['test@test.com'], + }, + uuid: ruleSO?.alert.actions[1].uuid, + }, ]); - expect(ruleSO?.alert.throttle).to.eql(null); + expect(ruleSO?.alert.throttle).to.eql('rule'); expect(ruleSO?.alert.notifyWhen).to.eql('onActiveAlert'); expect(ruleSO?.references).to.eql([ { @@ -132,6 +143,11 @@ export default ({ getService }: FtrProviderContext) => { name: 'action_0', type: 'action', }, + { + id: 'c95cb100-b075-11ec-bb3f-1f063f8e06cf', + name: 'action_1', + type: 'action', + }, ]); }); diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/patch_rules.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/patch_rules.ts index 1fcd785820240..98b64726bbaca 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/patch_rules.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/patch_rules.ts @@ -23,12 +23,14 @@ import { createRule, getSimpleMlRule, createLegacyRuleAction, + getLegacyActionSO, } from '../../utils'; // eslint-disable-next-line import/no-default-export export default ({ getService }: FtrProviderContext) => { const supertest = getService('supertest'); const log = getService('log'); + const es = getService('es'); describe('patch_rules', () => { describe('patch rules', () => { @@ -351,6 +353,11 @@ export default ({ getService }: FtrProviderContext) => { ]); await createLegacyRuleAction(supertest, rule.id, connector.body.id); + // check for legacy sidecar action + const sidecarActionsResults = await getLegacyActionSO(es); + expect(sidecarActionsResults.hits.hits.length).to.eql(1); + expect(sidecarActionsResults.hits.hits[0]?._source?.references[0].id).to.eql(rule.id); + // patch disable the rule const patchResponse = await supertest .patch(DETECTION_ENGINE_RULES_URL) @@ -373,8 +380,12 @@ export default ({ getService }: FtrProviderContext) => { }, ]; outputRule.throttle = '1h'; - outputRule.revision = 2; // Expected revision is 2 as call to `createLegacyRuleAction()` does two separate rules updates for `notifyWhen` & `actions` field + outputRule.revision = 1; expect(bodyToCompare).to.eql(outputRule); + + // legacy sidecar action should be gone + const sidecarActionsPostResults = await getLegacyActionSO(es); + expect(sidecarActionsPostResults.hits.hits.length).to.eql(0); }); it('should give a 404 if it is given a fake id', async () => { diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/patch_rules_bulk.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/patch_rules_bulk.ts index e7437f91bc69b..1a26b17bca42e 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/patch_rules_bulk.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/patch_rules_bulk.ts @@ -22,12 +22,14 @@ import { removeServerGeneratedPropertiesIncludingRuleId, createRule, createLegacyRuleAction, + getLegacyActionSO, } from '../../utils'; // eslint-disable-next-line import/no-default-export export default ({ getService }: FtrProviderContext) => { const supertest = getService('supertest'); const log = getService('log'); + const es = getService('es'); describe('patch_rules_bulk', () => { describe('deprecations', () => { @@ -169,6 +171,14 @@ export default ({ getService }: FtrProviderContext) => { createLegacyRuleAction(supertest, rule1.id, connector.body.id), createLegacyRuleAction(supertest, rule2.id, connector.body.id), ]); + + // check for legacy sidecar action + const sidecarActionsResults = await getLegacyActionSO(es); + expect(sidecarActionsResults.hits.hits.length).to.eql(2); + expect( + sidecarActionsResults.hits.hits.map((hit) => hit?._source?.references[0].id).sort() + ).to.eql([rule1.id, rule2.id].sort()); + // patch a simple rule's name const { body } = await supertest .patch(DETECTION_ENGINE_RULES_BULK_UPDATE) @@ -179,6 +189,10 @@ export default ({ getService }: FtrProviderContext) => { ]) .expect(200); + // legacy sidecar action should be gone + const sidecarActionsPostResults = await getLegacyActionSO(es); + expect(sidecarActionsPostResults.hits.hits.length).to.eql(0); + // @ts-expect-error body.forEach((response) => { const bodyToCompare = removeServerGeneratedProperties(response); @@ -196,7 +210,7 @@ export default ({ getService }: FtrProviderContext) => { }, ]; outputRule.throttle = '1h'; - outputRule.revision = 2; // Expected revision is 2 as call to `createLegacyRuleAction()` does two separate rules updates for `notifyWhen` & `actions` field + outputRule.revision = 1; expect(bodyToCompare).to.eql(outputRule); }); }); diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/perform_bulk_action.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/perform_bulk_action.ts index 3d39b62355732..c77ac5f142f92 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/perform_bulk_action.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/perform_bulk_action.ts @@ -33,6 +33,7 @@ import { getWebHookAction, installMockPrebuiltRules, removeServerGeneratedProperties, + waitForRuleSuccess, } from '../../utils'; // eslint-disable-next-line import/no-default-export @@ -40,6 +41,7 @@ export default ({ getService }: FtrProviderContext): void => { const supertest = getService('supertest'); const es = getService('es'); const log = getService('log'); + const esArchiver = getService('esArchiver'); const postBulkAction = () => supertest.post(DETECTION_ENGINE_RULES_BULK_ACTION).set('kbn-xsrf', 'true'); @@ -72,11 +74,13 @@ export default ({ getService }: FtrProviderContext): void => { describe('perform_bulk_action', () => { beforeEach(async () => { await createSignalsIndex(supertest, log); + await esArchiver.load('x-pack/test/functional/es_archives/auditbeat/hosts'); }); afterEach(async () => { await deleteSignalsIndex(supertest, log); await deleteAllRules(supertest, log); + await esArchiver.load('x-pack/test/functional/es_archives/auditbeat/hosts'); }); it('should export rules', async () => { @@ -329,6 +333,8 @@ export default ({ getService }: FtrProviderContext): void => { uuid: ruleBody.actions[0].uuid, }, ]); + // we want to ensure rule is executing successfully, to prevent any AAD issues related to partial update of rule SO + await waitForRuleSuccess({ id: rule1.id, supertest, log }); }); it('should disable rules', async () => { @@ -428,7 +434,7 @@ export default ({ getService }: FtrProviderContext): void => { expect(rulesResponse.total).to.eql(2); }); - it('should duplicate rule with a legacy action and migrate new rules action', async () => { + it('should duplicate rule with a legacy action', async () => { const ruleId = 'ruleId'; const [connector, ruleToDuplicate] = await Promise.all([ supertest @@ -465,10 +471,6 @@ export default ({ getService }: FtrProviderContext): void => { // Check that the duplicated rule is returned with the response expect(body.attributes.results.created[0].name).to.eql(`${ruleToDuplicate.name} [Duplicate]`); - // legacy sidecar action should be gone - const sidecarActionsPostResults = await getLegacyActionSO(es); - expect(sidecarActionsPostResults.hits.hits.length).to.eql(0); - // Check that the updates have been persisted const { body: rulesResponse } = await supertest .get(`${DETECTION_ENGINE_RULES_URL}/_find`) @@ -478,6 +480,7 @@ export default ({ getService }: FtrProviderContext): void => { expect(rulesResponse.total).to.eql(2); rulesResponse.data.forEach((rule: RuleResponse) => { + const uuid = rule.actions[0].uuid; expect(rule.actions).to.eql([ { action_type_id: '.slack', @@ -487,7 +490,7 @@ export default ({ getService }: FtrProviderContext): void => { message: 'Hourly\nRule {{context.rule.name}} generated {{state.signals_count}} alerts', }, - uuid: rule.actions[0].uuid, + ...(uuid ? { uuid } : {}), }, ]); }); @@ -1091,7 +1094,6 @@ export default ({ getService }: FtrProviderContext): void => { }, ], }); - expect(setTagsBody.attributes.summary).to.eql({ failed: 0, skipped: 0, @@ -1515,6 +1517,76 @@ export default ({ getService }: FtrProviderContext): void => { expect(readRule.actions).to.eql([]); }); + + it('should migrate legacy actions on edit when actions edited', async () => { + const ruleId = 'ruleId'; + const [connector, createdRule] = await Promise.all([ + supertest + .post(`/api/actions/connector`) + .set('kbn-xsrf', 'foo') + .send({ + name: 'My action', + connector_type_id: '.slack', + secrets: { + webhookUrl: 'http://localhost:1234', + }, + }), + createRule(supertest, log, getSimpleRule(ruleId, true)), + ]); + // create a new connector + const webHookConnector = await createWebHookConnector(); + + await createLegacyRuleAction(supertest, createdRule.id, connector.body.id); + + // check for legacy sidecar action + const sidecarActionsResults = await getLegacyActionSO(es); + expect(sidecarActionsResults.hits.hits.length).to.eql(1); + expect(sidecarActionsResults.hits.hits[0]?._source?.references[0].id).to.eql( + createdRule.id + ); + + const { body } = await postBulkAction() + .send({ + ids: [createdRule.id], + action: BulkActionType.edit, + [BulkActionType.edit]: [ + { + type: BulkActionEditType.set_rule_actions, + value: { + throttle: '1h', + actions: [ + { + ...webHookActionMock, + id: webHookConnector.id, + }, + ], + }, + }, + ], + }) + .expect(200); + + const expectedRuleActions = [ + { + ...webHookActionMock, + id: webHookConnector.id, + action_type_id: '.webhook', + uuid: body.attributes.results.updated[0].actions[0].uuid, + }, + ]; + + // Check that the updated rule is returned with the response + expect(body.attributes.results.updated[0].actions).to.eql(expectedRuleActions); + + // Check that the updates have been persisted + const { body: readRule } = await fetchRule(ruleId).expect(200); + + expect(readRule.actions).to.eql(expectedRuleActions); + + // Sidecar should be removed + const sidecarActionsPostResults = await getLegacyActionSO(es); + expect(sidecarActionsPostResults.hits.hits.length).to.eql(0); + }); }); describe('add_rule_actions', () => { diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/update_rules.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/update_rules.ts index f48562b991b62..8396f72a9bb08 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/update_rules.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/update_rules.ts @@ -27,12 +27,14 @@ import { getSimpleRule, createLegacyRuleAction, getThresholdRuleForSignalTesting, + getLegacyActionSO, } from '../../utils'; // eslint-disable-next-line import/no-default-export export default ({ getService }: FtrProviderContext) => { const supertest = getService('supertest'); const log = getService('log'); + const es = getService('es'); describe('update_rules', () => { describe('update rules', () => { @@ -207,6 +209,13 @@ export default ({ getService }: FtrProviderContext) => { ]); await createLegacyRuleAction(supertest, createRuleBody.id, connector.body.id); + // check for legacy sidecar action + const sidecarActionsResults = await getLegacyActionSO(es); + expect(sidecarActionsResults.hits.hits.length).to.eql(1); + expect(sidecarActionsResults.hits.hits[0]?._source?.references[0].id).to.eql( + createRuleBody.id + ); + const action1 = { group: 'default', id: connector.body.id, @@ -233,7 +242,7 @@ export default ({ getService }: FtrProviderContext) => { const outputRule = getSimpleRuleOutputWithoutRuleId(); outputRule.name = 'some other name'; - outputRule.revision = 2; // Migration of action results in additional revision increment (change to `notifyWhen`), so expected revision is 2 + outputRule.revision = 1; outputRule.actions = [ { action_type_id: '.slack', @@ -249,6 +258,10 @@ export default ({ getService }: FtrProviderContext) => { outputRule.throttle = '1d'; expect(bodyToCompare).to.eql(outputRule); + + // legacy sidecar action should be gone + const sidecarActionsPostResults = await getLegacyActionSO(es); + expect(sidecarActionsPostResults.hits.hits.length).to.eql(0); }); it('should update a single rule property of name using the auto-generated id', async () => { diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/update_rules_bulk.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/update_rules_bulk.ts index 8c32cf71874a6..0c8dcacd0ebbf 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/update_rules_bulk.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/update_rules_bulk.ts @@ -24,12 +24,14 @@ import { createRule, getSimpleRule, createLegacyRuleAction, + getLegacyActionSO, } from '../../utils'; // eslint-disable-next-line import/no-default-export export default ({ getService }: FtrProviderContext) => { const supertest = getService('supertest'); const log = getService('log'); + const es = getService('es'); describe('update_rules_bulk', () => { describe('deprecations', () => { @@ -148,6 +150,13 @@ export default ({ getService }: FtrProviderContext) => { createLegacyRuleAction(supertest, rule2.id, connector.body.id), ]); + // check for legacy sidecar action + const sidecarActionsResults = await getLegacyActionSO(es); + expect(sidecarActionsResults.hits.hits.length).to.eql(2); + expect( + sidecarActionsResults.hits.hits.map((hit) => hit?._source?.references[0].id).sort() + ).to.eql([rule1.id, rule2.id].sort()); + const updatedRule1 = getSimpleRuleUpdate('rule-1'); updatedRule1.name = 'some other name'; updatedRule1.actions = [action1]; @@ -165,11 +174,15 @@ export default ({ getService }: FtrProviderContext) => { .send([updatedRule1, updatedRule2]) .expect(200); + // legacy sidecar action should be gone + const sidecarActionsPostResults = await getLegacyActionSO(es); + expect(sidecarActionsPostResults.hits.hits.length).to.eql(0); + body.forEach((response) => { const bodyToCompare = removeServerGeneratedProperties(response); const outputRule = getSimpleRuleOutput(response.rule_id); outputRule.name = 'some other name'; - outputRule.revision = 2; + outputRule.revision = 1; outputRule.actions = [ { action_type_id: '.slack', @@ -232,7 +245,7 @@ export default ({ getService }: FtrProviderContext) => { body.forEach((response) => { const outputRule = getSimpleRuleOutput(response.rule_id); outputRule.name = 'some other name'; - outputRule.revision = 2; + outputRule.revision = 1; outputRule.actions = []; outputRule.throttle = 'no_actions'; const bodyToCompare = removeServerGeneratedProperties(response); 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 index 25824769857d7..9eb08337427d9 100644 --- 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 @@ -70,3 +70,6 @@ export const waitForRuleSuccess = (params: WaitForRuleStatusParams): Promise => waitForRuleStatus(RuleExecutionStatus['partial failure'], params); + +export const waitForRuleFailure = (params: WaitForRuleStatusParams): Promise => + waitForRuleStatus(RuleExecutionStatus.failed, params); From 4e5ad09b3ae34ac0f140bacc30e5ebae0b7411a5 Mon Sep 17 00:00:00 2001 From: Dmitrii Shevchenko Date: Wed, 19 Apr 2023 13:16:24 +0200 Subject: [PATCH 33/78] [Security Solution] Handle missing base versions during rule upgrade (#154571) **Resolves: https://github.com/elastic/kibana/issues/152836** **Resolves: https://github.com/elastic/kibana/issues/148183** **Resolves partially: https://github.com/elastic/kibana/issues/148189** --- .../response_schema.ts | 3 - .../review_rule_upgrade/response_schema.ts | 9 --- .../diff/three_way_diff/three_way_diff.ts | 14 +++- .../three_way_diff/three_way_diff_outcome.ts | 14 +++- .../api/get_prebuilt_rules_status/route.ts | 4 +- .../api/review_rule_installation/route.ts | 2 +- .../api/review_rule_upgrade/route.ts | 28 +++---- .../logic/diff/calculate_rule_diff.ts | 11 +-- .../algorithms/simple_diff_algorithm.ts | 24 +++--- .../calculation/calculate_rule_fields_diff.ts | 74 ++++++++++--------- .../calculation/diff_calculation_helpers.ts | 8 +- 11 files changed, 106 insertions(+), 85 deletions(-) diff --git a/x-pack/plugins/security_solution/common/detection_engine/prebuilt_rules/api/get_prebuilt_rules_status/response_schema.ts b/x-pack/plugins/security_solution/common/detection_engine/prebuilt_rules/api/get_prebuilt_rules_status/response_schema.ts index ea131df38375d..a60311f40b0a2 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/prebuilt_rules/api/get_prebuilt_rules_status/response_schema.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/prebuilt_rules/api/get_prebuilt_rules_status/response_schema.ts @@ -15,9 +15,6 @@ export interface GetPrebuiltRulesStatusResponseBody { } export interface PrebuiltRulesStatusStats { - /** Total number of existing (known) prebuilt rules */ - num_prebuilt_rules_total: number; - /** Number of installed prebuilt rules */ num_prebuilt_rules_installed: number; diff --git a/x-pack/plugins/security_solution/common/detection_engine/prebuilt_rules/api/review_rule_upgrade/response_schema.ts b/x-pack/plugins/security_solution/common/detection_engine/prebuilt_rules/api/review_rule_upgrade/response_schema.ts index 97a4cee4c993f..35994a7963956 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/prebuilt_rules/api/review_rule_upgrade/response_schema.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/prebuilt_rules/api/review_rule_upgrade/response_schema.ts @@ -25,17 +25,8 @@ export interface RuleUpgradeStatsForReview { /** Number of installed prebuilt rules available for upgrade (stock + customized) */ num_rules_to_upgrade_total: number; - /** Number of installed prebuilt rules available for upgrade which are stock (non-customized) */ - num_rules_to_upgrade_not_customized: number; - - /** Number of installed prebuilt rules available for upgrade which are customized by the user */ - num_rules_to_upgrade_customized: number; - /** A union of all tags of all rules available for upgrade */ tags: RuleTagArray; - - /** A union of all fields "to be upgraded" across all the rules available for upgrade. An array of field names. */ - fields: string[]; } export interface RuleUpgradeInfoForReview { diff --git a/x-pack/plugins/security_solution/common/detection_engine/prebuilt_rules/model/diff/three_way_diff/three_way_diff.ts b/x-pack/plugins/security_solution/common/detection_engine/prebuilt_rules/model/diff/three_way_diff/three_way_diff.ts index 521b31dc4ea15..3b6831709ced9 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/prebuilt_rules/model/diff/three_way_diff/three_way_diff.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/prebuilt_rules/model/diff/three_way_diff/three_way_diff.ts @@ -8,14 +8,26 @@ import type { ThreeWayDiffOutcome } from './three_way_diff_outcome'; import type { ThreeWayMergeOutcome } from './three_way_merge_outcome'; +/** + * A symbol that represents a missing value and used when a base version of a + * rule is not available. We need a mechanism that helps us distinguish two + * situations: + * - the base version is found, and its value is `undefined` or `null` + * - the base version is not found + * + */ +export const MissingVersion = Symbol('MissingVersion'); +export type MissingVersion = typeof MissingVersion; + /** * Three versions of a value to pass to a diff algorithm. */ export interface ThreeVersionsOf { /** * Corresponds to the stock version of the currently installed prebuilt rule. + * This field is optional because the base version is not always available in the package. */ - base_version: TValue; + base_version: TValue | MissingVersion; /** * Corresponds exactly to the currently installed prebuilt rule: diff --git a/x-pack/plugins/security_solution/common/detection_engine/prebuilt_rules/model/diff/three_way_diff/three_way_diff_outcome.ts b/x-pack/plugins/security_solution/common/detection_engine/prebuilt_rules/model/diff/three_way_diff/three_way_diff_outcome.ts index 07d3fb526c209..fd36a69dbde3c 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/prebuilt_rules/model/diff/three_way_diff/three_way_diff_outcome.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/prebuilt_rules/model/diff/three_way_diff/three_way_diff_outcome.ts @@ -6,6 +6,7 @@ */ import { isEqual } from 'lodash'; +import { MissingVersion } from './three_way_diff'; /** * Result of comparing three versions of a value against each other. @@ -29,7 +30,7 @@ export enum ThreeWayDiffOutcome { } export const determineDiffOutcome = ( - baseVersion: TValue, + baseVersion: TValue | MissingVersion, currentVersion: TValue, targetVersion: TValue ): ThreeWayDiffOutcome => { @@ -37,6 +38,17 @@ export const determineDiffOutcome = ( const baseEqlTarget = isEqual(baseVersion, targetVersion); const currentEqlTarget = isEqual(currentVersion, targetVersion); + if (baseVersion === MissingVersion) { + /** + * We couldn't find the base version of the rule in the package so further + * version comparison is not possible. We assume that the rule is not + * customized and the value can be updated if there's an update. + */ + return currentEqlTarget + ? ThreeWayDiffOutcome.StockValueNoUpdate + : ThreeWayDiffOutcome.StockValueCanUpdate; + } + if (baseEqlCurrent) { return currentEqlTarget ? ThreeWayDiffOutcome.StockValueNoUpdate diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/get_prebuilt_rules_status/route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/get_prebuilt_rules_status/route.ts index 1926320114a17..d1bdcfc80e757 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/get_prebuilt_rules_status/route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/get_prebuilt_rules_status/route.ts @@ -73,11 +73,9 @@ export const getPrebuiltRulesStatusRoute = (router: SecuritySolutionPluginRouter }; const calculateRuleStats = (buckets: VersionBuckets): PrebuiltRulesStatusStats => { - const { latestVersions, installedVersions, latestVersionsToInstall, installedVersionsToUpgrade } = - buckets; + const { installedVersions, latestVersionsToInstall, installedVersionsToUpgrade } = buckets; return { - num_prebuilt_rules_total: latestVersions.length, num_prebuilt_rules_installed: installedVersions.length, num_prebuilt_rules_to_install: latestVersionsToInstall.length, num_prebuilt_rules_to_upgrade: installedVersionsToUpgrade.length, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/review_rule_installation/route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/review_rule_installation/route.ts index 74d9c6ee80030..8eb8b91af8fbb 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/review_rule_installation/route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/review_rule_installation/route.ts @@ -79,7 +79,7 @@ export const reviewRuleInstallationRoute = (router: SecuritySolutionPluginRouter const getAggregatedTags = (rules: PrebuiltRuleAsset[]): string[] => { const set = new Set(rules.flatMap((rule) => rule.tags || [])); - return Array.from(set.values()); + return Array.from(set.values()).sort((a, b) => a.localeCompare(b)); }; const calculateRuleStats = ( diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/review_rule_upgrade/route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/review_rule_upgrade/route.ts index 3b52cc102110e..626ee8f0bbf7a 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/review_rule_upgrade/route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/review_rule_upgrade/route.ts @@ -30,6 +30,7 @@ import { buildSiemResponse } from '../../../routes/utils'; import { createPrebuiltRuleAssetsClient } from '../../logic/rule_assets/prebuilt_rule_assets_client'; import { createPrebuiltRuleObjectsClient } from '../../logic/rule_objects/prebuilt_rule_objects_client'; import { getVersionBuckets } from '../../model/rule_versions/get_version_buckets'; +import { invariant } from '../../../../../../common/utils/invariant'; export const reviewRuleUpgradeRoute = (router: SecuritySolutionPluginRouter) => { router.post( @@ -114,28 +115,27 @@ const getRuleDiffCalculationArgs = ( const baseRule = baseRulesMap.get(ruleId); const latestRule = latestRulesMap.get(ruleId); - // TODO: https://github.com/elastic/kibana/issues/148189 - // Make base versions optional for diff calculation. We need to support this in order to be able - // to still show diffs for rule assets coming from packages without historical versions. - if (installedRule != null && baseRule != null && latestRule != null) { - result.push({ - currentVersion: installedRule, - baseVersion: baseRule, - targetVersion: latestRule, - }); - } + // baseRule can be undefined if the rule has no historical versions, but other versions should always be present + invariant(installedRule != null, `installedRule is not found for rule_id: ${ruleId}`); + invariant(latestRule != null, `latestRule is not found for rule_id: ${ruleId}`); + + result.push({ + currentVersion: installedRule, + baseVersion: baseRule, + targetVersion: latestRule, + }); }); return result; }; const calculateRuleStats = (results: CalculateRuleDiffResult[]): RuleUpgradeStatsForReview => { + const allTags = new Set( + results.flatMap((result) => result.ruleVersions.input.current.tags) + ); return { num_rules_to_upgrade_total: results.length, - num_rules_to_upgrade_not_customized: results.length, - num_rules_to_upgrade_customized: 0, - tags: [], - fields: [], + tags: [...allTags].sort((a, b) => a.localeCompare(b)), }; }; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/diff/calculate_rule_diff.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/diff/calculate_rule_diff.ts index 026589ad0ca0d..061c1d1082bdd 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/diff/calculate_rule_diff.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/diff/calculate_rule_diff.ts @@ -8,6 +8,7 @@ import type { DiffableRule } from '../../../../../../common/detection_engine/prebuilt_rules/model/diff/diffable_rule/diffable_rule'; import type { FullRuleDiff } from '../../../../../../common/detection_engine/prebuilt_rules/model/diff/rule_diff/rule_diff'; import type { ThreeWayDiff } from '../../../../../../common/detection_engine/prebuilt_rules/model/diff/three_way_diff/three_way_diff'; +import { MissingVersion } from '../../../../../../common/detection_engine/prebuilt_rules/model/diff/three_way_diff/three_way_diff'; import type { RuleResponse } from '../../../../../../common/detection_engine/rule_schema'; import type { PrebuiltRuleAsset } from '../../model/rule_assets/prebuilt_rule_asset'; @@ -16,7 +17,7 @@ import { convertRuleToDiffable } from './normalization/convert_rule_to_diffable' export interface CalculateRuleDiffArgs { currentVersion: RuleResponse; - baseVersion: PrebuiltRuleAsset; + baseVersion?: PrebuiltRuleAsset; targetVersion: PrebuiltRuleAsset; } @@ -25,12 +26,12 @@ export interface CalculateRuleDiffResult { ruleVersions: { input: { current: RuleResponse; - base: PrebuiltRuleAsset; + base?: PrebuiltRuleAsset; target: PrebuiltRuleAsset; }; output: { current: DiffableRule; - base: DiffableRule; + base?: DiffableRule; target: DiffableRule; }; }; @@ -60,12 +61,12 @@ export const calculateRuleDiff = (args: CalculateRuleDiffArgs): CalculateRuleDif const { baseVersion, currentVersion, targetVersion } = args; - const diffableBaseVersion = convertRuleToDiffable(baseVersion); + const diffableBaseVersion = baseVersion ? convertRuleToDiffable(baseVersion) : undefined; const diffableCurrentVersion = convertRuleToDiffable(currentVersion); const diffableTargetVersion = convertRuleToDiffable(targetVersion); const fieldsDiff = calculateRuleFieldsDiff({ - base_version: diffableBaseVersion, + base_version: diffableBaseVersion || MissingVersion, current_version: diffableCurrentVersion, target_version: diffableTargetVersion, }); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/diff/calculation/algorithms/simple_diff_algorithm.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/diff/calculation/algorithms/simple_diff_algorithm.ts index 699f8f20bcf1f..90a55ee3d6eff 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/diff/calculation/algorithms/simple_diff_algorithm.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/diff/calculation/algorithms/simple_diff_algorithm.ts @@ -29,12 +29,11 @@ export const simpleDiffAlgorithm = ( const diffOutcome = determineDiffOutcome(baseVersion, currentVersion, targetVersion); const valueCanUpdate = determineIfValueCanUpdate(diffOutcome); - const { mergeOutcome, mergedVersion } = mergeVersions( - baseVersion, + const { mergeOutcome, mergedVersion } = mergeVersions({ currentVersion, targetVersion, - diffOutcome - ); + diffOutcome, + }); return { base_version: baseVersion, @@ -54,12 +53,17 @@ interface MergeResult { mergedVersion: TValue; } -const mergeVersions = ( - baseVersion: TValue, - currentVersion: TValue, - targetVersion: TValue, - diffOutcome: ThreeWayDiffOutcome -): MergeResult => { +interface MergeArgs { + currentVersion: TValue; + targetVersion: TValue; + diffOutcome: ThreeWayDiffOutcome; +} + +const mergeVersions = ({ + currentVersion, + targetVersion, + diffOutcome, +}: MergeArgs): MergeResult => { switch (diffOutcome) { case ThreeWayDiffOutcome.StockValueNoUpdate: case ThreeWayDiffOutcome.CustomizedValueNoUpdate: diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/diff/calculation/calculate_rule_fields_diff.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/diff/calculation/calculate_rule_fields_diff.ts index bb7a83ff20b23..abd084c1789e4 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/diff/calculation/calculate_rule_fields_diff.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/diff/calculation/calculate_rule_fields_diff.ts @@ -33,6 +33,7 @@ import type { import type { FieldsDiffAlgorithmsFor } from '../../../../../../../common/detection_engine/prebuilt_rules/model/diff/rule_diff/fields_diff'; import type { ThreeVersionsOf } from '../../../../../../../common/detection_engine/prebuilt_rules/model/diff/three_way_diff/three_way_diff'; +import { MissingVersion } from '../../../../../../../common/detection_engine/prebuilt_rules/model/diff/three_way_diff/three_way_diff'; import { calculateFieldsDiffFor } from './diff_calculation_helpers'; import { simpleDiffAlgorithm } from './algorithms/simple_diff_algorithm'; @@ -47,15 +48,16 @@ const TARGET_TYPE_ERROR = `Target version can't be of different rule type`; export const calculateRuleFieldsDiff = ( ruleVersions: ThreeVersionsOf ): RuleFieldsDiff => { - validateRuleVersions(ruleVersions); - const commonFieldsDiff = calculateCommonFieldsDiff(ruleVersions); // eslint-disable-next-line @typescript-eslint/naming-convention const { base_version, current_version, target_version } = ruleVersions; + const hasBaseVersion = base_version !== MissingVersion; switch (current_version.type) { case 'query': { - invariant(base_version.type === 'query', BASE_TYPE_ERROR); + if (hasBaseVersion) { + invariant(base_version.type === 'query', BASE_TYPE_ERROR); + } invariant(target_version.type === 'query', TARGET_TYPE_ERROR); return { ...commonFieldsDiff, @@ -63,7 +65,9 @@ export const calculateRuleFieldsDiff = ( }; } case 'saved_query': { - invariant(base_version.type === 'saved_query', BASE_TYPE_ERROR); + if (hasBaseVersion) { + invariant(base_version.type === 'saved_query', BASE_TYPE_ERROR); + } invariant(target_version.type === 'saved_query', TARGET_TYPE_ERROR); return { ...commonFieldsDiff, @@ -71,7 +75,9 @@ export const calculateRuleFieldsDiff = ( }; } case 'eql': { - invariant(base_version.type === 'eql', BASE_TYPE_ERROR); + if (hasBaseVersion) { + invariant(base_version.type === 'eql', BASE_TYPE_ERROR); + } invariant(target_version.type === 'eql', TARGET_TYPE_ERROR); return { ...commonFieldsDiff, @@ -79,7 +85,9 @@ export const calculateRuleFieldsDiff = ( }; } case 'threat_match': { - invariant(base_version.type === 'threat_match', BASE_TYPE_ERROR); + if (hasBaseVersion) { + invariant(base_version.type === 'threat_match', BASE_TYPE_ERROR); + } invariant(target_version.type === 'threat_match', TARGET_TYPE_ERROR); return { ...commonFieldsDiff, @@ -87,7 +95,9 @@ export const calculateRuleFieldsDiff = ( }; } case 'threshold': { - invariant(base_version.type === 'threshold', BASE_TYPE_ERROR); + if (hasBaseVersion) { + invariant(base_version.type === 'threshold', BASE_TYPE_ERROR); + } invariant(target_version.type === 'threshold', TARGET_TYPE_ERROR); return { ...commonFieldsDiff, @@ -95,7 +105,9 @@ export const calculateRuleFieldsDiff = ( }; } case 'machine_learning': { - invariant(base_version.type === 'machine_learning', BASE_TYPE_ERROR); + if (hasBaseVersion) { + invariant(base_version.type === 'machine_learning', BASE_TYPE_ERROR); + } invariant(target_version.type === 'machine_learning', TARGET_TYPE_ERROR); return { ...commonFieldsDiff, @@ -103,7 +115,9 @@ export const calculateRuleFieldsDiff = ( }; } case 'new_terms': { - invariant(base_version.type === 'new_terms', BASE_TYPE_ERROR); + if (hasBaseVersion) { + invariant(base_version.type === 'new_terms', BASE_TYPE_ERROR); + } invariant(target_version.type === 'new_terms', TARGET_TYPE_ERROR); return { ...commonFieldsDiff, @@ -116,20 +130,10 @@ export const calculateRuleFieldsDiff = ( } }; -const validateRuleVersions = (ruleVersions: ThreeVersionsOf): void => { - // eslint-disable-next-line @typescript-eslint/naming-convention - const { base_version, current_version, target_version } = ruleVersions; - const types = new Set([base_version.type, current_version.type, target_version.type]); - - if (types.size > 1) { - throw new Error('Cannot change rule type during rule upgrade'); - } -}; - const calculateCommonFieldsDiff = ( - fieldsVersions: ThreeVersionsOf + ruleVersions: ThreeVersionsOf ): CommonFieldsDiff => { - return calculateFieldsDiffFor(fieldsVersions, commonFieldsDiffAlgorithms); + return calculateFieldsDiffFor(ruleVersions, commonFieldsDiffAlgorithms); }; const commonFieldsDiffAlgorithms: FieldsDiffAlgorithmsFor = { @@ -164,9 +168,9 @@ const commonFieldsDiffAlgorithms: FieldsDiffAlgorithmsFor }; const calculateCustomQueryFieldsDiff = ( - fieldsVersions: ThreeVersionsOf + ruleVersions: ThreeVersionsOf ): CustomQueryFieldsDiff => { - return calculateFieldsDiffFor(fieldsVersions, customQueryFieldsDiffAlgorithms); + return calculateFieldsDiffFor(ruleVersions, customQueryFieldsDiffAlgorithms); }; const customQueryFieldsDiffAlgorithms: FieldsDiffAlgorithmsFor = { @@ -177,9 +181,9 @@ const customQueryFieldsDiffAlgorithms: FieldsDiffAlgorithmsFor + ruleVersions: ThreeVersionsOf ): SavedQueryFieldsDiff => { - return calculateFieldsDiffFor(fieldsVersions, savedQueryFieldsDiffAlgorithms); + return calculateFieldsDiffFor(ruleVersions, savedQueryFieldsDiffAlgorithms); }; const savedQueryFieldsDiffAlgorithms: FieldsDiffAlgorithmsFor = { @@ -190,9 +194,9 @@ const savedQueryFieldsDiffAlgorithms: FieldsDiffAlgorithmsFor + ruleVersions: ThreeVersionsOf ): EqlFieldsDiff => { - return calculateFieldsDiffFor(fieldsVersions, eqlFieldsDiffAlgorithms); + return calculateFieldsDiffFor(ruleVersions, eqlFieldsDiffAlgorithms); }; const eqlFieldsDiffAlgorithms: FieldsDiffAlgorithmsFor = { @@ -205,9 +209,9 @@ const eqlFieldsDiffAlgorithms: FieldsDiffAlgorithmsFor = { }; const calculateThreatMatchFieldsDiff = ( - fieldsVersions: ThreeVersionsOf + ruleVersions: ThreeVersionsOf ): ThreatMatchFieldsDiff => { - return calculateFieldsDiffFor(fieldsVersions, threatMatchFieldsDiffAlgorithms); + return calculateFieldsDiffFor(ruleVersions, threatMatchFieldsDiffAlgorithms); }; const threatMatchFieldsDiffAlgorithms: FieldsDiffAlgorithmsFor = { @@ -223,9 +227,9 @@ const threatMatchFieldsDiffAlgorithms: FieldsDiffAlgorithmsFor + ruleVersions: ThreeVersionsOf ): ThresholdFieldsDiff => { - return calculateFieldsDiffFor(fieldsVersions, thresholdFieldsDiffAlgorithms); + return calculateFieldsDiffFor(ruleVersions, thresholdFieldsDiffAlgorithms); }; const thresholdFieldsDiffAlgorithms: FieldsDiffAlgorithmsFor = { @@ -236,9 +240,9 @@ const thresholdFieldsDiffAlgorithms: FieldsDiffAlgorithmsFor + ruleVersions: ThreeVersionsOf ): MachineLearningFieldsDiff => { - return calculateFieldsDiffFor(fieldsVersions, machineLearningFieldsDiffAlgorithms); + return calculateFieldsDiffFor(ruleVersions, machineLearningFieldsDiffAlgorithms); }; const machineLearningFieldsDiffAlgorithms: FieldsDiffAlgorithmsFor = @@ -249,9 +253,9 @@ const machineLearningFieldsDiffAlgorithms: FieldsDiffAlgorithmsFor + ruleVersions: ThreeVersionsOf ): NewTermsFieldsDiff => { - return calculateFieldsDiffFor(fieldsVersions, newTermsFieldsDiffAlgorithms); + return calculateFieldsDiffFor(ruleVersions, newTermsFieldsDiffAlgorithms); }; const newTermsFieldsDiffAlgorithms: FieldsDiffAlgorithmsFor = { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/diff/calculation/diff_calculation_helpers.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/diff/calculation/diff_calculation_helpers.ts index eb1b6e2d38aca..d36c33cf5d890 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/diff/calculation/diff_calculation_helpers.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/diff/calculation/diff_calculation_helpers.ts @@ -11,13 +11,14 @@ import type { FieldsDiffAlgorithmsFor, } from '../../../../../../../common/detection_engine/prebuilt_rules/model/diff/rule_diff/fields_diff'; import type { ThreeVersionsOf } from '../../../../../../../common/detection_engine/prebuilt_rules/model/diff/three_way_diff/three_way_diff'; +import { MissingVersion } from '../../../../../../../common/detection_engine/prebuilt_rules/model/diff/three_way_diff/three_way_diff'; export const calculateFieldsDiffFor = ( - objectVersions: ThreeVersionsOf, + ruleVersions: ThreeVersionsOf, fieldsDiffAlgorithms: FieldsDiffAlgorithmsFor ): FieldsDiff => { const result = mapValues(fieldsDiffAlgorithms, (calculateFieldDiff, fieldName) => { - const fieldVersions = pickField(fieldName as keyof TObject, objectVersions); + const fieldVersions = pickField(fieldName as keyof TObject, ruleVersions); const fieldDiff = calculateFieldDiff(fieldVersions); return fieldDiff; }); @@ -31,7 +32,8 @@ const pickField = ( versions: ThreeVersionsOf ): ThreeVersionsOf => { return { - base_version: versions.base_version[fieldName], + base_version: + versions.base_version !== MissingVersion ? versions.base_version[fieldName] : MissingVersion, current_version: versions.current_version[fieldName], target_version: versions.target_version[fieldName], }; From ec39987b995b8460e1e47db6096a719da86f6318 Mon Sep 17 00:00:00 2001 From: Coen Warmer Date: Wed, 19 Apr 2023 13:41:02 +0200 Subject: [PATCH 34/78] [SLO] Add SloRulesBadge to SloListItem (#155187) --- .../hooks/slo/use_fetch_rules_for_slo.ts | 90 +++++++++++++++++++ .../components/badges/slo_badges.stories.tsx | 3 +- .../slos/components/badges/slo_badges.tsx | 12 ++- .../slo_indicator_type_badge.stories.tsx | 2 +- .../badges/slo_rules_badge.stories.tsx | 35 ++++++++ .../components/badges/slo_rules_badge.tsx | 41 +++++++++ .../badges/slo_time_window_badge.stories.tsx | 2 +- .../pages/slos/components/slo_list_item.tsx | 29 ++++-- .../pages/slos/components/slo_list_items.tsx | 18 ++-- 9 files changed, 211 insertions(+), 21 deletions(-) create mode 100644 x-pack/plugins/observability/public/hooks/slo/use_fetch_rules_for_slo.ts create mode 100644 x-pack/plugins/observability/public/pages/slos/components/badges/slo_rules_badge.stories.tsx create mode 100644 x-pack/plugins/observability/public/pages/slos/components/badges/slo_rules_badge.tsx diff --git a/x-pack/plugins/observability/public/hooks/slo/use_fetch_rules_for_slo.ts b/x-pack/plugins/observability/public/hooks/slo/use_fetch_rules_for_slo.ts new file mode 100644 index 0000000000000..0c0b5a7108952 --- /dev/null +++ b/x-pack/plugins/observability/public/hooks/slo/use_fetch_rules_for_slo.ts @@ -0,0 +1,90 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { + QueryObserverResult, + RefetchOptions, + RefetchQueryFilters, + useQuery, +} from '@tanstack/react-query'; +import { Rule } from '@kbn/triggers-actions-ui-plugin/public'; +import { useKibana } from '../../utils/kibana_react'; + +type SloId = string; + +interface Params { + sloIds: SloId[]; +} + +// eslint-disable-next-line @typescript-eslint/consistent-type-definitions +export type SloRule = { sloId: string; name: string }; + +interface RuleApiResponse { + page: number; + total: number; + per_page: number; + data: Array>; +} + +export interface UseFetchRulesForSloResponse { + isInitialLoading: boolean; + isLoading: boolean; + isRefetching: boolean; + isSuccess: boolean; + isError: boolean; + data: Record>> | undefined; + refetch: ( + options?: (RefetchOptions & RefetchQueryFilters) | undefined + ) => Promise>> | undefined, unknown>>; +} + +export function useFetchRulesForSlo({ sloIds = [] }: Params): UseFetchRulesForSloResponse { + const { http } = useKibana().services; + + const { isInitialLoading, isLoading, isError, isSuccess, isRefetching, data, refetch } = useQuery( + { + queryKey: ['fetchRulesForSlo', sloIds], + queryFn: async () => { + try { + const body = JSON.stringify({ + filter: `alert.attributes.params.sloId:(${sloIds.join(' or ')})`, + fields: ['params.sloId', 'name'], + per_page: 1000, + }); + + const response = await http.post(`/internal/alerting/rules/_find`, { + body, + }); + + const init = sloIds.reduce((acc, sloId) => ({ ...acc, [sloId]: [] }), {}); + + return response.data.reduce( + (acc, rule) => ({ + ...acc, + [rule.params.sloId]: acc[rule.params.sloId].concat(rule), + }), + init as Record>> + ); + } catch (error) { + // ignore error for retrieving slos + } + }, + enabled: Boolean(sloIds), + refetchOnWindowFocus: false, + } + ); + + return { + data, + isLoading, + isInitialLoading, + isRefetching, + isSuccess, + isError, + refetch, + }; +} diff --git a/x-pack/plugins/observability/public/pages/slos/components/badges/slo_badges.stories.tsx b/x-pack/plugins/observability/public/pages/slos/components/badges/slo_badges.stories.tsx index 50430e0f6bd0b..267ebac0ce9b4 100644 --- a/x-pack/plugins/observability/public/pages/slos/components/badges/slo_badges.stories.tsx +++ b/x-pack/plugins/observability/public/pages/slos/components/badges/slo_badges.stories.tsx @@ -15,7 +15,7 @@ import { SloBadges as Component, Props } from './slo_badges'; export default { component: Component, - title: 'app/SLO/ListPage/Badges/SloBadges', + title: 'app/SLO/Badges/SloBadges', decorators: [KibanaReactStorybookDecorator], }; @@ -27,6 +27,7 @@ const Template: ComponentStory = (props: Props) => ( const defaultProps = { slo: buildForecastedSlo(), + rules: [], }; export const SloBadges = Template.bind({}); diff --git a/x-pack/plugins/observability/public/pages/slos/components/badges/slo_badges.tsx b/x-pack/plugins/observability/public/pages/slos/components/badges/slo_badges.tsx index 93d0c70a6858c..15d36f3525ed1 100644 --- a/x-pack/plugins/observability/public/pages/slos/components/badges/slo_badges.tsx +++ b/x-pack/plugins/observability/public/pages/slos/components/badges/slo_badges.tsx @@ -8,25 +8,31 @@ import React from 'react'; import { EuiFlexGroup } from '@elastic/eui'; import { SLOWithSummaryResponse } from '@kbn/slo-schema'; +import { Rule } from '@kbn/triggers-actions-ui-plugin/public'; import { SloIndicatorTypeBadge } from './slo_indicator_type_badge'; import { SloStatusBadge } from '../../../../components/slo/slo_status_badge'; +import { SloActiveAlertsBadge } from '../../../../components/slo/slo_status_badge/slo_active_alerts_badge'; import { SloTimeWindowBadge } from './slo_time_window_badge'; import type { ActiveAlerts } from '../../../../hooks/slo/use_fetch_active_alerts'; -import { SloActiveAlertsBadge } from '../../../../components/slo/slo_status_badge/slo_active_alerts_badge'; +import type { SloRule } from '../../../../hooks/slo/use_fetch_rules_for_slo'; +import { SloRulesBadge } from './slo_rules_badge'; export interface Props { - slo: SLOWithSummaryResponse; activeAlerts?: ActiveAlerts; + rules: Array> | undefined; + slo: SLOWithSummaryResponse; + onClickRuleBadge: () => void; } -export function SloBadges({ slo, activeAlerts }: Props) { +export function SloBadges({ activeAlerts, rules, slo, onClickRuleBadge }: Props) { return ( + ); } diff --git a/x-pack/plugins/observability/public/pages/slos/components/badges/slo_indicator_type_badge.stories.tsx b/x-pack/plugins/observability/public/pages/slos/components/badges/slo_indicator_type_badge.stories.tsx index fa451706de164..c8536bdc93ddd 100644 --- a/x-pack/plugins/observability/public/pages/slos/components/badges/slo_indicator_type_badge.stories.tsx +++ b/x-pack/plugins/observability/public/pages/slos/components/badges/slo_indicator_type_badge.stories.tsx @@ -20,7 +20,7 @@ import { buildSlo } from '../../../../data/slo/slo'; export default { component: Component, - title: 'app/SLO/ListPage/Badges/SloIndicatorTypeBadge', + title: 'app/SLO/Badges/SloIndicatorTypeBadge', decorators: [KibanaReactStorybookDecorator], }; diff --git a/x-pack/plugins/observability/public/pages/slos/components/badges/slo_rules_badge.stories.tsx b/x-pack/plugins/observability/public/pages/slos/components/badges/slo_rules_badge.stories.tsx new file mode 100644 index 0000000000000..c97be672356f5 --- /dev/null +++ b/x-pack/plugins/observability/public/pages/slos/components/badges/slo_rules_badge.stories.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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { ComponentStory } from '@storybook/react'; + +import { EuiFlexGroup } from '@elastic/eui'; +import { Rule } from '@kbn/triggers-actions-ui-plugin/public'; +import { SloRule } from '../../../../hooks/slo/use_fetch_rules_for_slo'; +import { KibanaReactStorybookDecorator } from '../../../../utils/kibana_react.storybook_decorator'; +import { SloRulesBadge as Component, Props } from './slo_rules_badge'; + +export default { + component: Component, + title: 'app/SLO/Badges/SloRulesBadge', + decorators: [KibanaReactStorybookDecorator], +}; + +const Template: ComponentStory = (props: Props) => ( + + + +); + +export const WithNoRule = Template.bind({}); +WithNoRule.args = { rules: [] }; + +export const WithLoadingRule = Template.bind({}); +WithLoadingRule.args = { rules: undefined }; +export const WithRule = Template.bind({}); +WithRule.args = { rules: [{ name: 'rulename' }] as Array> }; diff --git a/x-pack/plugins/observability/public/pages/slos/components/badges/slo_rules_badge.tsx b/x-pack/plugins/observability/public/pages/slos/components/badges/slo_rules_badge.tsx new file mode 100644 index 0000000000000..c60617152cb00 --- /dev/null +++ b/x-pack/plugins/observability/public/pages/slos/components/badges/slo_rules_badge.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 { EuiBadge, EuiIcon, EuiToolTip } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { Rule } from '@kbn/triggers-actions-ui-plugin/public'; +import { SloRule } from '../../../../hooks/slo/use_fetch_rules_for_slo'; + +export interface Props { + rules: Array> | undefined; + onClick: () => void; +} + +export function SloRulesBadge({ rules, onClick }: Props) { + return rules === undefined || rules.length > 0 ? null : ( + + + + + + ); +} diff --git a/x-pack/plugins/observability/public/pages/slos/components/badges/slo_time_window_badge.stories.tsx b/x-pack/plugins/observability/public/pages/slos/components/badges/slo_time_window_badge.stories.tsx index 7f534e831f9fb..4298c8a28aa11 100644 --- a/x-pack/plugins/observability/public/pages/slos/components/badges/slo_time_window_badge.stories.tsx +++ b/x-pack/plugins/observability/public/pages/slos/components/badges/slo_time_window_badge.stories.tsx @@ -15,7 +15,7 @@ import { buildSlo } from '../../../../data/slo/slo'; export default { component: Component, - title: 'app/SLO/ListPage/Badges/SloTimeWindowBadge', + title: 'app/SLO/Badges/SloTimeWindowBadge', decorators: [KibanaReactStorybookDecorator], }; diff --git a/x-pack/plugins/observability/public/pages/slos/components/slo_list_item.tsx b/x-pack/plugins/observability/public/pages/slos/components/slo_list_item.tsx index 6c8e44bec189d..ffe07cbb9a902 100644 --- a/x-pack/plugins/observability/public/pages/slos/components/slo_list_item.tsx +++ b/x-pack/plugins/observability/public/pages/slos/components/slo_list_item.tsx @@ -6,7 +6,7 @@ */ import React, { useState } from 'react'; -import { useIsMutating } from '@tanstack/react-query'; +import { useIsMutating, useQueryClient } from '@tanstack/react-query'; import { EuiButtonIcon, EuiContextMenuItem, @@ -21,7 +21,7 @@ import { import { i18n } from '@kbn/i18n'; import { HistoricalSummaryResponse, SLOWithSummaryResponse } from '@kbn/slo-schema'; -import { ActiveAlerts } from '../../../hooks/slo/use_fetch_active_alerts'; +import type { Rule } from '@kbn/triggers-actions-ui-plugin/public'; import { useCapabilities } from '../../../hooks/slo/use_capabilities'; import { useKibana } from '../../../utils/kibana_react'; import { useCloneSlo } from '../../../hooks/slo/use_clone_slo'; @@ -36,9 +36,12 @@ import { import { SLO_BURN_RATE_RULE_ID } from '../../../../common/constants'; import { sloFeatureId } from '../../../../common'; import { paths } from '../../../config/paths'; +import type { ActiveAlerts } from '../../../hooks/slo/use_fetch_active_alerts'; +import type { SloRule } from '../../../hooks/slo/use_fetch_rules_for_slo'; export interface SloListItemProps { slo: SLOWithSummaryResponse; + rules: Array> | undefined; historicalSummary?: HistoricalSummaryResponse[]; historicalSummaryLoading: boolean; activeAlerts?: ActiveAlerts; @@ -46,6 +49,7 @@ export interface SloListItemProps { export function SloListItem({ slo, + rules, historicalSummary = [], historicalSummaryLoading, activeAlerts, @@ -56,6 +60,7 @@ export function SloListItem({ triggersActionsUi: { getAddRuleFlyout: AddRuleFlyout }, } = useKibana().services; const { hasWriteCapabilities } = useCapabilities(); + const queryClient = useQueryClient(); const filteredRuleTypes = useGetFilteredRuleTypes(); @@ -83,6 +88,10 @@ export function SloListItem({ setIsAddRuleFlyoutOpen(true); }; + const handleSavedRule = async () => { + queryClient.invalidateQueries(['fetchRulesForSlo']); + }; + const handleClone = () => { const newSlo = transformValuesToCreateSLOInput( transformSloResponseToCreateSloInput({ ...slo, name: `[Copy] ${slo.name}` })! @@ -125,7 +134,12 @@ export function SloListItem({
- +
@@ -220,21 +234,22 @@ export function SloListItem({ - {isDeleteConfirmationModalOpen ? ( - - ) : null} - {isAddRuleFlyoutOpen ? ( { setIsAddRuleFlyoutOpen(false); }} /> ) : null} + + {isDeleteConfirmationModalOpen ? ( + + ) : null} ); } diff --git a/x-pack/plugins/observability/public/pages/slos/components/slo_list_items.tsx b/x-pack/plugins/observability/public/pages/slos/components/slo_list_items.tsx index cf95af3101c13..2f6f71612788e 100644 --- a/x-pack/plugins/observability/public/pages/slos/components/slo_list_items.tsx +++ b/x-pack/plugins/observability/public/pages/slos/components/slo_list_items.tsx @@ -7,8 +7,9 @@ import React from 'react'; import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; -import { SLOWithSummaryResponse } from '@kbn/slo-schema'; +import type { SLOWithSummaryResponse } from '@kbn/slo-schema'; import { useFetchActiveAlerts } from '../../../hooks/slo/use_fetch_active_alerts'; +import { useFetchRulesForSlo } from '../../../hooks/slo/use_fetch_rules_for_slo'; import { useFetchHistoricalSummary } from '../../../hooks/slo/use_fetch_historical_summary'; import { SloListItem } from './slo_list_item'; import { SloListEmpty } from './slo_list_empty'; @@ -21,12 +22,12 @@ export interface Props { } export function SloListItems({ sloList, loading, error }: Props) { - const { isLoading: historicalSummaryLoading, sloHistoricalSummaryResponse } = - useFetchHistoricalSummary({ sloIds: sloList.map((slo) => slo.id) }); + const sloIds = sloList.map((slo) => slo.id); - const { data: activeAlertsBySlo } = useFetchActiveAlerts({ - sloIds: sloList.map((slo) => slo.id), - }); + const { data: activeAlertsBySlo } = useFetchActiveAlerts({ sloIds }); + const { data: rulesBySlo } = useFetchRulesForSlo({ sloIds }); + const { isLoading: historicalSummaryLoading, sloHistoricalSummaryResponse } = + useFetchHistoricalSummary({ sloIds }); if (!loading && !error && sloList.length === 0) { return ; @@ -40,10 +41,11 @@ export function SloListItems({ sloList, loading, error }: Props) { {sloList.map((slo) => ( ))} From 25df1feee3c3567aeaa21bbbbe804e497aa01107 Mon Sep 17 00:00:00 2001 From: jennypavlova Date: Wed, 19 Apr 2023 14:21:26 +0200 Subject: [PATCH 35/78] [Infrastructure UI] Hosts View: Filtering by metadata functionality (#155170) Closes #151010 ## Summary This PR adds filtering functionality to the metadata table. After some discussions there are changes in the design - see the [last comments](https://github.com/elastic/kibana/issues/151010#issuecomment-1513005095) ## Additional changes As the filters will change the order I changed the id to not depend on the table index anymore (used os instead) inside [hosts table ](https://github.com/elastic/kibana/pull/155170/files#diff-168ba138bc6696100078e3e9cbc438ed7646d6336e9ef85a9c88553c9d8956f5) ## Testing - Open the single host view for one of the hosts available in the table - Inside the flyout select the Metadata tab - Click on any of the available filter icons to add a filter - Check if the filter is applied and a toast is displayed - Click on the same filter icon to remove it and check that it is removed - If there are other filters applied they should combined with the metadata filters (so if filter A is added and then a metadata filter is applied both filters should be visible) https://user-images.githubusercontent.com/14139027/232837814-7292c7ec-8b63-4172-bcd9-2d49daf3b145.mov --- .../metadata/add_metadata_filter_button.tsx | 120 ++++++++++++++++++ .../metadata/build_metadata_filter.ts | 46 +++++++ .../host_details_flyout/metadata/table.tsx | 13 +- .../hosts/hooks/use_hosts_table.test.ts | 4 +- .../metrics/hosts/hooks/use_hosts_table.tsx | 4 +- .../test/functional/apps/infra/hosts_view.ts | 17 ++- .../page_objects/infra_hosts_view.ts | 19 +++ 7 files changed, 217 insertions(+), 6 deletions(-) create mode 100644 x-pack/plugins/infra/public/pages/metrics/hosts/components/host_details_flyout/metadata/add_metadata_filter_button.tsx create mode 100644 x-pack/plugins/infra/public/pages/metrics/hosts/components/host_details_flyout/metadata/build_metadata_filter.ts diff --git a/x-pack/plugins/infra/public/pages/metrics/hosts/components/host_details_flyout/metadata/add_metadata_filter_button.tsx b/x-pack/plugins/infra/public/pages/metrics/hosts/components/host_details_flyout/metadata/add_metadata_filter_button.tsx new file mode 100644 index 0000000000000..51277427b6352 --- /dev/null +++ b/x-pack/plugins/infra/public/pages/metrics/hosts/components/host_details_flyout/metadata/add_metadata_filter_button.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 React, { useMemo } from 'react'; +import { i18n } from '@kbn/i18n'; +import { EuiToolTip, EuiButtonIcon } from '@elastic/eui'; +import { buildMetadataFilter } from './build_metadata_filter'; +import { useMetricsDataViewContext } from '../../../hooks/use_data_view'; +import { useUnifiedSearchContext } from '../../../hooks/use_unified_search'; +import { useKibanaContextForPlugin } from '../../../../../../hooks/use_kibana'; + +interface AddMetadataFilterButtonProps { + item: { + name: string; + value: string | string[] | undefined; + }; +} + +const filterAddedToastTitle = i18n.translate( + 'xpack.infra.hostsViewPage.flyout.metadata.filterAdded', + { + defaultMessage: 'Filter was added', + } +); + +export const AddMetadataFilterButton = ({ item }: AddMetadataFilterButtonProps) => { + const { dataView } = useMetricsDataViewContext(); + const { searchCriteria } = useUnifiedSearchContext(); + + const { + services: { + data: { + query: { filterManager: filterManagerService }, + }, + notifications: { toasts: toastsService }, + }, + } = useKibanaContextForPlugin(); + + const existingFilter = useMemo( + () => searchCriteria.filters.find((filter) => filter.meta.key === item.name), + [item.name, searchCriteria.filters] + ); + + const handleAddFilter = () => { + const newFilter = buildMetadataFilter({ + field: item.name, + value: item.value ?? '', + dataView, + negate: false, + }); + if (newFilter) { + filterManagerService.addFilters(newFilter); + toastsService.addSuccess({ + title: filterAddedToastTitle, + toastLifeTimeMs: 10000, + }); + } + }; + + if (existingFilter) { + return ( + + + filterManagerService.removeFilter(existingFilter)} + /> + + + ); + } + + return ( + + + + + + ); +}; diff --git a/x-pack/plugins/infra/public/pages/metrics/hosts/components/host_details_flyout/metadata/build_metadata_filter.ts b/x-pack/plugins/infra/public/pages/metrics/hosts/components/host_details_flyout/metadata/build_metadata_filter.ts new file mode 100644 index 0000000000000..8464d34073414 --- /dev/null +++ b/x-pack/plugins/infra/public/pages/metrics/hosts/components/host_details_flyout/metadata/build_metadata_filter.ts @@ -0,0 +1,46 @@ +/* + * Copyright 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 { buildPhrasesFilter, buildPhraseFilter, FilterStateStore } from '@kbn/es-query'; +import type { DataView } from '@kbn/data-views-plugin/common'; + +export function buildMetadataFilter({ + field, + value, + dataView, + negate = false, +}: { + value: string | Array; + negate: boolean; + field: string; + dataView: DataView | undefined; +}) { + if (!dataView) { + return null; + } + const indexField = dataView.getFieldByName(field)!; + const areMultipleValues = Array.isArray(value) && value.length > 1; + const filter = areMultipleValues + ? buildPhrasesFilter(indexField, value, dataView) + : buildPhraseFilter(indexField, Array.isArray(value) ? value[0] : value, dataView); + + filter.meta.type = areMultipleValues ? 'phrases' : 'phrase'; + filter.$state = { store: 'appState' as FilterStateStore.APP_STATE }; + + filter.meta.value = Array.isArray(value) + ? !areMultipleValues + ? `${value[0]}` + : undefined + : value; + + filter.meta.key = field; + filter.meta.alias = null; + filter.meta.negate = negate; + filter.meta.disabled = false; + + return filter; +} diff --git a/x-pack/plugins/infra/public/pages/metrics/hosts/components/host_details_flyout/metadata/table.tsx b/x-pack/plugins/infra/public/pages/metrics/hosts/components/host_details_flyout/metadata/table.tsx index 25aa38cdcb7c5..4cc037335c773 100644 --- a/x-pack/plugins/infra/public/pages/metrics/hosts/components/host_details_flyout/metadata/table.tsx +++ b/x-pack/plugins/infra/public/pages/metrics/hosts/components/host_details_flyout/metadata/table.tsx @@ -20,6 +20,7 @@ import useToggle from 'react-use/lib/useToggle'; import { debounce } from 'lodash'; import { Query } from '@elastic/eui'; import { useHostFlyoutOpen } from '../../../hooks/use_host_flyout_open_url_state'; +import { AddMetadataFilterButton } from './add_metadata_filter_button'; interface Row { name: string; @@ -117,10 +118,19 @@ export const Table = (props: Props) => { { field: 'value', name: VALUE_LABEL, - width: '65%', + width: '55%', sortable: false, render: (_name: string, item: Row) => , }, + { + field: 'value', + name: 'Actions', + sortable: false, + showOnHover: true, + render: (_name: string, item: Row) => { + return ; + }, + }, ], [] ); @@ -132,6 +142,7 @@ export const Table = (props: Props) => { responsive={false} columns={columns} items={rows} + rowProps={{ className: 'euiTableRow-hasActions' }} search={search} loading={loading} error={searchError ? `${searchError.message}` : ''} diff --git a/x-pack/plugins/infra/public/pages/metrics/hosts/hooks/use_hosts_table.test.ts b/x-pack/plugins/infra/public/pages/metrics/hosts/hooks/use_hosts_table.test.ts index 0085b9b923140..4ae8823adaf2e 100644 --- a/x-pack/plugins/infra/public/pages/metrics/hosts/hooks/use_hosts_table.test.ts +++ b/x-pack/plugins/infra/public/pages/metrics/hosts/hooks/use_hosts_table.test.ts @@ -74,7 +74,7 @@ describe('useHostTable hook', () => { name: 'host-0', os: '-', ip: '', - id: 'host-0-0', + id: 'host-0--', title: { cloudProvider: 'aws', name: 'host-0', @@ -105,7 +105,7 @@ describe('useHostTable hook', () => { name: 'host-1', os: 'macOS', ip: '243.86.94.22', - id: 'host-1-1', + id: 'host-1-macOS', title: { cloudProvider: null, name: 'host-1', diff --git a/x-pack/plugins/infra/public/pages/metrics/hosts/hooks/use_hosts_table.tsx b/x-pack/plugins/infra/public/pages/metrics/hosts/hooks/use_hosts_table.tsx index a898c46316648..0b7021b97f84c 100644 --- a/x-pack/plugins/infra/public/pages/metrics/hosts/hooks/use_hosts_table.tsx +++ b/x-pack/plugins/infra/public/pages/metrics/hosts/hooks/use_hosts_table.tsx @@ -50,8 +50,8 @@ const formatMetric = (type: SnapshotMetricInput['type'], value: number | undefin }; const buildItemsList = (nodes: SnapshotNode[]) => { - return nodes.map(({ metrics, path, name }, index) => ({ - id: `${name}-${index}`, + return nodes.map(({ metrics, path, name }) => ({ + id: `${name}-${path.at(-1)?.os ?? '-'}`, name, os: path.at(-1)?.os ?? '-', ip: path.at(-1)?.ip ?? '', diff --git a/x-pack/test/functional/apps/infra/hosts_view.ts b/x-pack/test/functional/apps/infra/hosts_view.ts index 9ef3909691172..b22bb934109dc 100644 --- a/x-pack/test/functional/apps/infra/hosts_view.ts +++ b/x-pack/test/functional/apps/infra/hosts_view.ts @@ -239,9 +239,24 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { await logoutAndDeleteReadOnlyUser(); }); - it('should render metadata tab', async () => { + it('should render metadata tab, add and remove filter', async () => { const metadataTab = await pageObjects.infraHostsView.getMetadataTabName(); expect(metadataTab).to.contain('Metadata'); + + await pageObjects.infraHostsView.clickAddMetadataFilter(); + await pageObjects.infraHome.waitForLoading(); + + // Add Filter + const addedFilter = await pageObjects.infraHostsView.getAppliedFilter(); + expect(addedFilter).to.contain('host.architecture: arm64'); + const removeFilterExists = await pageObjects.infraHostsView.getRemoveFilterExist(); + expect(removeFilterExists).to.be(true); + + // Remove filter + await pageObjects.infraHostsView.clickRemoveMetadataFilter(); + await pageObjects.infraHome.waitForLoading(); + const removeFilterShouldNotExist = await pageObjects.infraHostsView.getRemoveFilterExist(); + expect(removeFilterShouldNotExist).to.be(false); }); it('should navigate to Uptime after click', async () => { diff --git a/x-pack/test/functional/page_objects/infra_hosts_view.ts b/x-pack/test/functional/page_objects/infra_hosts_view.ts index cc8582e43d08b..ae0cc601f8cc7 100644 --- a/x-pack/test/functional/page_objects/infra_hosts_view.ts +++ b/x-pack/test/functional/page_objects/infra_hosts_view.ts @@ -44,6 +44,14 @@ export function InfraHostsViewProvider({ getService }: FtrProviderContext) { return testSubjects.click('hostsView-flyout-apm-services-link'); }, + async clickAddMetadataFilter() { + return testSubjects.click('hostsView-flyout-metadata-add-filter'); + }, + + async clickRemoveMetadataFilter() { + return testSubjects.click('hostsView-flyout-metadata-remove-filter'); + }, + async getHostsLandingPageDisabled() { const container = await testSubjects.find('hostView-no-enable-access'); const containerText = await container.getVisibleText(); @@ -142,6 +150,17 @@ export function InfraHostsViewProvider({ getService }: FtrProviderContext) { return tabTitle.getVisibleText(); }, + async getAppliedFilter() { + const filter = await testSubjects.find( + "filter-badge-'host.architecture: arm64' filter filter-enabled filter-key-host.architecture filter-value-arm64 filter-unpinned filter-id-0" + ); + return filter.getVisibleText(); + }, + + async getRemoveFilterExist() { + return testSubjects.exists('hostsView-flyout-metadata-remove-filter'); + }, + async getProcessesTabContentTitle(index: number) { const processesListElements = await testSubjects.findAll('infraProcessesSummaryTableItem'); return processesListElements[index].findByCssSelector('dt'); From fdf5cdf2d4bba63be9fff79600c2915886978a5f Mon Sep 17 00:00:00 2001 From: Shahzad Date: Wed, 19 Apr 2023 14:39:26 +0200 Subject: [PATCH 36/78] [Synthetics] Waterfall tooltip formatting (#155240) --- .../network_data/data_formatting.test.ts | 38 +++++++++---------- .../common/network_data/data_formatting.ts | 2 +- 2 files changed, 20 insertions(+), 20 deletions(-) diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/step_details_page/common/network_data/data_formatting.test.ts b/x-pack/plugins/synthetics/public/apps/synthetics/components/step_details_page/common/network_data/data_formatting.test.ts index 814785ed6fe6b..1e1112603e67f 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/step_details_page/common/network_data/data_formatting.test.ts +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/step_details_page/common/network_data/data_formatting.test.ts @@ -247,7 +247,7 @@ describe('getSeriesAndDomain', () => { "showTooltip": true, "tooltipProps": Object { "colour": "#b0c9e0", - "value": "Queued / Blocked: 0.854ms", + "value": "Queued / Blocked: 0.9ms", }, }, "x": 0, @@ -262,7 +262,7 @@ describe('getSeriesAndDomain', () => { "showTooltip": true, "tooltipProps": Object { "colour": "#aad9cc", - "value": "DNS: 3.560ms", + "value": "DNS: 4ms", }, }, "x": 0, @@ -277,7 +277,7 @@ describe('getSeriesAndDomain', () => { "showTooltip": true, "tooltipProps": Object { "colour": "#c8b8dc", - "value": "Connecting: 25.721ms", + "value": "Connecting: 26ms", }, }, "x": 0, @@ -292,7 +292,7 @@ describe('getSeriesAndDomain', () => { "showTooltip": true, "tooltipProps": Object { "colour": "#e5c7d7", - "value": "TLS: 55.387ms", + "value": "TLS: 55ms", }, }, "x": 0, @@ -307,7 +307,7 @@ describe('getSeriesAndDomain', () => { "showTooltip": true, "tooltipProps": Object { "colour": "#f3b3a6", - "value": "Sending request: 0.360ms", + "value": "Sending request: 0.4ms", }, }, "x": 0, @@ -322,7 +322,7 @@ describe('getSeriesAndDomain', () => { "showTooltip": true, "tooltipProps": Object { "colour": "#e7664c", - "value": "Waiting (TTFB): 34.578ms", + "value": "Waiting (TTFB): 35ms", }, }, "x": 0, @@ -337,7 +337,7 @@ describe('getSeriesAndDomain', () => { "showTooltip": true, "tooltipProps": Object { "colour": "#9170b8", - "value": "Content downloading (CSS): 0.552ms", + "value": "Content downloading (CSS): 0.6ms", }, }, "x": 0, @@ -352,7 +352,7 @@ describe('getSeriesAndDomain', () => { "showTooltip": true, "tooltipProps": Object { "colour": "#b0c9e0", - "value": "Queued / Blocked: 84.546ms", + "value": "Queued / Blocked: 85ms", }, }, "x": 1, @@ -367,7 +367,7 @@ describe('getSeriesAndDomain', () => { "showTooltip": true, "tooltipProps": Object { "colour": "#f3b3a6", - "value": "Sending request: 0.239ms", + "value": "Sending request: 0.2ms", }, }, "x": 1, @@ -382,7 +382,7 @@ describe('getSeriesAndDomain', () => { "showTooltip": true, "tooltipProps": Object { "colour": "#e7664c", - "value": "Waiting (TTFB): 52.561ms", + "value": "Waiting (TTFB): 53ms", }, }, "x": 1, @@ -397,7 +397,7 @@ describe('getSeriesAndDomain', () => { "showTooltip": true, "tooltipProps": Object { "colour": "#da8b45", - "value": "Content downloading (JS): 3.068ms", + "value": "Content downloading (JS): 3ms", }, }, "x": 1, @@ -420,7 +420,7 @@ describe('getSeriesAndDomain', () => { "showTooltip": true, "tooltipProps": Object { "colour": "#b0c9e0", - "value": "Queued / Blocked: 0.854ms", + "value": "Queued / Blocked: 0.9ms", }, }, "x": 0, @@ -435,7 +435,7 @@ describe('getSeriesAndDomain', () => { "showTooltip": true, "tooltipProps": Object { "colour": "#aad9cc", - "value": "DNS: 3.560ms", + "value": "DNS: 4ms", }, }, "x": 0, @@ -450,7 +450,7 @@ describe('getSeriesAndDomain', () => { "showTooltip": true, "tooltipProps": Object { "colour": "#c8b8dc", - "value": "Connecting: 25.721ms", + "value": "Connecting: 26ms", }, }, "x": 0, @@ -465,7 +465,7 @@ describe('getSeriesAndDomain', () => { "showTooltip": true, "tooltipProps": Object { "colour": "#e5c7d7", - "value": "TLS: 55.387ms", + "value": "TLS: 55ms", }, }, "x": 0, @@ -480,7 +480,7 @@ describe('getSeriesAndDomain', () => { "showTooltip": true, "tooltipProps": Object { "colour": "#f3b3a6", - "value": "Sending request: 0.360ms", + "value": "Sending request: 0.4ms", }, }, "x": 0, @@ -495,7 +495,7 @@ describe('getSeriesAndDomain', () => { "showTooltip": true, "tooltipProps": Object { "colour": "#e7664c", - "value": "Waiting (TTFB): 34.578ms", + "value": "Waiting (TTFB): 35ms", }, }, "x": 0, @@ -510,7 +510,7 @@ describe('getSeriesAndDomain', () => { "showTooltip": true, "tooltipProps": Object { "colour": "#9170b8", - "value": "Content downloading (CSS): 0.552ms", + "value": "Content downloading (CSS): 0.6ms", }, }, "x": 0, @@ -524,7 +524,7 @@ describe('getSeriesAndDomain', () => { "showTooltip": true, "tooltipProps": Object { "colour": "#da8b45", - "value": "Content downloading (JS): 2.793ms", + "value": "Content downloading (JS): 3ms", }, }, "x": 1, diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/step_details_page/common/network_data/data_formatting.ts b/x-pack/plugins/synthetics/public/apps/synthetics/components/step_details_page/common/network_data/data_formatting.ts index 11f8a6ddbd43b..356b7ec1381ca 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/step_details_page/common/network_data/data_formatting.ts +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/step_details_page/common/network_data/data_formatting.ts @@ -55,7 +55,7 @@ const getFriendlyTooltipValue = ({ const formattedMimeType: MimeType = MimeTypesMap[mimeType]; label += ` (${FriendlyMimetypeLabels[formattedMimeType] || mimeType})`; } - return `${label}: ${formatValueForDisplay(value)}ms`; + return `${label}: ${formatValueForDisplay(value, value > 1 ? 0 : 1)}ms`; }; export const isHighlightedItem = ( item: NetworkEvent, From 7e633bb063051f60e5c26a1ed9fa243c2cd5b1bb Mon Sep 17 00:00:00 2001 From: Chris Cowan Date: Wed, 19 Apr 2023 07:03:23 -0600 Subject: [PATCH 37/78] [SLO] Add slo.id and slo.version to Alert-as-data index (#155198) --- .../common/field_names/infra_metrics.ts | 3 +++ .../lib/rules/slo_burn_rate/executor.test.ts | 3 +++ .../lib/rules/slo_burn_rate/executor.ts | 3 +++ .../lib/rules/slo_burn_rate/field_map.ts | 23 +++++++++++++++++++ .../lib/rules/slo_burn_rate/register.ts | 3 ++- x-pack/plugins/observability/server/plugin.ts | 9 +++++--- 6 files changed, 40 insertions(+), 4 deletions(-) create mode 100644 x-pack/plugins/observability/server/lib/rules/slo_burn_rate/field_map.ts diff --git a/x-pack/plugins/observability/common/field_names/infra_metrics.ts b/x-pack/plugins/observability/common/field_names/infra_metrics.ts index 26683dd2a206e..52d6d5d72f151 100644 --- a/x-pack/plugins/observability/common/field_names/infra_metrics.ts +++ b/x-pack/plugins/observability/common/field_names/infra_metrics.ts @@ -9,3 +9,6 @@ export const SYSTEM_CPU_PERCENTAGE_FIELD = 'system.cpu.total.norm.pct'; export const SYSTEM_MEMORY_PERCENTAGE_FIELD = 'system.memory.used.pct'; export const DOCKER_CPU_PERCENTAGE_FIELD = 'docker.cpu.total.pct'; export const K8S_POD_CPU_PERCENTAGE_FIELD = 'kubernetes.pod.cpu.usage.node.pct'; + +export const SLO_ID_FIELD = 'slo.id'; +export const SLO_REVISION_FIELD = 'slo.revision'; diff --git a/x-pack/plugins/observability/server/lib/rules/slo_burn_rate/executor.test.ts b/x-pack/plugins/observability/server/lib/rules/slo_burn_rate/executor.test.ts index 25f0916f18c18..3249a3640e027 100644 --- a/x-pack/plugins/observability/server/lib/rules/slo_burn_rate/executor.test.ts +++ b/x-pack/plugins/observability/server/lib/rules/slo_burn_rate/executor.test.ts @@ -37,6 +37,7 @@ import { BurnRateRuleParams, AlertStates, } from './types'; +import { SLO_ID_FIELD, SLO_REVISION_FIELD } from '../../../../common/field_names/infra_metrics'; const commonEsResponse = { took: 100, @@ -254,6 +255,8 @@ describe('BurnRateRuleExecutor', () => { 'The burn rate for the past 1h is 2 and for the past 5m is 2. Alert when above 2 for both windows', [ALERT_EVALUATION_THRESHOLD]: 2, [ALERT_EVALUATION_VALUE]: 2, + [SLO_ID_FIELD]: slo.id, + [SLO_REVISION_FIELD]: slo.revision, }, }); expect(alertMock.scheduleActions).toBeCalledWith( diff --git a/x-pack/plugins/observability/server/lib/rules/slo_burn_rate/executor.ts b/x-pack/plugins/observability/server/lib/rules/slo_burn_rate/executor.ts index bb7679447d367..33e988b2902af 100644 --- a/x-pack/plugins/observability/server/lib/rules/slo_burn_rate/executor.ts +++ b/x-pack/plugins/observability/server/lib/rules/slo_burn_rate/executor.ts @@ -17,6 +17,7 @@ import { ExecutorType } from '@kbn/alerting-plugin/server'; import { addSpaceIdToPath } from '@kbn/spaces-plugin/server'; import { IBasePath } from '@kbn/core/server'; +import { SLO_ID_FIELD, SLO_REVISION_FIELD } from '../../../../common/field_names/infra_metrics'; import { Duration, toDurationUnit } from '../../../domain/models'; import { DefaultSLIClient, KibanaSavedObjectsSLORepository } from '../../../services/slo'; import { computeBurnRate } from '../../../domain/services'; @@ -125,6 +126,8 @@ export const getRuleExecutor = ({ [ALERT_REASON]: reason, [ALERT_EVALUATION_THRESHOLD]: params.burnRateThreshold, [ALERT_EVALUATION_VALUE]: Math.min(longWindowBurnRate, shortWindowBurnRate), + [SLO_ID_FIELD]: slo.id, + [SLO_REVISION_FIELD]: slo.revision, }, }); diff --git a/x-pack/plugins/observability/server/lib/rules/slo_burn_rate/field_map.ts b/x-pack/plugins/observability/server/lib/rules/slo_burn_rate/field_map.ts new file mode 100644 index 0000000000000..eaff284654155 --- /dev/null +++ b/x-pack/plugins/observability/server/lib/rules/slo_burn_rate/field_map.ts @@ -0,0 +1,23 @@ +/* + * Copyright 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 { SLO_ID_FIELD, SLO_REVISION_FIELD } from '../../../../common/field_names/infra_metrics'; + +export const sloRuleFieldMap = { + [SLO_ID_FIELD]: { + type: 'keyword', + array: false, + required: false, + }, + [SLO_REVISION_FIELD]: { + type: 'long', + array: false, + required: false, + }, +}; + +export type SLORuleFieldMap = typeof sloRuleFieldMap; diff --git a/x-pack/plugins/observability/server/lib/rules/slo_burn_rate/register.ts b/x-pack/plugins/observability/server/lib/rules/slo_burn_rate/register.ts index 8b666bfc263ad..91c0b9f8c8ffd 100644 --- a/x-pack/plugins/observability/server/lib/rules/slo_burn_rate/register.ts +++ b/x-pack/plugins/observability/server/lib/rules/slo_burn_rate/register.ts @@ -16,6 +16,7 @@ import { SLO_RULE_REGISTRATION_CONTEXT } from '../../../common/constants'; import { SLO_BURN_RATE_RULE_ID } from '../../../../common/constants'; import { ALERT_ACTION, getRuleExecutor } from './executor'; +import { sloRuleFieldMap } from './field_map'; const durationSchema = schema.object({ value: schema.number(), @@ -63,7 +64,7 @@ export function sloBurnRateRuleType( }, alerts: { context: SLO_RULE_REGISTRATION_CONTEXT, - mappings: { fieldMap: legacyExperimentalFieldMap }, + mappings: { fieldMap: { ...legacyExperimentalFieldMap, ...sloRuleFieldMap } }, useEcs: true, useLegacyAlerts: true, }, diff --git a/x-pack/plugins/observability/server/plugin.ts b/x-pack/plugins/observability/server/plugin.ts index 2e79509fbef4a..9fdf113cc64af 100644 --- a/x-pack/plugins/observability/server/plugin.ts +++ b/x-pack/plugins/observability/server/plugin.ts @@ -23,7 +23,6 @@ import { getApiTags as getCasesApiTags, } from '@kbn/cases-plugin/common'; import { SpacesPluginSetup } from '@kbn/spaces-plugin/server'; -import { ECS_COMPONENT_TEMPLATE_NAME } from '@kbn/alerting-plugin/server'; import type { GuidedOnboardingPluginSetup } from '@kbn/guided-onboarding-plugin/server'; import { mappingFromFieldMap } from '@kbn/alerting-plugin/common'; @@ -47,6 +46,7 @@ import { SLO_RULE_REGISTRATION_CONTEXT } from './common/constants'; import { registerRuleTypes } from './lib/rules/register_rule_types'; import { SLO_BURN_RATE_RULE_ID } from '../common/constants'; import { registerSloUsageCollector } from './lib/collectors/register'; +import { sloRuleFieldMap } from './lib/rules/slo_burn_rate/field_map'; export type ObservabilityPluginSetup = ReturnType; @@ -223,11 +223,14 @@ export class ObservabilityPlugin implements Plugin { feature: sloFeatureId, registrationContext: SLO_RULE_REGISTRATION_CONTEXT, dataset: Dataset.alerts, - componentTemplateRefs: [ECS_COMPONENT_TEMPLATE_NAME], + componentTemplateRefs: [], componentTemplates: [ { name: 'mappings', - mappings: mappingFromFieldMap(legacyExperimentalFieldMap, 'strict'), + mappings: mappingFromFieldMap( + { ...legacyExperimentalFieldMap, ...sloRuleFieldMap }, + 'strict' + ), }, ], }); From d1dff0b2c78c633011644efb86f5cf421c28692d Mon Sep 17 00:00:00 2001 From: Oliver Gupte Date: Wed, 19 Apr 2023 09:31:15 -0400 Subject: [PATCH 38/78] [APM] Fleet migration support for bundled APM package (#153159) Closes #149342. It accomplishes this by returning the ArchivePackage, unzipped bundled package that includes most of the same fields as the RegistryPackage. These fields are used in APM to support the fleet migration workflow. --- .buildkite/ftr_configs.yml | 1 + .../app/settings/schema/schema_overview.tsx | 2 +- .../routes/fleet/get_latest_apm_package.ts | 15 +- .../routes/fleet/run_migration_check.ts | 20 +- .../create_apm_users/authentication.ts | 11 ++ .../services/epm/package_service.mock.ts | 1 + .../services/epm/package_service.test.ts | 19 ++ .../server/services/epm/package_service.ts | 10 + .../test/apm_api_integration/cloud/config.ts | 10 + .../test/apm_api_integration/common/config.ts | 7 +- .../test/apm_api_integration/configs/index.ts | 8 + .../tests/fleet/apm_package_policy_setup.ts | 10 +- .../tests/fleet/migration_check.spec.ts | 172 ++++++++++++++++++ 13 files changed, 274 insertions(+), 12 deletions(-) create mode 100644 x-pack/test/apm_api_integration/cloud/config.ts create mode 100644 x-pack/test/apm_api_integration/tests/fleet/migration_check.spec.ts diff --git a/.buildkite/ftr_configs.yml b/.buildkite/ftr_configs.yml index baee9753ce56c..ed9b81dba9667 100644 --- a/.buildkite/ftr_configs.yml +++ b/.buildkite/ftr_configs.yml @@ -184,6 +184,7 @@ enabled: - x-pack/test/api_integration/apis/uptime/config.ts - x-pack/test/api_integration/apis/watcher/config.ts - x-pack/test/apm_api_integration/basic/config.ts + - x-pack/test/apm_api_integration/cloud/config.ts - x-pack/test/apm_api_integration/rules/config.ts - x-pack/test/apm_api_integration/trial/config.ts - x-pack/test/banners_functional/config.ts diff --git a/x-pack/plugins/apm/public/components/app/settings/schema/schema_overview.tsx b/x-pack/plugins/apm/public/components/app/settings/schema/schema_overview.tsx index be52301aaaa43..8e30f70c8de61 100644 --- a/x-pack/plugins/apm/public/components/app/settings/schema/schema_overview.tsx +++ b/x-pack/plugins/apm/public/components/app/settings/schema/schema_overview.tsx @@ -248,7 +248,7 @@ function getDisabledReason({ ) ); } - if (!hasCloudAgentPolicy) { + if (hasRequiredRole && !hasCloudAgentPolicy) { reasons.push( i18n.translate( 'xpack.apm.settings.schema.disabledReason.hasCloudAgentPolicy', diff --git a/x-pack/plugins/apm/server/routes/fleet/get_latest_apm_package.ts b/x-pack/plugins/apm/server/routes/fleet/get_latest_apm_package.ts index 671b4d56dad94..43528c037222e 100644 --- a/x-pack/plugins/apm/server/routes/fleet/get_latest_apm_package.ts +++ b/x-pack/plugins/apm/server/routes/fleet/get_latest_apm_package.ts @@ -17,12 +17,19 @@ export async function getLatestApmPackage({ request: KibanaRequest; }) { const packageClient = fleetPluginStart.packageService.asScoped(request); - const { name, version } = await packageClient.fetchFindLatestPackage( + const latestPackage = await packageClient.fetchFindLatestPackage( APM_PACKAGE_NAME ); - const registryPackage = await packageClient.getPackage(name, version); - const { title, policy_templates: policyTemplates } = - registryPackage.packageInfo; + const packageInfo = + 'buffer' in latestPackage + ? (await packageClient.readBundledPackage(latestPackage)).packageInfo + : latestPackage; + const { + name, + version, + title, + policy_templates: policyTemplates, + } = packageInfo; const firstTemplate = policyTemplates?.[0]; const policyTemplateInputVars = firstTemplate && 'inputs' in firstTemplate diff --git a/x-pack/plugins/apm/server/routes/fleet/run_migration_check.ts b/x-pack/plugins/apm/server/routes/fleet/run_migration_check.ts index 3d1a2d740df6e..072d280200850 100644 --- a/x-pack/plugins/apm/server/routes/fleet/run_migration_check.ts +++ b/x-pack/plugins/apm/server/routes/fleet/run_migration_check.ts @@ -49,6 +49,17 @@ export async function runMigrationCheck({ ]); const hasRequiredRole = isSuperuser({ securityPluginStart, request }); + if (!hasRequiredRole) { + return { + has_cloud_agent_policy: false, + has_cloud_apm_package_policy: false, + cloud_apm_migration_enabled: cloudApmMigrationEnabled, + has_required_role: false, + cloud_apm_package_policy: undefined, + has_apm_integrations: false, + latest_apm_package_version: '', + }; + } const cloudAgentPolicy = hasRequiredRole ? await getCloudAgentPolicy({ savedObjectsClient, @@ -57,14 +68,15 @@ export async function runMigrationCheck({ : undefined; const apmPackagePolicy = getApmPackagePolicy(cloudAgentPolicy); const coreStart = await core.start(); - const packagePolicies = await getApmPackagePolicies({ - coreStart, - fleetPluginStart, - }); const latestApmPackage = await getLatestApmPackage({ fleetPluginStart, request, }); + + const packagePolicies = await getApmPackagePolicies({ + coreStart, + fleetPluginStart, + }); return { has_cloud_agent_policy: !!cloudAgentPolicy, has_cloud_apm_package_policy: !!apmPackagePolicy, diff --git a/x-pack/plugins/apm/server/test_helpers/create_apm_users/authentication.ts b/x-pack/plugins/apm/server/test_helpers/create_apm_users/authentication.ts index f4ea0629f848b..fedc98151ce40 100644 --- a/x-pack/plugins/apm/server/test_helpers/create_apm_users/authentication.ts +++ b/x-pack/plugins/apm/server/test_helpers/create_apm_users/authentication.ts @@ -16,6 +16,7 @@ export enum ApmUsername { apmManageOwnAgentKeys = 'apm_manage_own_agent_keys', apmManageOwnAndCreateAgentKeys = 'apm_manage_own_and_create_agent_keys', apmMonitorClusterAndIndices = 'apm_monitor_cluster_and_indices', + apmManageServiceAccount = 'apm_manage_service_account', } export enum ApmCustomRolename { @@ -24,6 +25,7 @@ export enum ApmCustomRolename { apmManageOwnAgentKeys = 'apm_manage_own_agent_keys', apmManageOwnAndCreateAgentKeys = 'apm_manage_own_and_create_agent_keys', apmMonitorClusterAndIndices = 'apm_monitor_cluster_and_indices', + apmManageServiceAccount = 'apm_manage_service_account', } export const customRoles = { @@ -88,6 +90,11 @@ export const customRoles = { cluster: ['monitor'], }, }, + [ApmCustomRolename.apmManageServiceAccount]: { + elasticsearch: { + cluster: ['manage_service_account'], + }, + }, }; export const users: Record< @@ -123,6 +130,10 @@ export const users: Record< builtInRoleNames: ['viewer'], customRoleNames: [ApmCustomRolename.apmMonitorClusterAndIndices], }, + [ApmUsername.apmManageServiceAccount]: { + builtInRoleNames: ['editor'], + customRoleNames: [ApmCustomRolename.apmManageServiceAccount], + }, }; export const APM_TEST_PASSWORD = 'changeme'; diff --git a/x-pack/plugins/fleet/server/services/epm/package_service.mock.ts b/x-pack/plugins/fleet/server/services/epm/package_service.mock.ts index 69e1217b0493c..4007ad7545ece 100644 --- a/x-pack/plugins/fleet/server/services/epm/package_service.mock.ts +++ b/x-pack/plugins/fleet/server/services/epm/package_service.mock.ts @@ -11,6 +11,7 @@ const createClientMock = (): jest.Mocked => ({ getInstallation: jest.fn(), ensureInstalledPackage: jest.fn(), fetchFindLatestPackage: jest.fn(), + readBundledPackage: jest.fn(), getPackage: jest.fn(), getPackages: jest.fn(), reinstallEsAssets: jest.fn(), diff --git a/x-pack/plugins/fleet/server/services/epm/package_service.test.ts b/x-pack/plugins/fleet/server/services/epm/package_service.test.ts index 779f0dad02c8c..aa6f8c81111f5 100644 --- a/x-pack/plugins/fleet/server/services/epm/package_service.test.ts +++ b/x-pack/plugins/fleet/server/services/epm/package_service.test.ts @@ -26,6 +26,7 @@ import * as epmPackagesGet from './packages/get'; import * as epmPackagesInstall from './packages/install'; import * as epmRegistry from './registry'; import * as epmTransformsInstall from './elasticsearch/transform/install'; +import * as epmArchiveParse from './archive/parse'; const testKeys = [ 'getInstallation', @@ -33,6 +34,7 @@ const testKeys = [ 'fetchFindLatestPackage', 'getPackage', 'reinstallEsAssets', + 'readBundledPackage', ]; function getTest( @@ -144,6 +146,23 @@ function getTest( ], }; break; + case testKeys[5]: + const bundledPackage = { name: 'package name', version: '8.0.0', buffer: Buffer.from([]) }; + test = { + method: mocks.packageClient.readBundledPackage.bind(mocks.packageClient), + args: [bundledPackage], + spy: jest.spyOn(epmArchiveParse, 'generatePackageInfoFromArchiveBuffer'), + spyArgs: [bundledPackage.buffer, 'application/zip'], + spyResponse: { + packageInfo: { name: 'readBundledPackage test' }, + paths: ['/some/test/path'], + }, + expectedReturnValue: { + packageInfo: { name: 'readBundledPackage test' }, + paths: ['/some/test/path'], + }, + }; + break; default: throw new Error('invalid test key'); } diff --git a/x-pack/plugins/fleet/server/services/epm/package_service.ts b/x-pack/plugins/fleet/server/services/epm/package_service.ts index b0dd6e9dc38b4..4e820df7a99fc 100644 --- a/x-pack/plugins/fleet/server/services/epm/package_service.ts +++ b/x-pack/plugins/fleet/server/services/epm/package_service.ts @@ -32,6 +32,7 @@ import { installTransforms, isTransform } from './elasticsearch/transform/instal import type { FetchFindLatestPackageOptions } from './registry'; import { fetchFindLatestPackageOrThrow, getPackage } from './registry'; import { ensureInstalledPackage, getInstallation, getPackages } from './packages'; +import { generatePackageInfoFromArchiveBuffer } from './archive'; export type InstalledAssetType = EsAssetReference; @@ -54,6 +55,10 @@ export interface PackageClient { options?: FetchFindLatestPackageOptions ): Promise; + readBundledPackage( + bundledPackage: BundledPackage + ): Promise<{ packageInfo: ArchivePackage; paths: string[] }>; + getPackage( packageName: string, packageVersion: string @@ -137,6 +142,11 @@ class PackageClientImpl implements PackageClient { return fetchFindLatestPackageOrThrow(packageName, options); } + public async readBundledPackage(bundledPackage: BundledPackage) { + await this.#runPreflight(); + return generatePackageInfoFromArchiveBuffer(bundledPackage.buffer, 'application/zip'); + } + public async getPackage( packageName: string, packageVersion: string, diff --git a/x-pack/test/apm_api_integration/cloud/config.ts b/x-pack/test/apm_api_integration/cloud/config.ts new file mode 100644 index 0000000000000..06421969888b5 --- /dev/null +++ b/x-pack/test/apm_api_integration/cloud/config.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 { configs } from '../configs'; + +export default configs.cloud; diff --git a/x-pack/test/apm_api_integration/common/config.ts b/x-pack/test/apm_api_integration/common/config.ts index b1991a837335c..27c615cf4dbe4 100644 --- a/x-pack/test/apm_api_integration/common/config.ts +++ b/x-pack/test/apm_api_integration/common/config.ts @@ -56,7 +56,8 @@ type ApmApiClientKey = | 'noMlAccessUser' | 'manageOwnAgentKeysUser' | 'createAndAllAgentKeysUser' - | 'monitorClusterAndIndicesUser'; + | 'monitorClusterAndIndicesUser' + | 'manageServiceAccount'; export type ApmApiClient = Record>>; @@ -146,6 +147,10 @@ export function createTestConfig( kibanaServer, username: ApmUsername.apmMonitorClusterAndIndices, }), + manageServiceAccount: await getApmApiClient({ + kibanaServer, + username: ApmUsername.apmManageServiceAccount, + }), }; }, ml: MachineLearningAPIProvider, diff --git a/x-pack/test/apm_api_integration/configs/index.ts b/x-pack/test/apm_api_integration/configs/index.ts index eed3a3c9f2443..72dbace5e686b 100644 --- a/x-pack/test/apm_api_integration/configs/index.ts +++ b/x-pack/test/apm_api_integration/configs/index.ts @@ -37,6 +37,14 @@ const apmFtrConfigs = { 'logging.loggers': [apmDebugLogger], }, }, + cloud: { + license: 'basic' as const, + kibanaConfig: { + 'xpack.apm.agent.migrations.enabled': 'true', + 'xpack.apm.forceSyntheticSource': 'true', + 'logging.loggers': [apmDebugLogger], + }, + }, }; export type APMFtrConfigName = keyof typeof apmFtrConfigs; diff --git a/x-pack/test/apm_api_integration/tests/fleet/apm_package_policy_setup.ts b/x-pack/test/apm_api_integration/tests/fleet/apm_package_policy_setup.ts index 34c9fabe143eb..aeb2f00fb9360 100644 --- a/x-pack/test/apm_api_integration/tests/fleet/apm_package_policy_setup.ts +++ b/x-pack/test/apm_api_integration/tests/fleet/apm_package_policy_setup.ts @@ -11,7 +11,7 @@ export function setupFleet(bettertest: BetterTest) { return bettertest({ pathname: '/api/fleet/setup', method: 'post' }); } -export async function createAgentPolicy(bettertest: BetterTest) { +export async function createAgentPolicy(bettertest: BetterTest, id?: string) { const agentPolicyResponse = await bettertest<{ item: AgentPolicy }>({ pathname: '/api/fleet/agent_policies', method: 'post', @@ -19,6 +19,7 @@ export async function createAgentPolicy(bettertest: BetterTest) { body: { name: 'test_agent_policy', description: '', + id, namespace: 'default', monitoring_enabled: ['logs', 'metrics'], }, @@ -27,7 +28,11 @@ export async function createAgentPolicy(bettertest: BetterTest) { return agentPolicyResponse.body.item.id; } -export async function createPackagePolicy(bettertest: BetterTest, agentPolicyId: string) { +export async function createPackagePolicy( + bettertest: BetterTest, + agentPolicyId: string, + id?: string +) { // Get version of available APM package const apmPackageResponse = await bettertest<{ item: any }>({ pathname: `/api/fleet/epm/packages/apm`, @@ -43,6 +48,7 @@ export async function createPackagePolicy(bettertest: BetterTest, agentPolicyId: description: '', namespace: 'default', policy_id: agentPolicyId, + id, enabled: true, inputs: [{ type: 'apm', policy_template: 'apmserver', enabled: true, streams: [], vars: {} }], package: { name: 'apm', title: 'Elastic APM', version: apmPackageVersion }, diff --git a/x-pack/test/apm_api_integration/tests/fleet/migration_check.spec.ts b/x-pack/test/apm_api_integration/tests/fleet/migration_check.spec.ts new file mode 100644 index 0000000000000..1f89f65c536b9 --- /dev/null +++ b/x-pack/test/apm_api_integration/tests/fleet/migration_check.spec.ts @@ -0,0 +1,172 @@ +/* + * Copyright 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 { FtrProviderContext } from '../../common/ftr_provider_context'; +import { + createAgentPolicy, + createPackagePolicy, + deleteAgentPolicy, + deletePackagePolicy, + setupFleet, +} from './apm_package_policy_setup'; +import { getBettertest } from '../../common/bettertest'; + +export default function ApiTest(ftrProviderContext: FtrProviderContext) { + const { getService } = ftrProviderContext; + const registry = getService('registry'); + const supertest = getService('supertest'); + const bettertest = getBettertest(supertest); + const apmApiClient = getService('apmApiClient'); + + registry.when('Fleet migration check - basic', { config: 'basic', archives: [] }, () => { + before(async () => { + await setupFleet(bettertest); + }); + + describe('cloud_apm_migration_enabled', () => { + it('should be false when when config not set', async () => { + const { body } = await bettertest({ + pathname: '/internal/apm/fleet/migration_check', + }); + expect(body).to.have.property('cloud_apm_migration_enabled', false); + }); + }); + }); + + registry.when('Fleet migration check - cloud', { config: 'cloud', archives: [] }, () => { + before(async () => { + await setupFleet(bettertest); + }); + + describe('migration check properties', () => { + it('should contain all expected properties', async () => { + const { status, body } = await bettertest({ + pathname: '/internal/apm/fleet/migration_check', + }); + expect(status).to.equal(200); + expect(body).to.have.property('has_cloud_agent_policy'); + expect(body).to.have.property('has_cloud_apm_package_policy'); + expect(body).to.have.property('cloud_apm_migration_enabled'); + expect(body).to.have.property('has_required_role'); + expect(body).to.have.property('has_apm_integrations'); + expect(body).to.have.property('latest_apm_package_version'); + }); + }); + + describe('cloud_apm_migration_enabled', () => { + it('should be true when when config is set', async () => { + const { body } = await bettertest({ + pathname: '/internal/apm/fleet/migration_check', + }); + expect(body).to.have.property('cloud_apm_migration_enabled', true); + }); + }); + + describe('has_cloud_agent_policy', () => { + it('should be false when cloud agent policy does not exist', async () => { + const { body } = await bettertest({ + pathname: '/internal/apm/fleet/migration_check', + }); + expect(body).to.have.property('has_cloud_agent_policy', false); + }); + describe('with Cloud agent policy', () => { + before(async () => { + await createAgentPolicy(bettertest, 'policy-elastic-agent-on-cloud'); + }); + after(async () => { + await deleteAgentPolicy(bettertest, 'policy-elastic-agent-on-cloud'); + }); + it('should be true when cloud agent policy exists', async () => { + const { body } = await bettertest({ + pathname: '/internal/apm/fleet/migration_check', + }); + expect(body).to.have.property('has_cloud_agent_policy', true); + }); + }); + }); + + describe('has_cloud_apm_package_policy', () => { + before(async () => { + await createAgentPolicy(bettertest, 'policy-elastic-agent-on-cloud'); + }); + after(async () => { + await deleteAgentPolicy(bettertest, 'policy-elastic-agent-on-cloud'); + }); + it('should be false when the Cloud APM package policy does not exist', async () => { + const { body } = await bettertest({ + pathname: '/internal/apm/fleet/migration_check', + }); + expect(body).to.have.property('has_cloud_apm_package_policy', false); + expect(body).to.not.have.property('cloud_apm_package_policy'); + expect(body).to.have.property('has_apm_integrations', false); + }); + describe('with Cloud APM package policy', () => { + before(async () => { + await createPackagePolicy(bettertest, 'policy-elastic-agent-on-cloud', 'apm'); + }); + after(async () => { + await deletePackagePolicy(bettertest, 'apm'); + }); + it('should be true when the Cloud APM package policy exists', async () => { + const { body } = await bettertest({ + pathname: '/internal/apm/fleet/migration_check', + }); + expect(body).to.have.property('has_cloud_apm_package_policy', true); + expect(body).to.have.property('cloud_apm_package_policy'); + expect(body).to.have.property('has_apm_integrations', true); + }); + }); + }); + + describe('has_apm_integrations', () => { + before(async () => { + await createAgentPolicy(bettertest, 'test-agent-policy'); + }); + after(async () => { + await deleteAgentPolicy(bettertest, 'test-agent-policy'); + }); + it('should be false when no APM package policies exist', async () => { + const { body } = await bettertest({ + pathname: '/internal/apm/fleet/migration_check', + }); + expect(body).to.have.property('has_apm_integrations', false); + expect(body).to.have.property('has_cloud_apm_package_policy', false); + }); + describe('with custom APM package policy', () => { + before(async () => { + await createPackagePolicy(bettertest, 'test-agent-policy', 'test-apm-package-policy'); + }); + after(async () => { + await deletePackagePolicy(bettertest, 'test-apm-package-policy'); + }); + it('should be true when any APM package policy exists', async () => { + const { body } = await bettertest({ + pathname: '/internal/apm/fleet/migration_check', + }); + expect(body).to.have.property('has_apm_integrations', true); + expect(body).to.have.property('has_cloud_apm_package_policy', false); + }); + }); + }); + + describe('has_required_role', () => { + it('should be true when user is superuser', async () => { + const { body } = await bettertest({ + pathname: '/internal/apm/fleet/migration_check', + }); + expect(body).to.have.property('has_required_role', true); + }); + it('should be false when user is not superuser', async () => { + const { body } = await apmApiClient.manageServiceAccount({ + endpoint: 'GET /internal/apm/fleet/migration_check', + }); + expect(body).to.have.property('has_required_role', false); + }); + }); + }); +} From 0283b7abd3ca3ef322b0559baf3c9370b2ad203c Mon Sep 17 00:00:00 2001 From: Walter Rafelsberger Date: Wed, 19 Apr 2023 15:51:31 +0200 Subject: [PATCH 39/78] [ML] AIOps: Adds execution context to client side `data.search` requests (#154891) Part of https://github.com/elastic/kibana/issues/147378. This PR adds context information to data.search requests so that they appear in the search slow log. --- .../ml/route_utils/src/create_execution_context.ts | 3 ++- x-pack/packages/ml/route_utils/tsconfig.json | 1 + .../explain_log_rate_spikes_page.tsx | 1 + .../log_categorization/log_categorization_page.tsx | 1 + .../aiops/public/hooks/use_aiops_app_context.ts | 3 ++- x-pack/plugins/aiops/public/hooks/use_data.ts | 11 +++++++++++ .../application/aiops/change_point_detection.tsx | 1 + .../application/aiops/explain_log_rate_spikes.tsx | 1 + .../public/application/aiops/log_categorization.tsx | 1 + 9 files changed, 21 insertions(+), 2 deletions(-) diff --git a/x-pack/packages/ml/route_utils/src/create_execution_context.ts b/x-pack/packages/ml/route_utils/src/create_execution_context.ts index 2aa12d3a13a9c..aa274fb199ed3 100644 --- a/x-pack/packages/ml/route_utils/src/create_execution_context.ts +++ b/x-pack/packages/ml/route_utils/src/create_execution_context.ts @@ -6,6 +6,7 @@ */ import type { CoreStart } from '@kbn/core/server'; +import type { KibanaExecutionContext } from '@kbn/core-execution-context-common'; /** * Creates an execution context to be passed on as part of ES queries. @@ -22,7 +23,7 @@ export function createExecutionContext( name: string, id?: string, type = 'application' -) { +): KibanaExecutionContext { const labels = coreStart.executionContext.getAsLabels(); const page = labels.page as string; return { diff --git a/x-pack/packages/ml/route_utils/tsconfig.json b/x-pack/packages/ml/route_utils/tsconfig.json index ece6bfd9e321a..d7a348f37f8e8 100644 --- a/x-pack/packages/ml/route_utils/tsconfig.json +++ b/x-pack/packages/ml/route_utils/tsconfig.json @@ -17,5 +17,6 @@ ], "kbn_references": [ "@kbn/core", + "@kbn/core-execution-context-common", ] } diff --git a/x-pack/plugins/aiops/public/components/explain_log_rate_spikes/explain_log_rate_spikes_page.tsx b/x-pack/plugins/aiops/public/components/explain_log_rate_spikes/explain_log_rate_spikes_page.tsx index 86082d64e0e27..80640f59901bc 100644 --- a/x-pack/plugins/aiops/public/components/explain_log_rate_spikes/explain_log_rate_spikes_page.tsx +++ b/x-pack/plugins/aiops/public/components/explain_log_rate_spikes/explain_log_rate_spikes_page.tsx @@ -114,6 +114,7 @@ export const ExplainLogRateSpikesPage: FC = () => { { selectedDataView: dataView, selectedSavedSearch }, aiopsListState, setGlobalState, + 'explain_log_rage_spikes', currentSelectedSignificantTerm, currentSelectedGroup ); diff --git a/x-pack/plugins/aiops/public/components/log_categorization/log_categorization_page.tsx b/x-pack/plugins/aiops/public/components/log_categorization/log_categorization_page.tsx index feb6ef9c9b31c..b673d8a498a21 100644 --- a/x-pack/plugins/aiops/public/components/log_categorization/log_categorization_page.tsx +++ b/x-pack/plugins/aiops/public/components/log_categorization/log_categorization_page.tsx @@ -111,6 +111,7 @@ export const LogCategorizationPage: FC = () => { { selectedDataView: dataView, selectedSavedSearch }, aiopsListState, setGlobalState, + 'log_categorization', undefined, undefined, BAR_TARGET diff --git a/x-pack/plugins/aiops/public/hooks/use_aiops_app_context.ts b/x-pack/plugins/aiops/public/hooks/use_aiops_app_context.ts index 92ea33c18e2d3..b9500ecbb6495 100644 --- a/x-pack/plugins/aiops/public/hooks/use_aiops_app_context.ts +++ b/x-pack/plugins/aiops/public/hooks/use_aiops_app_context.ts @@ -8,7 +8,6 @@ import { createContext, useContext } from 'react'; import type { IStorageWrapper } from '@kbn/kibana-utils-plugin/public'; - import type { DataPublicPluginStart } from '@kbn/data-plugin/public'; import type { UnifiedSearchPublicPluginStart } from '@kbn/unified-search-plugin/public'; import type { ChartsPluginStart } from '@kbn/charts-plugin/public'; @@ -17,6 +16,7 @@ import type { SharePluginStart } from '@kbn/share-plugin/public'; import type { CoreStart, CoreSetup, + ExecutionContextStart, HttpStart, IUiSettingsClient, ThemeServiceStart, @@ -26,6 +26,7 @@ import type { LensPublicStart } from '@kbn/lens-plugin/public'; export interface AiopsAppDependencies { application: CoreStart['application']; data: DataPublicPluginStart; + executionContext: ExecutionContextStart; charts: ChartsPluginStart; fieldFormats: FieldFormatsStart; http: HttpStart; diff --git a/x-pack/plugins/aiops/public/hooks/use_data.ts b/x-pack/plugins/aiops/public/hooks/use_data.ts index 82db675a94c4f..c390582ccae1a 100644 --- a/x-pack/plugins/aiops/public/hooks/use_data.ts +++ b/x-pack/plugins/aiops/public/hooks/use_data.ts @@ -8,12 +8,15 @@ import { useEffect, useMemo, useState } from 'react'; import { merge } from 'rxjs'; +import { useExecutionContext } from '@kbn/kibana-react-plugin/public'; import type { DataView } from '@kbn/data-views-plugin/public'; import type { SignificantTerm } from '@kbn/ml-agg-utils'; import type { SavedSearch } from '@kbn/discover-plugin/public'; import type { Dictionary } from '@kbn/ml-url-state'; import { mlTimefilterRefresh$, useTimefilter } from '@kbn/ml-date-picker'; +import { PLUGIN_ID } from '../../common'; + import type { DocumentStatsSearchStrategyParams } from '../get_document_stats'; import type { AiOpsIndexBasedAppState } from '../components/explain_log_rate_spikes/explain_log_rate_spikes_app_state'; import { getEsQueryFromSavedSearch } from '../application/utils/search_utils'; @@ -33,17 +36,25 @@ export const useData = ( }: { selectedDataView: DataView; selectedSavedSearch: SavedSearch | null }, aiopsListState: AiOpsIndexBasedAppState, onUpdate: (params: Dictionary) => void, + contextId: string, selectedSignificantTerm?: SignificantTerm, selectedGroup?: GroupTableItem | null, barTarget: number = DEFAULT_BAR_TARGET ) => { const { + executionContext, uiSettings, data: { query: { filterManager }, }, } = useAiopsAppContext(); + useExecutionContext(executionContext, { + name: PLUGIN_ID, + type: 'application', + id: contextId, + }); + const [lastRefresh, setLastRefresh] = useState(0); /** Prepare required params to pass to search strategy **/ diff --git a/x-pack/plugins/ml/public/application/aiops/change_point_detection.tsx b/x-pack/plugins/ml/public/application/aiops/change_point_detection.tsx index 77642001757ab..f98e9a3d3c852 100644 --- a/x-pack/plugins/ml/public/application/aiops/change_point_detection.tsx +++ b/x-pack/plugins/ml/public/application/aiops/change_point_detection.tsx @@ -49,6 +49,7 @@ export const ChangePointDetectionPage: FC = () => { appDependencies={pick(services, [ 'application', 'data', + 'executionContext', 'charts', 'fieldFormats', 'http', diff --git a/x-pack/plugins/ml/public/application/aiops/explain_log_rate_spikes.tsx b/x-pack/plugins/ml/public/application/aiops/explain_log_rate_spikes.tsx index 031297c55929a..ce1f54c64143e 100644 --- a/x-pack/plugins/ml/public/application/aiops/explain_log_rate_spikes.tsx +++ b/x-pack/plugins/ml/public/application/aiops/explain_log_rate_spikes.tsx @@ -49,6 +49,7 @@ export const ExplainLogRateSpikesPage: FC = () => { appDependencies={pick(services, [ 'application', 'data', + 'executionContext', 'charts', 'fieldFormats', 'http', diff --git a/x-pack/plugins/ml/public/application/aiops/log_categorization.tsx b/x-pack/plugins/ml/public/application/aiops/log_categorization.tsx index cdc0c079d541c..468afb044cb75 100644 --- a/x-pack/plugins/ml/public/application/aiops/log_categorization.tsx +++ b/x-pack/plugins/ml/public/application/aiops/log_categorization.tsx @@ -49,6 +49,7 @@ export const LogCategorizationPage: FC = () => { appDependencies={pick(services, [ 'application', 'data', + 'executionContext', 'charts', 'fieldFormats', 'http', From fce3664397fe71db2062d0c77e64af8204b18357 Mon Sep 17 00:00:00 2001 From: Carlos Crespo Date: Wed, 19 Apr 2023 16:04:51 +0200 Subject: [PATCH 40/78] [Infrastructure UI] KPI charts with Lens Embeddable (#154903) ## Summary This PR replaces all but Host Count metric charts with Lens and aligns the formulas used in Lens with the formulas used in the Snapshot API. The Hosts Count was not converted yet, because Snapshot API [post-process the results and filters out hosts that don't have metrics and come from APM](https://github.com/elastic/kibana/blob/47c71b30253cf1bdd1339af5f471a6d747871c64/x-pack/plugins/infra/server/routes/snapshot/lib/transform_metrics_ui_response.ts#L61). I decided to keep it unchanged until we start using a new API, which won't have that step. In order to avoid multiple requests going to the server simultaneously, I introduced an intersection observer in the Lens chart component. It will make them only trigger a request once the user has scrolled over a chart component. This aims to prevent the occurrence of `circuit_breaking_exception` exceptions when async_search has to process too much data. ### Differences between Lens and current KPI charts image Currently, Lens doesn't provide an option for adding a suffix to the metric value nor set a max number of decimal places. (e.g (e.g 3.8 Mbit **/s**) ### For reviewer Unfortunately, it's a big PR. Everything in the `lens/formulas` folder is just related to the metric formulas that are reused between KPIs and Metrics Charts. The core of the changes is in the `lens/visualization_types` folder. It contains the details on how to build different objects to render either a metric type of chart or a lineXY one. I have also aligned `cpu` and `memory` formulas with what we have in the Inventory Model. When comparing the current KPI with the new ones in Lens Embeddable, the results were not matching. --------- Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../public/common/visualizations/constants.ts | 36 ++++ .../public/common/visualizations/index.ts | 27 ++- ...ualization.ts => build_lens_attributes.ts} | 10 +- .../visualizations/lens/formulas/host/cpu.ts | 36 ++++ .../lens/formulas/host/diskio_read.ts | 23 +++ .../lens/formulas/host/diskio_write.ts | 23 +++ .../lens/formulas/host/host_count.ts | 23 +++ .../lens/formulas/host/index.ts | 16 ++ .../visualizations/lens/formulas/host/load.ts | 76 +++++++++ .../lens/formulas/host/memory.ts | 34 ++++ .../lens/formulas/host/memory_available.ts | 23 +++ .../visualizations/lens/formulas/host/rx.ts | 24 +++ .../visualizations/lens/formulas/host/tx.ts | 24 +++ .../lens/formulas/host/utils.ts | 21 +++ .../common/visualizations/lens/hosts/cpu.ts | 97 ----------- .../visualizations/lens/hosts/diskio_read.ts | 93 ----------- .../visualizations/lens/hosts/diskio_write.ts | 94 ----------- .../common/visualizations/lens/hosts/index.ts | 15 -- .../common/visualizations/lens/hosts/load.ts | 144 ---------------- .../visualizations/lens/hosts/memory.ts | 97 ----------- .../lens/hosts/memory_available.ts | 91 ----------- .../common/visualizations/lens/hosts/rx.ts | 92 ----------- .../common/visualizations/lens/hosts/tx.ts | 92 ----------- .../common/visualizations/lens/types.ts | 19 --- .../common/visualizations/lens/utils.ts | 71 +++----- .../lens/visualization_types/index.ts | 9 + .../lens/visualization_types/line_chart.ts | 154 ++++++++++++++++++ .../lens/visualization_types/metric_chart.ts | 139 ++++++++++++++++ .../public/common/visualizations/types.ts | 63 +++++++ .../public/hooks/use_intersection_once.ts | 25 +++ .../public/hooks/use_lens_attributes.test.ts | 46 ++++-- .../infra/public/hooks/use_lens_attributes.ts | 71 +++++--- .../hosts/components/chart/lens_wrapper.tsx | 93 +++++++++++ .../metric_chart_wrapper.tsx} | 70 +++++--- .../hosts/components/hosts_container.tsx | 4 +- .../metrics/hosts/components/hosts_table.tsx | 2 +- .../components/kpi_charts/kpi_charts.tsx | 154 ------------------ .../hosts/components/kpi_charts/tile.tsx | 32 ---- .../{kpi_charts => kpis}/hosts_tile.tsx | 6 +- .../hosts/components/kpis/kpi_grid.tsx | 115 +++++++++++++ .../metrics/hosts/components/kpis/tile.tsx | 137 ++++++++++++++++ .../components/tabs/metrics/metric_chart.tsx | 57 +++---- x-pack/plugins/infra/public/types.ts | 8 +- .../translations/translations/fr-FR.json | 10 +- .../translations/translations/ja-JP.json | 10 +- .../translations/translations/zh-CN.json | 10 +- .../test/functional/apps/infra/hosts_view.ts | 12 +- 47 files changed, 1301 insertions(+), 1227 deletions(-) create mode 100644 x-pack/plugins/infra/public/common/visualizations/constants.ts rename x-pack/plugins/infra/public/common/visualizations/lens/{lens_visualization.ts => build_lens_attributes.ts} (69%) create mode 100644 x-pack/plugins/infra/public/common/visualizations/lens/formulas/host/cpu.ts create mode 100644 x-pack/plugins/infra/public/common/visualizations/lens/formulas/host/diskio_read.ts create mode 100644 x-pack/plugins/infra/public/common/visualizations/lens/formulas/host/diskio_write.ts create mode 100644 x-pack/plugins/infra/public/common/visualizations/lens/formulas/host/host_count.ts create mode 100644 x-pack/plugins/infra/public/common/visualizations/lens/formulas/host/index.ts create mode 100644 x-pack/plugins/infra/public/common/visualizations/lens/formulas/host/load.ts create mode 100644 x-pack/plugins/infra/public/common/visualizations/lens/formulas/host/memory.ts create mode 100644 x-pack/plugins/infra/public/common/visualizations/lens/formulas/host/memory_available.ts create mode 100644 x-pack/plugins/infra/public/common/visualizations/lens/formulas/host/rx.ts create mode 100644 x-pack/plugins/infra/public/common/visualizations/lens/formulas/host/tx.ts create mode 100644 x-pack/plugins/infra/public/common/visualizations/lens/formulas/host/utils.ts delete mode 100644 x-pack/plugins/infra/public/common/visualizations/lens/hosts/cpu.ts delete mode 100644 x-pack/plugins/infra/public/common/visualizations/lens/hosts/diskio_read.ts delete mode 100644 x-pack/plugins/infra/public/common/visualizations/lens/hosts/diskio_write.ts delete mode 100644 x-pack/plugins/infra/public/common/visualizations/lens/hosts/index.ts delete mode 100644 x-pack/plugins/infra/public/common/visualizations/lens/hosts/load.ts delete mode 100644 x-pack/plugins/infra/public/common/visualizations/lens/hosts/memory.ts delete mode 100644 x-pack/plugins/infra/public/common/visualizations/lens/hosts/memory_available.ts delete mode 100644 x-pack/plugins/infra/public/common/visualizations/lens/hosts/rx.ts delete mode 100644 x-pack/plugins/infra/public/common/visualizations/lens/hosts/tx.ts delete mode 100644 x-pack/plugins/infra/public/common/visualizations/lens/types.ts create mode 100644 x-pack/plugins/infra/public/common/visualizations/lens/visualization_types/index.ts create mode 100644 x-pack/plugins/infra/public/common/visualizations/lens/visualization_types/line_chart.ts create mode 100644 x-pack/plugins/infra/public/common/visualizations/lens/visualization_types/metric_chart.ts create mode 100644 x-pack/plugins/infra/public/common/visualizations/types.ts create mode 100644 x-pack/plugins/infra/public/hooks/use_intersection_once.ts create mode 100644 x-pack/plugins/infra/public/pages/metrics/hosts/components/chart/lens_wrapper.tsx rename x-pack/plugins/infra/public/pages/metrics/hosts/components/{kpi_charts/kpi_chart.tsx => chart/metric_chart_wrapper.tsx} (65%) delete mode 100644 x-pack/plugins/infra/public/pages/metrics/hosts/components/kpi_charts/kpi_charts.tsx delete mode 100644 x-pack/plugins/infra/public/pages/metrics/hosts/components/kpi_charts/tile.tsx rename x-pack/plugins/infra/public/pages/metrics/hosts/components/{kpi_charts => kpis}/hosts_tile.tsx (81%) create mode 100644 x-pack/plugins/infra/public/pages/metrics/hosts/components/kpis/kpi_grid.tsx create mode 100644 x-pack/plugins/infra/public/pages/metrics/hosts/components/kpis/tile.tsx diff --git a/x-pack/plugins/infra/public/common/visualizations/constants.ts b/x-pack/plugins/infra/public/common/visualizations/constants.ts new file mode 100644 index 0000000000000..3ea98953c3c4c --- /dev/null +++ b/x-pack/plugins/infra/public/common/visualizations/constants.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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { + cpu, + diskIORead, + diskIOWrite, + load, + memory, + memoryAvailable, + rx, + tx, + hostCount, +} from './lens/formulas/host'; +import { LineChart, MetricChart } from './lens/visualization_types'; + +export const hostLensFormulas = { + cpu, + diskIORead, + diskIOWrite, + hostCount, + load, + memory, + memoryAvailable, + rx, + tx, +}; + +export const visualizationTypes = { + lineChart: LineChart, + metricChart: MetricChart, +}; diff --git a/x-pack/plugins/infra/public/common/visualizations/index.ts b/x-pack/plugins/infra/public/common/visualizations/index.ts index 9f933baa0edc6..cba62658e2e8a 100644 --- a/x-pack/plugins/infra/public/common/visualizations/index.ts +++ b/x-pack/plugins/infra/public/common/visualizations/index.ts @@ -5,19 +5,18 @@ * 2.0. */ -import { CPU, Load, Memory, MemoryAvailable, RX, TX, DiskIORead, DiskIOWrite } from './lens/hosts'; +export type { + HostsLensFormulas, + LineChartOptions, + LensChartConfig, + LensLineChartConfig, + MetricChartOptions, + HostsLensMetricChartFormulas, + HostsLensLineChartFormulas, + LensOptions, + LensAttributes, +} from './types'; -export { buildLensAttributes } from './lens/lens_visualization'; +export { hostLensFormulas, visualizationTypes } from './constants'; -export const hostMetricsLensAttributes = { - cpu: CPU, - load: Load, - memory: Memory, - memoryAvailable: MemoryAvailable, - rx: RX, - tx: TX, - diskIORead: DiskIORead, - diskIOWrite: DiskIOWrite, -}; - -export type HostLensAttributesTypes = keyof typeof hostMetricsLensAttributes; +export { buildLensAttributes } from './lens/build_lens_attributes'; diff --git a/x-pack/plugins/infra/public/common/visualizations/lens/lens_visualization.ts b/x-pack/plugins/infra/public/common/visualizations/lens/build_lens_attributes.ts similarity index 69% rename from x-pack/plugins/infra/public/common/visualizations/lens/lens_visualization.ts rename to x-pack/plugins/infra/public/common/visualizations/lens/build_lens_attributes.ts index 46f33718d405c..6c5adb7bce203 100644 --- a/x-pack/plugins/infra/public/common/visualizations/lens/lens_visualization.ts +++ b/x-pack/plugins/infra/public/common/visualizations/lens/build_lens_attributes.ts @@ -4,10 +4,11 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import type { LensAttributes } from '../../../types'; -import type { ILensVisualization } from './types'; +import type { LensAttributes, TVisualization, VisualizationAttributes } from '../types'; -export const buildLensAttributes = (visualization: ILensVisualization): LensAttributes => { +export const buildLensAttributes = >( + visualization: T +): LensAttributes => { return { title: visualization.getTitle(), visualizationType: visualization.getVisualizationType(), @@ -18,7 +19,8 @@ export const buildLensAttributes = (visualization: ILensVisualization): LensAttr layers: visualization.getLayers(), }, }, - filters: [], + internalReferences: visualization.getReferences(), + filters: visualization.getFilters(), query: { language: 'kuery', query: '' }, visualization: visualization.getVisualizationState(), adHocDataViews: visualization.getAdhocDataView(), diff --git a/x-pack/plugins/infra/public/common/visualizations/lens/formulas/host/cpu.ts b/x-pack/plugins/infra/public/common/visualizations/lens/formulas/host/cpu.ts new file mode 100644 index 0000000000000..f4d60c15a5317 --- /dev/null +++ b/x-pack/plugins/infra/public/common/visualizations/lens/formulas/host/cpu.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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { LensChartConfig, LensLineChartConfig } from '../../../types'; +import { getFilters } from './utils'; + +export const cpuLineChart: LensLineChartConfig = { + extraVisualizationState: { + yLeftExtent: { + mode: 'custom', + lowerBound: 0, + upperBound: 1, + }, + }, +}; + +export const cpu: LensChartConfig = { + title: 'CPU Usage', + formula: { + formula: + '(average(system.cpu.user.pct) + average(system.cpu.system.pct)) / max(system.cpu.cores)', + format: { + id: 'percent', + params: { + decimals: 0, + }, + }, + }, + getFilters, + + lineChartConfig: cpuLineChart, +}; diff --git a/x-pack/plugins/infra/public/common/visualizations/lens/formulas/host/diskio_read.ts b/x-pack/plugins/infra/public/common/visualizations/lens/formulas/host/diskio_read.ts new file mode 100644 index 0000000000000..344cff8a82f4a --- /dev/null +++ b/x-pack/plugins/infra/public/common/visualizations/lens/formulas/host/diskio_read.ts @@ -0,0 +1,23 @@ +/* + * Copyright 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 { LensChartConfig } from '../../../types'; +import { getFilters } from './utils'; + +export const diskIORead: LensChartConfig = { + title: 'Disk Read IOPS', + formula: { + formula: "counter_rate(max(system.diskio.read.bytes), kql='system.diskio.read.bytes: *')", + format: { + id: 'bytes', + params: { + decimals: 1, + }, + }, + }, + getFilters, +}; diff --git a/x-pack/plugins/infra/public/common/visualizations/lens/formulas/host/diskio_write.ts b/x-pack/plugins/infra/public/common/visualizations/lens/formulas/host/diskio_write.ts new file mode 100644 index 0000000000000..f0d1d2cd718b2 --- /dev/null +++ b/x-pack/plugins/infra/public/common/visualizations/lens/formulas/host/diskio_write.ts @@ -0,0 +1,23 @@ +/* + * Copyright 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 { LensChartConfig } from '../../../types'; +import { getFilters } from './utils'; + +export const diskIOWrite: LensChartConfig = { + title: 'Disk Write IOPS', + formula: { + formula: "counter_rate(max(system.diskio.write.bytes), kql='system.diskio.write.bytes: *')", + format: { + id: 'bytes', + params: { + decimals: 1, + }, + }, + }, + getFilters, +}; diff --git a/x-pack/plugins/infra/public/common/visualizations/lens/formulas/host/host_count.ts b/x-pack/plugins/infra/public/common/visualizations/lens/formulas/host/host_count.ts new file mode 100644 index 0000000000000..4f0d230176368 --- /dev/null +++ b/x-pack/plugins/infra/public/common/visualizations/lens/formulas/host/host_count.ts @@ -0,0 +1,23 @@ +/* + * Copyright 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 { LensChartConfig } from '../../../types'; +import { getFilters } from './utils'; + +export const hostCount: LensChartConfig = { + title: 'Hosts', + formula: { + formula: 'unique_count(host.name)', + format: { + id: 'number', + params: { + decimals: 0, + }, + }, + }, + getFilters, +}; diff --git a/x-pack/plugins/infra/public/common/visualizations/lens/formulas/host/index.ts b/x-pack/plugins/infra/public/common/visualizations/lens/formulas/host/index.ts new file mode 100644 index 0000000000000..8b15f4308cebc --- /dev/null +++ b/x-pack/plugins/infra/public/common/visualizations/lens/formulas/host/index.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. + */ + +export { cpu } from './cpu'; +export { diskIORead } from './diskio_read'; +export { diskIOWrite } from './diskio_write'; +export { hostCount } from './host_count'; +export { load } from './load'; +export { memory } from './memory'; +export { memoryAvailable } from './memory_available'; +export { rx } from './rx'; +export { tx } from './tx'; diff --git a/x-pack/plugins/infra/public/common/visualizations/lens/formulas/host/load.ts b/x-pack/plugins/infra/public/common/visualizations/lens/formulas/host/load.ts new file mode 100644 index 0000000000000..c4e349fe01aaf --- /dev/null +++ b/x-pack/plugins/infra/public/common/visualizations/lens/formulas/host/load.ts @@ -0,0 +1,76 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { ReferenceBasedIndexPatternColumn } from '@kbn/lens-plugin/public/datasources/form_based/operations/definitions/column_types'; +import type { LensChartConfig, LensLineChartConfig } from '../../../types'; +import { getFilters } from './utils'; + +const REFERENCE_LAYER = 'referenceLayer'; + +export const loadLineChart: LensLineChartConfig = { + extraLayers: { + [REFERENCE_LAYER]: { + linkToLayers: [], + columnOrder: ['referenceColumn'], + columns: { + referenceColumn: { + label: 'Reference', + dataType: 'number', + operationType: 'static_value', + isStaticValue: true, + isBucketed: false, + scale: 'ratio', + params: { + value: 1, + format: { + id: 'percent', + params: { + decimals: 0, + }, + }, + }, + references: [], + customLabel: true, + } as ReferenceBasedIndexPatternColumn, + }, + sampling: 1, + incompleteColumns: {}, + }, + }, + extraVisualizationState: { + layers: [ + { + layerId: REFERENCE_LAYER, + layerType: 'referenceLine', + accessors: ['referenceColumn'], + yConfig: [ + { + forAccessor: 'referenceColumn', + axisMode: 'left', + color: '#6092c0', + }, + ], + }, + ], + }, + extraReference: REFERENCE_LAYER, +}; + +export const load: LensChartConfig = { + title: 'Normalized Load', + formula: { + formula: 'average(system.load.1) / max(system.load.cores)', + format: { + id: 'percent', + params: { + decimals: 0, + }, + }, + }, + getFilters, + lineChartConfig: loadLineChart, +}; diff --git a/x-pack/plugins/infra/public/common/visualizations/lens/formulas/host/memory.ts b/x-pack/plugins/infra/public/common/visualizations/lens/formulas/host/memory.ts new file mode 100644 index 0000000000000..413b9aa99001f --- /dev/null +++ b/x-pack/plugins/infra/public/common/visualizations/lens/formulas/host/memory.ts @@ -0,0 +1,34 @@ +/* + * Copyright 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 { LensChartConfig, LensLineChartConfig } from '../../../types'; +import { getFilters } from './utils'; + +const memoryLineChart: LensLineChartConfig = { + extraVisualizationState: { + yLeftExtent: { + mode: 'custom', + lowerBound: 0, + upperBound: 1, + }, + }, +}; + +export const memory: LensChartConfig = { + title: 'Memory', + formula: { + formula: 'average(system.memory.actual.used.pct)', + format: { + id: 'percent', + params: { + decimals: 0, + }, + }, + }, + lineChartConfig: memoryLineChart, + getFilters, +}; diff --git a/x-pack/plugins/infra/public/common/visualizations/lens/formulas/host/memory_available.ts b/x-pack/plugins/infra/public/common/visualizations/lens/formulas/host/memory_available.ts new file mode 100644 index 0000000000000..69cea10f423ef --- /dev/null +++ b/x-pack/plugins/infra/public/common/visualizations/lens/formulas/host/memory_available.ts @@ -0,0 +1,23 @@ +/* + * Copyright 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 { LensChartConfig } from '../../../types'; +import { getFilters } from './utils'; + +export const memoryAvailable: LensChartConfig = { + title: 'Memory Available', + formula: { + formula: 'max(system.memory.total) - average(system.memory.actual.used.bytes)', + format: { + id: 'bytes', + params: { + decimals: 1, + }, + }, + }, + getFilters, +}; diff --git a/x-pack/plugins/infra/public/common/visualizations/lens/formulas/host/rx.ts b/x-pack/plugins/infra/public/common/visualizations/lens/formulas/host/rx.ts new file mode 100644 index 0000000000000..b396dffb979e6 --- /dev/null +++ b/x-pack/plugins/infra/public/common/visualizations/lens/formulas/host/rx.ts @@ -0,0 +1,24 @@ +/* + * Copyright 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 { LensChartConfig } from '../../../types'; +import { getFilters } from './utils'; + +export const rx: LensChartConfig = { + title: 'Network Inbound (RX)', + formula: { + formula: + "average(host.network.ingress.bytes) * 8 / (max(metricset.period, kql='host.network.ingress.bytes: *') / 1000)", + format: { + id: 'bits', + params: { + decimals: 1, + }, + }, + }, + getFilters, +}; diff --git a/x-pack/plugins/infra/public/common/visualizations/lens/formulas/host/tx.ts b/x-pack/plugins/infra/public/common/visualizations/lens/formulas/host/tx.ts new file mode 100644 index 0000000000000..f9f97a8ed9112 --- /dev/null +++ b/x-pack/plugins/infra/public/common/visualizations/lens/formulas/host/tx.ts @@ -0,0 +1,24 @@ +/* + * Copyright 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 { LensChartConfig } from '../../../types'; +import { getFilters } from './utils'; + +export const tx: LensChartConfig = { + title: 'Network Outbound (TX)', + formula: { + formula: + "average(host.network.egress.bytes) * 8 / (max(metricset.period, kql='host.network.egress.bytes: *') / 1000)", + format: { + id: 'bits', + params: { + decimals: 1, + }, + }, + }, + getFilters, +}; diff --git a/x-pack/plugins/infra/public/common/visualizations/lens/formulas/host/utils.ts b/x-pack/plugins/infra/public/common/visualizations/lens/formulas/host/utils.ts new file mode 100644 index 0000000000000..c0b16ca705b13 --- /dev/null +++ b/x-pack/plugins/infra/public/common/visualizations/lens/formulas/host/utils.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 { DataViewBase } from '@kbn/es-query'; + +export const getFilters = ({ id }: Pick) => [ + { + meta: { + index: id, + }, + query: { + exists: { + field: 'host.name', + }, + }, + }, +]; diff --git a/x-pack/plugins/infra/public/common/visualizations/lens/hosts/cpu.ts b/x-pack/plugins/infra/public/common/visualizations/lens/hosts/cpu.ts deleted file mode 100644 index 00ed6c799dda0..0000000000000 --- a/x-pack/plugins/infra/public/common/visualizations/lens/hosts/cpu.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 { - FormBasedLayer, - FormulaPublicApi, - PersistedIndexPatternLayer, - XYState, -} from '@kbn/lens-plugin/public'; -import type { DataView } from '@kbn/data-views-plugin/public'; -import { - DEFAULT_LAYER_ID, - getAdhocDataView, - getBreakdownColumn, - getDefaultReferences, - getHistogramColumn, - getXYVisualizationState, -} from '../utils'; -import type { LensOptions } from '../../../../types'; -import type { ILensVisualization } from '../types'; - -const BREAKDOWN_COLUMN_NAME = 'hosts_aggs_breakdown'; -const HISTOGRAM_COLUMN_NAME = 'x_date_histogram'; - -export class CPU implements ILensVisualization { - constructor( - private dataView: DataView, - private options: LensOptions, - private formula: FormulaPublicApi - ) {} - - getTitle(): string { - return 'CPU Usage'; - } - - getVisualizationType(): string { - return 'lnsXY'; - } - - getLayers = (): Record> => { - const baseLayer: PersistedIndexPatternLayer = { - columnOrder: [BREAKDOWN_COLUMN_NAME, HISTOGRAM_COLUMN_NAME], - columns: { - ...getBreakdownColumn(BREAKDOWN_COLUMN_NAME, 'host.name', this.options.breakdownSize), - ...getHistogramColumn(HISTOGRAM_COLUMN_NAME, this.dataView.timeFieldName ?? '@timestamp'), - }, - }; - - const dataLayer = this.formula.insertOrReplaceFormulaColumn( - 'y_cpu_usage', - { - formula: 'average(system.cpu.total.norm.pct)', - format: { - id: 'percent', - params: { - decimals: 0, - }, - }, - }, - baseLayer, - this.dataView - ); - - if (!dataLayer) { - throw new Error('Error generating the data layer for the chart'); - } - - return { [DEFAULT_LAYER_ID]: dataLayer }; - }; - getVisualizationState = (): XYState => { - return getXYVisualizationState({ - layers: [ - { - layerId: DEFAULT_LAYER_ID, - seriesType: 'line', - accessors: ['y_cpu_usage'], - yConfig: [], - layerType: 'data', - xAccessor: HISTOGRAM_COLUMN_NAME, - splitAccessor: BREAKDOWN_COLUMN_NAME, - }, - ], - yLeftExtent: { - mode: 'custom', - lowerBound: 0, - upperBound: 1, - }, - }); - }; - - getReferences = () => getDefaultReferences(this.dataView, DEFAULT_LAYER_ID); - getAdhocDataView = () => getAdhocDataView(this.dataView); -} diff --git a/x-pack/plugins/infra/public/common/visualizations/lens/hosts/diskio_read.ts b/x-pack/plugins/infra/public/common/visualizations/lens/hosts/diskio_read.ts deleted file mode 100644 index 768ae9a640075..0000000000000 --- a/x-pack/plugins/infra/public/common/visualizations/lens/hosts/diskio_read.ts +++ /dev/null @@ -1,93 +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 { - FormBasedLayer, - FormulaPublicApi, - PersistedIndexPatternLayer, - XYState, -} from '@kbn/lens-plugin/public'; -import type { DataView } from '@kbn/data-views-plugin/public'; -import type { LensOptions } from '../../../../types'; -import { - DEFAULT_LAYER_ID, - getAdhocDataView, - getBreakdownColumn, - getDefaultReferences, - getHistogramColumn, - getXYVisualizationState, -} from '../utils'; -import type { ILensVisualization } from '../types'; - -const BREAKDOWN_COLUMN_NAME = 'hosts_aggs_breakdown'; -const HISTOGRAM_COLUMN_NAME = 'x_date_histogram'; - -export class DiskIORead implements ILensVisualization { - constructor( - private dataView: DataView, - private options: LensOptions, - private formula: FormulaPublicApi - ) {} - - getTitle(): string { - return 'Disk Read IOPS'; - } - - getVisualizationType(): string { - return 'lnsXY'; - } - - getLayers = (): Record> => { - const baseLayer: PersistedIndexPatternLayer = { - columnOrder: [BREAKDOWN_COLUMN_NAME, HISTOGRAM_COLUMN_NAME], - columns: { - ...getBreakdownColumn(BREAKDOWN_COLUMN_NAME, 'host.name', this.options.breakdownSize), - ...getHistogramColumn(HISTOGRAM_COLUMN_NAME, this.dataView.timeFieldName ?? '@timestamp'), - }, - }; - - const dataLayer = this.formula.insertOrReplaceFormulaColumn( - 'y_diskio_read', - { - formula: "counter_rate(max(system.diskio.read.bytes), kql='system.diskio.read.bytes >= 0')", - format: { - id: 'bytes', - params: { - decimals: 1, - }, - }, - }, - baseLayer, - this.dataView - ); - - if (!dataLayer) { - throw new Error('Error generating the data layer for the chart'); - } - - return { [DEFAULT_LAYER_ID]: dataLayer }; - }; - - getVisualizationState = (): XYState => { - return getXYVisualizationState({ - layers: [ - { - layerId: DEFAULT_LAYER_ID, - seriesType: 'line', - accessors: ['y_diskio_read'], - yConfig: [], - layerType: 'data', - xAccessor: HISTOGRAM_COLUMN_NAME, - splitAccessor: BREAKDOWN_COLUMN_NAME, - }, - ], - }); - }; - - getReferences = () => getDefaultReferences(this.dataView, DEFAULT_LAYER_ID); - getAdhocDataView = () => getAdhocDataView(this.dataView); -} diff --git a/x-pack/plugins/infra/public/common/visualizations/lens/hosts/diskio_write.ts b/x-pack/plugins/infra/public/common/visualizations/lens/hosts/diskio_write.ts deleted file mode 100644 index ae6bd12fd026c..0000000000000 --- a/x-pack/plugins/infra/public/common/visualizations/lens/hosts/diskio_write.ts +++ /dev/null @@ -1,94 +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 { - FormBasedLayer, - FormulaPublicApi, - PersistedIndexPatternLayer, - XYState, -} from '@kbn/lens-plugin/public'; -import type { DataView } from '@kbn/data-views-plugin/public'; -import type { LensOptions } from '../../../../types'; -import { - DEFAULT_LAYER_ID, - getAdhocDataView, - getBreakdownColumn, - getDefaultReferences, - getHistogramColumn, - getXYVisualizationState, -} from '../utils'; -import type { ILensVisualization } from '../types'; - -const BREAKDOWN_COLUMN_NAME = 'hosts_aggs_breakdown'; -const HISTOGRAM_COLUMN_NAME = 'x_date_histogram'; - -export class DiskIOWrite implements ILensVisualization { - constructor( - private dataView: DataView, - private options: LensOptions, - private formula: FormulaPublicApi - ) {} - - getTitle(): string { - return 'Disk Write IOPS'; - } - - getVisualizationType(): string { - return 'lnsXY'; - } - - getLayers = (): Record> => { - const baseLayer: PersistedIndexPatternLayer = { - columnOrder: [BREAKDOWN_COLUMN_NAME, HISTOGRAM_COLUMN_NAME], - columns: { - ...getBreakdownColumn(BREAKDOWN_COLUMN_NAME, 'host.name', this.options.breakdownSize), - ...getHistogramColumn(HISTOGRAM_COLUMN_NAME, this.dataView.timeFieldName ?? '@timestamp'), - }, - }; - - const dataLayer = this.formula.insertOrReplaceFormulaColumn( - 'y_diskio_write', - { - formula: - "counter_rate(max(system.diskio.write.bytes), kql='system.diskio.write.bytes>= 0')", - format: { - id: 'bytes', - params: { - decimals: 1, - }, - }, - }, - baseLayer, - this.dataView - ); - - if (!dataLayer) { - throw new Error('Error generating the data layer for the chart'); - } - - return { [DEFAULT_LAYER_ID]: dataLayer }; - }; - - getVisualizationState = (): XYState => { - return getXYVisualizationState({ - layers: [ - { - layerId: DEFAULT_LAYER_ID, - seriesType: 'line', - accessors: ['y_diskio_write'], - yConfig: [], - layerType: 'data', - xAccessor: HISTOGRAM_COLUMN_NAME, - splitAccessor: BREAKDOWN_COLUMN_NAME, - }, - ], - }); - }; - - getReferences = () => getDefaultReferences(this.dataView, DEFAULT_LAYER_ID); - getAdhocDataView = () => getAdhocDataView(this.dataView); -} diff --git a/x-pack/plugins/infra/public/common/visualizations/lens/hosts/index.ts b/x-pack/plugins/infra/public/common/visualizations/lens/hosts/index.ts deleted file mode 100644 index 1332b4ed2aadc..0000000000000 --- a/x-pack/plugins/infra/public/common/visualizations/lens/hosts/index.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. - */ - -export { CPU } from './cpu'; -export { Load } from './load'; -export { Memory } from './memory'; -export { MemoryAvailable } from './memory_available'; -export { RX } from './rx'; -export { TX } from './tx'; -export { DiskIORead } from './diskio_read'; -export { DiskIOWrite } from './diskio_write'; diff --git a/x-pack/plugins/infra/public/common/visualizations/lens/hosts/load.ts b/x-pack/plugins/infra/public/common/visualizations/lens/hosts/load.ts deleted file mode 100644 index 59b6167c5dc1d..0000000000000 --- a/x-pack/plugins/infra/public/common/visualizations/lens/hosts/load.ts +++ /dev/null @@ -1,144 +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 { - PersistedIndexPatternLayer, - FormulaPublicApi, - XYState, - FormBasedLayer, -} from '@kbn/lens-plugin/public'; -import type { DataView } from '@kbn/data-views-plugin/public'; -import type { ReferenceBasedIndexPatternColumn } from '@kbn/lens-plugin/public/datasources/form_based/operations/definitions/column_types'; -import type { SavedObjectReference } from '@kbn/core-saved-objects-common'; -import { - DEFAULT_AD_HOC_DATA_VIEW_ID, - DEFAULT_LAYER_ID, - getAdhocDataView, - getBreakdownColumn, - getDefaultReferences, - getHistogramColumn, - getXYVisualizationState, -} from '../utils'; -import type { LensOptions } from '../../../../types'; -import type { ILensVisualization } from '../types'; - -const BREAKDOWN_COLUMN_NAME = 'hosts_aggs_breakdown'; -const HISTOGRAM_COLUMN_NAME = 'x_date_histogram'; -const REFERENCE_LAYER = 'referenceLayer'; - -export class Load implements ILensVisualization { - constructor( - private dataView: DataView, - private options: LensOptions, - private formula: FormulaPublicApi - ) {} - - getTitle(): string { - return 'Normalized Load'; - } - - getVisualizationType(): string { - return 'lnsXY'; - } - - getLayers = (): Record> => { - const baseLayer: PersistedIndexPatternLayer = { - columnOrder: [BREAKDOWN_COLUMN_NAME, HISTOGRAM_COLUMN_NAME], - columns: { - ...getBreakdownColumn(BREAKDOWN_COLUMN_NAME, 'host.name', this.options.breakdownSize), - ...getHistogramColumn(HISTOGRAM_COLUMN_NAME, this.dataView.timeFieldName ?? '@timestamp'), - }, - }; - - const dataLayer = this.formula.insertOrReplaceFormulaColumn( - 'y_cpu_cores_usage', - { - formula: 'average(system.load.1) / max(system.load.cores)', - format: { - id: 'percent', - params: { - decimals: 0, - }, - }, - }, - baseLayer, - this.dataView - ); - - if (!dataLayer) { - throw new Error('Error generating the data layer for the chart'); - } - - return { - [DEFAULT_LAYER_ID]: dataLayer, - referenceLayer: { - linkToLayers: [], - columnOrder: ['referenceColumn'], - columns: { - referenceColumn: { - label: 'Reference', - dataType: 'number', - operationType: 'static_value', - isStaticValue: true, - isBucketed: false, - scale: 'ratio', - params: { - value: 1, - format: { - id: 'percent', - params: { - decimals: 0, - }, - }, - }, - references: [], - customLabel: true, - } as ReferenceBasedIndexPatternColumn, - }, - sampling: 1, - incompleteColumns: {}, - }, - }; - }; - getVisualizationState = (): XYState => { - return getXYVisualizationState({ - layers: [ - { - layerId: DEFAULT_LAYER_ID, - seriesType: 'line', - accessors: ['y_cpu_cores_usage'], - yConfig: [], - layerType: 'data', - xAccessor: HISTOGRAM_COLUMN_NAME, - splitAccessor: BREAKDOWN_COLUMN_NAME, - }, - { - layerId: REFERENCE_LAYER, - layerType: 'referenceLine', - accessors: ['referenceColumn'], - yConfig: [ - { - forAccessor: 'referenceColumn', - axisMode: 'left', - color: '#6092c0', - }, - ], - }, - ], - }); - }; - - getReferences = (): SavedObjectReference[] => [ - ...getDefaultReferences(this.dataView, DEFAULT_LAYER_ID), - { - type: 'index-pattern', - id: this.dataView.id ?? DEFAULT_AD_HOC_DATA_VIEW_ID, - name: `indexpattern-datasource-layer-${REFERENCE_LAYER}`, - }, - ]; - getAdhocDataView = () => getAdhocDataView(this.dataView); -} diff --git a/x-pack/plugins/infra/public/common/visualizations/lens/hosts/memory.ts b/x-pack/plugins/infra/public/common/visualizations/lens/hosts/memory.ts deleted file mode 100644 index 01bcd45b6e283..0000000000000 --- a/x-pack/plugins/infra/public/common/visualizations/lens/hosts/memory.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 { - FormBasedLayer, - FormulaPublicApi, - PersistedIndexPatternLayer, - XYState, -} from '@kbn/lens-plugin/public'; -import type { DataView } from '@kbn/data-views-plugin/public'; -import { - DEFAULT_LAYER_ID, - getAdhocDataView, - getBreakdownColumn, - getDefaultReferences, - getHistogramColumn, - getXYVisualizationState, -} from '../utils'; -import type { LensOptions } from '../../../../types'; -import type { ILensVisualization } from '../types'; - -const BREAKDOWN_COLUMN_NAME = 'hosts_aggs_breakdown'; -const HISTOGRAM_COLUMN_NAME = 'x_date_histogram'; - -export class Memory implements ILensVisualization { - constructor( - private dataView: DataView, - private options: LensOptions, - private formula: FormulaPublicApi - ) {} - - getTitle(): string { - return 'Disk Writes IOPS'; - } - - getVisualizationType(): string { - return 'lnsXY'; - } - - getLayers = (): Record> => { - const baseLayer: PersistedIndexPatternLayer = { - columnOrder: [BREAKDOWN_COLUMN_NAME, HISTOGRAM_COLUMN_NAME], - columns: { - ...getBreakdownColumn(BREAKDOWN_COLUMN_NAME, 'host.name', this.options.breakdownSize), - ...getHistogramColumn(HISTOGRAM_COLUMN_NAME, this.dataView.timeFieldName ?? '@timestamp'), - }, - }; - - const dataLayer = this.formula.insertOrReplaceFormulaColumn( - 'y_memory_usage', - { - formula: 'average(system.memory.actual.used.bytes) / max(system.memory.total)', - format: { - id: 'percent', - params: { - decimals: 0, - }, - }, - }, - baseLayer, - this.dataView - ); - - if (!dataLayer) { - throw new Error('Error generating the data layer for the chart'); - } - - return { [DEFAULT_LAYER_ID]: dataLayer }; - }; - getVisualizationState = (): XYState => { - return getXYVisualizationState({ - layers: [ - { - layerId: DEFAULT_LAYER_ID, - seriesType: 'line', - accessors: ['y_memory_usage'], - yConfig: [], - layerType: 'data', - xAccessor: HISTOGRAM_COLUMN_NAME, - splitAccessor: BREAKDOWN_COLUMN_NAME, - }, - ], - yLeftExtent: { - mode: 'custom', - lowerBound: 0, - upperBound: 1, - }, - }); - }; - - getReferences = () => getDefaultReferences(this.dataView, DEFAULT_LAYER_ID); - getAdhocDataView = () => getAdhocDataView(this.dataView); -} diff --git a/x-pack/plugins/infra/public/common/visualizations/lens/hosts/memory_available.ts b/x-pack/plugins/infra/public/common/visualizations/lens/hosts/memory_available.ts deleted file mode 100644 index 69818f407180e..0000000000000 --- a/x-pack/plugins/infra/public/common/visualizations/lens/hosts/memory_available.ts +++ /dev/null @@ -1,91 +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 { - FormBasedLayer, - FormulaPublicApi, - PersistedIndexPatternLayer, - XYState, -} from '@kbn/lens-plugin/public'; -import type { DataView } from '@kbn/data-views-plugin/public'; -import { - DEFAULT_LAYER_ID, - getAdhocDataView, - getBreakdownColumn, - getDefaultReferences, - getHistogramColumn, - getXYVisualizationState, -} from '../utils'; -import type { LensOptions } from '../../../../types'; -import type { ILensVisualization } from '../types'; - -const BREAKDOWN_COLUMN_NAME = 'hosts_aggs_breakdown'; -const HISTOGRAM_COLUMN_NAME = 'x_date_histogram'; - -export class MemoryAvailable implements ILensVisualization { - constructor( - private dataView: DataView, - private options: LensOptions, - private formula: FormulaPublicApi - ) {} - - getTitle(): string { - return 'Memory Available'; - } - - getVisualizationType(): string { - return 'lnsXY'; - } - - getLayers = (): Record> => { - const baseLayer: PersistedIndexPatternLayer = { - columnOrder: [BREAKDOWN_COLUMN_NAME, HISTOGRAM_COLUMN_NAME], - columns: { - ...getBreakdownColumn(BREAKDOWN_COLUMN_NAME, 'host.name', this.options.breakdownSize), - ...getHistogramColumn(HISTOGRAM_COLUMN_NAME, this.dataView.timeFieldName ?? '@timestamp'), - }, - }; - - const dataLayer = this.formula.insertOrReplaceFormulaColumn( - 'y_memory_available', - { - formula: 'max(system.memory.total) - average(system.memory.actual.used.bytes)', - format: { - id: 'bytes', - params: { - decimals: 1, - }, - }, - }, - baseLayer, - this.dataView - ); - - if (!dataLayer) { - throw new Error('Error generating the data layer for the chart'); - } - - return { [DEFAULT_LAYER_ID]: dataLayer }; - }; - getVisualizationState = (): XYState => { - return getXYVisualizationState({ - layers: [ - { - layerId: DEFAULT_LAYER_ID, - seriesType: 'line', - accessors: ['y_memory_available'], - yConfig: [], - layerType: 'data', - xAccessor: HISTOGRAM_COLUMN_NAME, - splitAccessor: BREAKDOWN_COLUMN_NAME, - }, - ], - }); - }; - getReferences = () => getDefaultReferences(this.dataView, DEFAULT_LAYER_ID); - getAdhocDataView = () => getAdhocDataView(this.dataView); -} diff --git a/x-pack/plugins/infra/public/common/visualizations/lens/hosts/rx.ts b/x-pack/plugins/infra/public/common/visualizations/lens/hosts/rx.ts deleted file mode 100644 index ff5e19b40dc85..0000000000000 --- a/x-pack/plugins/infra/public/common/visualizations/lens/hosts/rx.ts +++ /dev/null @@ -1,92 +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 { - FormBasedLayer, - FormulaPublicApi, - PersistedIndexPatternLayer, - XYState, -} from '@kbn/lens-plugin/public'; -import type { DataView } from '@kbn/data-views-plugin/public'; -import { - DEFAULT_LAYER_ID, - getAdhocDataView, - getBreakdownColumn, - getDefaultReferences, - getHistogramColumn, - getXYVisualizationState, -} from '../utils'; -import type { LensOptions } from '../../../../types'; -import type { ILensVisualization } from '../types'; - -const BREAKDOWN_COLUMN_NAME = 'hosts_aggs_breakdown'; -const HISTOGRAM_COLUMN_NAME = 'x_date_histogram'; - -export class RX implements ILensVisualization { - constructor( - private dataView: DataView, - private options: LensOptions, - private formula: FormulaPublicApi - ) {} - - getTitle(): string { - return 'Network Inbound (RX)'; - } - - getVisualizationType(): string { - return 'lnsXY'; - } - - getLayers = (): Record> => { - const baseLayer: PersistedIndexPatternLayer = { - columnOrder: [BREAKDOWN_COLUMN_NAME, HISTOGRAM_COLUMN_NAME], - columns: { - ...getBreakdownColumn(BREAKDOWN_COLUMN_NAME, 'host.name', this.options.breakdownSize), - ...getHistogramColumn(HISTOGRAM_COLUMN_NAME, this.dataView.timeFieldName ?? '@timestamp'), - }, - }; - - const dataLayer = this.formula.insertOrReplaceFormulaColumn( - 'y_network_in_bytes', - { - formula: - "average(host.network.ingress.bytes) * 8 / (max(metricset.period, kql='host.network.ingress.bytes: *') / 1000)", - format: { - id: 'bits', - params: { - decimals: 1, - }, - }, - }, - baseLayer, - this.dataView - ); - - if (!dataLayer) { - throw new Error('Error generating the data layer for the chart'); - } - - return { [DEFAULT_LAYER_ID]: dataLayer }; - }; - getVisualizationState = (): XYState => { - return getXYVisualizationState({ - layers: [ - { - layerId: DEFAULT_LAYER_ID, - seriesType: 'line', - accessors: ['y_network_in_bytes'], - yConfig: [], - layerType: 'data', - xAccessor: HISTOGRAM_COLUMN_NAME, - splitAccessor: BREAKDOWN_COLUMN_NAME, - }, - ], - }); - }; - getReferences = () => getDefaultReferences(this.dataView, DEFAULT_LAYER_ID); - getAdhocDataView = () => getAdhocDataView(this.dataView); -} diff --git a/x-pack/plugins/infra/public/common/visualizations/lens/hosts/tx.ts b/x-pack/plugins/infra/public/common/visualizations/lens/hosts/tx.ts deleted file mode 100644 index 8923060763d79..0000000000000 --- a/x-pack/plugins/infra/public/common/visualizations/lens/hosts/tx.ts +++ /dev/null @@ -1,92 +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 { - FormBasedLayer, - FormulaPublicApi, - PersistedIndexPatternLayer, - XYState, -} from '@kbn/lens-plugin/public'; -import type { DataView } from '@kbn/data-views-plugin/public'; -import { - DEFAULT_LAYER_ID, - getAdhocDataView, - getBreakdownColumn, - getDefaultReferences, - getHistogramColumn, - getXYVisualizationState, -} from '../utils'; -import type { LensOptions } from '../../../../types'; -import type { ILensVisualization } from '../types'; - -const BREAKDOWN_COLUMN_NAME = 'hosts_aggs_breakdown'; -const HISTOGRAM_COLUMN_NAME = 'x_date_histogram'; - -export class TX implements ILensVisualization { - constructor( - private dataView: DataView, - private options: LensOptions, - private formula: FormulaPublicApi - ) {} - - getTitle(): string { - return 'Network Outbound (TX)'; - } - - getVisualizationType(): string { - return 'lnsXY'; - } - - getLayers = (): Record> => { - const baseLayer: PersistedIndexPatternLayer = { - columnOrder: [BREAKDOWN_COLUMN_NAME, HISTOGRAM_COLUMN_NAME], - columns: { - ...getBreakdownColumn(BREAKDOWN_COLUMN_NAME, 'host.name', this.options.breakdownSize), - ...getHistogramColumn(HISTOGRAM_COLUMN_NAME, this.dataView.timeFieldName ?? '@timestamp'), - }, - }; - - const dataLayer = this.formula.insertOrReplaceFormulaColumn( - 'y_network_out_bytes', - { - formula: - "average(host.network.egress.bytes) * 8 / (max(metricset.period, kql='host.network.egress.bytes: *') / 1000)", - format: { - id: 'bits', - params: { - decimals: 1, - }, - }, - }, - baseLayer, - this.dataView - ); - - if (!dataLayer) { - throw new Error('Error generating the data layer for the chart'); - } - - return { [DEFAULT_LAYER_ID]: dataLayer }; - }; - getVisualizationState = (): XYState => { - return getXYVisualizationState({ - layers: [ - { - layerId: DEFAULT_LAYER_ID, - seriesType: 'line', - accessors: ['y_network_out_bytes'], - yConfig: [], - layerType: 'data', - xAccessor: HISTOGRAM_COLUMN_NAME, - splitAccessor: BREAKDOWN_COLUMN_NAME, - }, - ], - }); - }; - getReferences = () => getDefaultReferences(this.dataView, DEFAULT_LAYER_ID); - getAdhocDataView = () => getAdhocDataView(this.dataView); -} diff --git a/x-pack/plugins/infra/public/common/visualizations/lens/types.ts b/x-pack/plugins/infra/public/common/visualizations/lens/types.ts deleted file mode 100644 index a4e2779fad52c..0000000000000 --- a/x-pack/plugins/infra/public/common/visualizations/lens/types.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 { SavedObjectReference } from '@kbn/core-saved-objects-common'; -import type { DataViewSpec } from '@kbn/data-views-plugin/common'; -import type { FormBasedLayer, XYState } from '@kbn/lens-plugin/public'; - -export interface ILensVisualization { - getTitle(): string; - getVisualizationType(): string; - getLayers(): Record>; - getVisualizationState(): XYState; - getReferences(): SavedObjectReference[]; - getAdhocDataView(): Record; -} diff --git a/x-pack/plugins/infra/public/common/visualizations/lens/utils.ts b/x-pack/plugins/infra/public/common/visualizations/lens/utils.ts index 8a4f1936c77d3..88b78e5a8096d 100644 --- a/x-pack/plugins/infra/public/common/visualizations/lens/utils.ts +++ b/x-pack/plugins/infra/public/common/visualizations/lens/utils.ts @@ -8,33 +8,45 @@ import { DateHistogramIndexPatternColumn, PersistedIndexPatternLayer, TermsIndexPatternColumn, - XYState, } from '@kbn/lens-plugin/public'; import type { DataView, DataViewSpec } from '@kbn/data-views-plugin/public'; import type { SavedObjectReference } from '@kbn/core-saved-objects-common'; export const DEFAULT_LAYER_ID = 'layer1'; export const DEFAULT_AD_HOC_DATA_VIEW_ID = 'infra_lens_ad_hoc_default'; +const DEFAULT_BREAKDOWN_SIZE = 10; -export const getHistogramColumn = (columnName: string, sourceField: string) => { +export const getHistogramColumn = ({ + columnName, + overrides, +}: { + columnName: string; + overrides?: Partial>; +}) => { return { [columnName]: { dataType: 'date', isBucketed: true, label: '@timestamp', operationType: 'date_histogram', - params: { interval: 'auto' }, scale: 'interval', - sourceField, + sourceField: '@timestamp', + ...overrides, + params: { interval: 'auto', ...overrides?.params }, } as DateHistogramIndexPatternColumn, }; }; -export const getBreakdownColumn = ( - columnName: string, - sourceField: string, - breakdownSize: number -): PersistedIndexPatternLayer['columns'] => { +export const getBreakdownColumn = ({ + columnName, + overrides, +}: { + columnName: string; + overrides?: Partial> & { + breakdownSize?: number; + }; +}): PersistedIndexPatternLayer['columns'] => { + const { breakdownSize = DEFAULT_BREAKDOWN_SIZE, sourceField } = overrides ?? {}; return { [columnName]: { label: `Top ${breakdownSize} values of ${sourceField}`, @@ -64,47 +76,6 @@ export const getBreakdownColumn = ( }; }; -export const getXYVisualizationState = ( - custom: Omit, 'layers'> & { layers: XYState['layers'] } -): XYState => ({ - legend: { - isVisible: false, - position: 'right', - showSingleSeries: false, - }, - valueLabels: 'show', - fittingFunction: 'Zero', - curveType: 'LINEAR', - yLeftScale: 'linear', - axisTitlesVisibilitySettings: { - x: false, - yLeft: false, - yRight: true, - }, - tickLabelsVisibilitySettings: { - x: true, - yLeft: true, - yRight: true, - }, - labelsOrientation: { - x: 0, - yLeft: 0, - yRight: 0, - }, - gridlinesVisibilitySettings: { - x: true, - yLeft: true, - yRight: true, - }, - preferredSeriesType: 'line', - valuesInLegend: false, - emphasizeFitting: true, - yTitle: '', - xTitle: '', - hideEndzones: true, - ...custom, -}); - export const getDefaultReferences = ( dataView: DataView, dataLayerId: string diff --git a/x-pack/plugins/infra/public/common/visualizations/lens/visualization_types/index.ts b/x-pack/plugins/infra/public/common/visualizations/lens/visualization_types/index.ts new file mode 100644 index 0000000000000..4abfeb3a60c45 --- /dev/null +++ b/x-pack/plugins/infra/public/common/visualizations/lens/visualization_types/index.ts @@ -0,0 +1,9 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export { LineChart } from './line_chart'; +export { MetricChart } from './metric_chart'; diff --git a/x-pack/plugins/infra/public/common/visualizations/lens/visualization_types/line_chart.ts b/x-pack/plugins/infra/public/common/visualizations/lens/visualization_types/line_chart.ts new file mode 100644 index 0000000000000..68035fc821e80 --- /dev/null +++ b/x-pack/plugins/infra/public/common/visualizations/lens/visualization_types/line_chart.ts @@ -0,0 +1,154 @@ +/* + * Copyright 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 { + FormBasedPersistedState, + FormulaPublicApi, + PersistedIndexPatternLayer, + XYState, +} from '@kbn/lens-plugin/public'; +import type { SavedObjectReference } from '@kbn/core-saved-objects-common'; +import type { DataView, DataViewSpec } from '@kbn/data-views-plugin/public'; +import { Filter } from '@kbn/es-query'; +import { + DEFAULT_LAYER_ID, + getAdhocDataView, + getBreakdownColumn, + getDefaultReferences, + getHistogramColumn, +} from '../utils'; +import type { LensChartConfig, VisualizationAttributes, LineChartOptions } from '../../types'; + +const BREAKDOWN_COLUMN_NAME = 'hosts_aggs_breakdown'; +const HISTOGRAM_COLUMN_NAME = 'x_date_histogram'; +const ACCESSOR = 'formula_accessor'; + +export class LineChart implements VisualizationAttributes { + constructor( + private chartConfig: LensChartConfig, + private dataView: DataView, + private formulaAPI: FormulaPublicApi, + private options?: LineChartOptions + ) {} + + getVisualizationType(): string { + return 'lnsXY'; + } + + getLayers(): FormBasedPersistedState['layers'] { + const baseLayer: PersistedIndexPatternLayer = { + columnOrder: [BREAKDOWN_COLUMN_NAME, HISTOGRAM_COLUMN_NAME], + columns: { + ...getBreakdownColumn({ + columnName: BREAKDOWN_COLUMN_NAME, + overrides: { + sourceField: 'host.name', + breakdownSize: this.options?.breakdownSize, + }, + }), + ...getHistogramColumn({ + columnName: HISTOGRAM_COLUMN_NAME, + overrides: { + sourceField: this.dataView.timeFieldName, + }, + }), + }, + }; + + const dataLayer = this.formulaAPI.insertOrReplaceFormulaColumn( + ACCESSOR, + this.chartConfig.formula, + baseLayer, + this.dataView + ); + + if (!dataLayer) { + throw new Error('Error generating the data layer for the chart'); + } + + return { [DEFAULT_LAYER_ID]: dataLayer, ...this.chartConfig.lineChartConfig?.extraLayers }; + } + + getVisualizationState(): XYState { + const extraVisualizationState = this.chartConfig.lineChartConfig?.extraVisualizationState; + + return getXYVisualizationState({ + ...extraVisualizationState, + layers: [ + { + layerId: DEFAULT_LAYER_ID, + seriesType: 'line', + accessors: [ACCESSOR], + yConfig: [], + layerType: 'data', + xAccessor: HISTOGRAM_COLUMN_NAME, + splitAccessor: BREAKDOWN_COLUMN_NAME, + }, + ...(extraVisualizationState?.layers ? extraVisualizationState?.layers : []), + ], + }); + } + + getReferences(): SavedObjectReference[] { + const extraReference = this.chartConfig.lineChartConfig?.extraReference; + return [ + ...getDefaultReferences(this.dataView, DEFAULT_LAYER_ID), + ...(extraReference ? getDefaultReferences(this.dataView, extraReference) : []), + ]; + } + + getAdhocDataView(): Record { + return getAdhocDataView(this.dataView); + } + + getTitle(): string { + return this.options?.title ?? this.chartConfig.title ?? ''; + } + + getFilters(): Filter[] { + return this.chartConfig.getFilters({ id: this.dataView.id ?? DEFAULT_LAYER_ID }); + } +} + +export const getXYVisualizationState = ( + custom: Omit, 'layers'> & { layers: XYState['layers'] } +): XYState => ({ + legend: { + isVisible: false, + position: 'right', + showSingleSeries: false, + }, + valueLabels: 'show', + fittingFunction: 'Zero', + curveType: 'LINEAR', + yLeftScale: 'linear', + axisTitlesVisibilitySettings: { + x: false, + yLeft: false, + yRight: true, + }, + tickLabelsVisibilitySettings: { + x: true, + yLeft: true, + yRight: true, + }, + labelsOrientation: { + x: 0, + yLeft: 0, + yRight: 0, + }, + gridlinesVisibilitySettings: { + x: true, + yLeft: true, + yRight: true, + }, + preferredSeriesType: 'line', + valuesInLegend: false, + emphasizeFitting: true, + hideEndzones: true, + ...custom, +}); diff --git a/x-pack/plugins/infra/public/common/visualizations/lens/visualization_types/metric_chart.ts b/x-pack/plugins/infra/public/common/visualizations/lens/visualization_types/metric_chart.ts new file mode 100644 index 0000000000000..823d882e02f8a --- /dev/null +++ b/x-pack/plugins/infra/public/common/visualizations/lens/visualization_types/metric_chart.ts @@ -0,0 +1,139 @@ +/* + * Copyright 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 { + FormBasedPersistedState, + FormulaPublicApi, + MetricVisualizationState, + PersistedIndexPatternLayer, +} from '@kbn/lens-plugin/public'; +import type { SavedObjectReference } from '@kbn/core-saved-objects-common'; +import type { DataView, DataViewSpec } from '@kbn/data-views-plugin/public'; +import type { Filter } from '@kbn/es-query'; +import { + DEFAULT_LAYER_ID, + getAdhocDataView, + getDefaultReferences, + getHistogramColumn, +} from '../utils'; + +import type { VisualizationAttributes, LensChartConfig, MetricChartOptions } from '../../types'; + +const HISTOGRAM_COLUMN_NAME = 'x_date_histogram'; +const TRENDLINE_LAYER_ID = 'trendline_layer'; +const TRENDLINE_ACCESSOR = 'metric_trendline_formula_accessor'; +const ACCESSOR = 'metric_formula_accessor'; + +export class MetricChart implements VisualizationAttributes { + constructor( + private chartConfig: LensChartConfig, + private dataView: DataView, + private formulaAPI: FormulaPublicApi, + private options?: MetricChartOptions + ) {} + + getVisualizationType(): string { + return 'lnsMetric'; + } + + getTrendLineLayer(baseLayer: PersistedIndexPatternLayer): FormBasedPersistedState['layers'] { + const trendLineLayer = this.formulaAPI.insertOrReplaceFormulaColumn( + TRENDLINE_ACCESSOR, + this.chartConfig.formula, + baseLayer, + this.dataView + ); + + if (!trendLineLayer) { + throw new Error('Error generating the data layer for the chart'); + } + + return { + [TRENDLINE_LAYER_ID]: { + linkToLayers: [DEFAULT_LAYER_ID], + ...trendLineLayer, + }, + }; + } + + getLayers(): FormBasedPersistedState['layers'] { + const { showTrendLine = true } = this.options ?? {}; + const baseLayer: PersistedIndexPatternLayer = { + columnOrder: [HISTOGRAM_COLUMN_NAME], + columns: getHistogramColumn({ + columnName: HISTOGRAM_COLUMN_NAME, + overrides: { + sourceField: this.dataView.timeFieldName, + params: { + interval: 'auto', + includeEmptyRows: true, + }, + }, + }), + sampling: 1, + }; + + const baseLayerDetails = this.formulaAPI.insertOrReplaceFormulaColumn( + ACCESSOR, + { + ...this.chartConfig.formula, + label: this.options?.title ?? this.chartConfig.title, + }, + { columnOrder: [], columns: {} }, + this.dataView + ); + + if (!baseLayerDetails) { + throw new Error('Error generating the data layer for the chart'); + } + + return { + [DEFAULT_LAYER_ID]: baseLayerDetails, + ...(showTrendLine ? this.getTrendLineLayer(baseLayer) : {}), + }; + } + + getVisualizationState(): MetricVisualizationState { + const { subtitle, backgroundColor, showTrendLine = true } = this.options ?? {}; + return { + layerId: DEFAULT_LAYER_ID, + layerType: 'data', + metricAccessor: ACCESSOR, + color: backgroundColor, + subtitle, + showBar: false, + ...(showTrendLine + ? { + trendlineLayerId: TRENDLINE_LAYER_ID, + trendlineLayerType: 'metricTrendline', + trendlineMetricAccessor: TRENDLINE_ACCESSOR, + trendlineTimeAccessor: HISTOGRAM_COLUMN_NAME, + } + : {}), + }; + } + + getReferences(): SavedObjectReference[] { + const { showTrendLine = true } = this.options ?? {}; + return [ + ...getDefaultReferences(this.dataView, DEFAULT_LAYER_ID), + ...(showTrendLine ? getDefaultReferences(this.dataView, TRENDLINE_LAYER_ID) : []), + ]; + } + + getAdhocDataView(): Record { + return getAdhocDataView(this.dataView); + } + + getTitle(): string { + return this.options?.showTitle ? this.options?.title ?? this.chartConfig.title : ''; + } + + getFilters(): Filter[] { + return this.chartConfig.getFilters({ id: this.dataView.id ?? DEFAULT_LAYER_ID }); + } +} diff --git a/x-pack/plugins/infra/public/common/visualizations/types.ts b/x-pack/plugins/infra/public/common/visualizations/types.ts new file mode 100644 index 0000000000000..76f644525bd14 --- /dev/null +++ b/x-pack/plugins/infra/public/common/visualizations/types.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 { SavedObjectReference } from '@kbn/core-saved-objects-common'; +import type { DataViewSpec } from '@kbn/data-views-plugin/common'; +import { DataViewBase, Filter } from '@kbn/es-query'; +import { + FormBasedPersistedState, + FormulaPublicApi, + MetricVisualizationState, + TypedLensByValueInput, + XYState, +} from '@kbn/lens-plugin/public'; +import { hostLensFormulas, visualizationTypes } from './constants'; + +export type LensAttributes = TypedLensByValueInput['attributes']; + +export interface LensOptions { + title: string; +} +export interface LineChartOptions extends LensOptions { + breakdownSize?: number; +} +export interface MetricChartOptions extends LensOptions { + subtitle?: string; + showTitle?: boolean; + showTrendLine?: boolean; + backgroundColor?: string; +} + +export interface LensLineChartConfig { + extraVisualizationState?: Partial & { layers: XYState['layers'] }>; + extraLayers?: FormBasedPersistedState['layers']; + extraReference?: string; +} +export interface LensChartConfig { + title: string; + formula: Formula; + lineChartConfig?: LensLineChartConfig; + getFilters: ({ id }: Pick) => Filter[]; +} + +export type TVisualization = XYState | MetricVisualizationState; +export interface VisualizationAttributes { + getTitle(): string; + getVisualizationType(): string; + getLayers(): FormBasedPersistedState['layers']; + getVisualizationState(): T; + getReferences(): SavedObjectReference[]; + getFilters(): Filter[]; + getAdhocDataView(): Record; +} + +export type Formula = Parameters[1]; + +export type VisualizationTypes = keyof typeof visualizationTypes; +export type HostsLensFormulas = keyof typeof hostLensFormulas; +export type HostsLensMetricChartFormulas = Exclude; +export type HostsLensLineChartFormulas = Exclude; diff --git a/x-pack/plugins/infra/public/hooks/use_intersection_once.ts b/x-pack/plugins/infra/public/hooks/use_intersection_once.ts new file mode 100644 index 0000000000000..8894e9fec3176 --- /dev/null +++ b/x-pack/plugins/infra/public/hooks/use_intersection_once.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. + */ + +import { RefObject, useEffect, useState } from 'react'; +import useIntersection from 'react-use/lib/useIntersection'; + +export const useIntersectedOnce = ( + ref: RefObject, + options: IntersectionObserverInit +) => { + const [intersectedOnce, setIntersectedOnce] = useState(false); + const intersection = useIntersection(ref, options); + + useEffect(() => { + if (!intersectedOnce && (intersection?.intersectionRatio ?? 0) > 0) { + setIntersectedOnce(true); + } + }, [intersectedOnce, intersection?.intersectionRatio]); + + return { intersectedOnce, intersection }; +}; diff --git a/x-pack/plugins/infra/public/hooks/use_lens_attributes.test.ts b/x-pack/plugins/infra/public/hooks/use_lens_attributes.test.ts index d4eb0e810cc1e..b58b25ec35b28 100644 --- a/x-pack/plugins/infra/public/hooks/use_lens_attributes.test.ts +++ b/x-pack/plugins/infra/public/hooks/use_lens_attributes.test.ts @@ -48,7 +48,11 @@ describe('useHostTable hook', () => { it('should return the basic lens attributes', async () => { const { result, waitForNextUpdate } = renderHook(() => useLensAttributes({ + visualizationType: 'lineChart', type: 'load', + options: { + title: 'Injected Normalized Load', + }, dataView: mockDataView, }) ); @@ -57,12 +61,12 @@ describe('useHostTable hook', () => { const { state, title } = result.current.attributes ?? {}; const { datasourceStates, filters } = state ?? {}; - expect(title).toBe('Normalized Load'); + expect(title).toBe('Injected Normalized Load'); expect(datasourceStates).toEqual({ formBased: { layers: { layer1: { - columnOrder: ['hosts_aggs_breakdown', 'x_date_histogram', 'y_cpu_cores_usage'], + columnOrder: ['hosts_aggs_breakdown', 'x_date_histogram', 'formula_accessor'], columns: { hosts_aggs_breakdown: { dataType: 'string', @@ -100,7 +104,7 @@ describe('useHostTable hook', () => { scale: 'interval', sourceField: '@timestamp', }, - y_cpu_cores_usage: { + formula_accessor: { customLabel: false, dataType: 'number', filter: undefined, @@ -154,19 +158,36 @@ describe('useHostTable hook', () => { }, }, }); - expect(filters).toEqual([]); + expect(filters).toEqual([ + { + meta: { + index: 'mock-id', + }, + query: { + exists: { + field: 'host.name', + }, + }, + }, + ]); }); - it('should return attributes with injected values', async () => { + it('should return extra actions', async () => { const { result, waitForNextUpdate } = renderHook(() => useLensAttributes({ + visualizationType: 'lineChart', type: 'load', dataView: mockDataView, }) ); await waitForNextUpdate(); - const injectedData = { + const extraActions = result.current.getExtraActions({ + timeRange: { + from: 'now-15m', + to: 'now', + mode: 'relative', + }, query: { language: 'kuery', query: '{term: { host.name: "a"}}', @@ -186,17 +207,8 @@ describe('useHostTable hook', () => { query: { range: { 'system.load.cores': { gte: 0 } } }, }, ], - title: 'Injected CPU Cores', - }; - - const injectedAttributes = result.current.injectData(injectedData); - - const { state, title } = injectedAttributes ?? {}; - const { filters, query } = state ?? {}; + }); - expect(title).toEqual(injectedData.title); - expect(query).toEqual(injectedData.query); - expect(filters).toHaveLength(1); - expect(filters).toContain(injectedData.filters[0]); + expect(extraActions.openInLens).not.toBeNull(); }); }); diff --git a/x-pack/plugins/infra/public/hooks/use_lens_attributes.ts b/x-pack/plugins/infra/public/hooks/use_lens_attributes.ts index c3b005f9ad8ad..c9ce48c909f9a 100644 --- a/x-pack/plugins/infra/public/hooks/use_lens_attributes.ts +++ b/x-pack/plugins/infra/public/hooks/use_lens_attributes.ts @@ -12,25 +12,45 @@ import { useKibana } from '@kbn/kibana-react-plugin/public'; import { ActionExecutionContext } from '@kbn/ui-actions-plugin/public'; import { i18n } from '@kbn/i18n'; import useAsync from 'react-use/lib/useAsync'; -import { InfraClientSetupDeps, LensAttributes, LensOptions } from '../types'; +import { InfraClientSetupDeps } from '../types'; import { buildLensAttributes, - HostLensAttributesTypes, - hostMetricsLensAttributes, + HostsLensFormulas, + HostsLensMetricChartFormulas, + HostsLensLineChartFormulas, + LineChartOptions, + MetricChartOptions, + LensAttributes, + hostLensFormulas, + visualizationTypes, } from '../common/visualizations'; -interface UseLensAttributesParams { - type: HostLensAttributesTypes; +type Options = LineChartOptions | MetricChartOptions; +interface UseLensAttributesBaseParams { dataView: DataView | undefined; - options?: LensOptions; + type: T; + options?: O; } +interface UseLensAttributesLineChartParams + extends UseLensAttributesBaseParams { + visualizationType: 'lineChart'; +} + +interface UseLensAttributesMetricChartParams + extends UseLensAttributesBaseParams { + visualizationType: 'metricChart'; +} + +type UseLensAttributesParams = + | UseLensAttributesLineChartParams + | UseLensAttributesMetricChartParams; + export const useLensAttributes = ({ type, dataView, - options = { - breakdownSize: 10, - }, + options, + visualizationType, }: UseLensAttributesParams) => { const { services: { lens }, @@ -39,31 +59,31 @@ export const useLensAttributes = ({ const { value, error } = useAsync(lens.stateHelperApi, [lens]); const { formula: formulaAPI } = value ?? {}; - const attributes: LensAttributes | null = useMemo(() => { + const attributes = useMemo(() => { if (!dataView || !formulaAPI) { return null; } - const VisualizationClass = hostMetricsLensAttributes[type]; + const lensChartConfig = hostLensFormulas[type]; + const VisualizationType = visualizationTypes[visualizationType]; + const visualizationAttributes = buildLensAttributes( - new VisualizationClass(dataView, options, formulaAPI) + new VisualizationType(lensChartConfig, dataView, formulaAPI, options) ); return visualizationAttributes; - }, [dataView, formulaAPI, options, type]); + }, [dataView, formulaAPI, options, type, visualizationType]); - const injectData = (data: { + const injectFilters = (data: { + timeRange: TimeRange; filters: Filter[]; query: Query; - title?: string; }): LensAttributes | null => { if (!attributes) { return null; } - return { ...attributes, - ...(!!data.title ? { title: data.title } : {}), state: { ...attributes.state, query: data.query, @@ -72,7 +92,15 @@ export const useLensAttributes = ({ }; }; - const getExtraActions = (currentAttributes: LensAttributes | null, timeRange: TimeRange) => { + const getExtraActions = ({ + timeRange, + filters, + query, + }: { + timeRange: TimeRange; + filters: Filter[]; + query: Query; + }) => { return { openInLens: { id: 'openInLens', @@ -93,12 +121,13 @@ export const useLensAttributes = ({ return true; }, async execute(_context: ActionExecutionContext): Promise { - if (currentAttributes) { + const injectedAttributes = injectFilters({ timeRange, filters, query }); + if (injectedAttributes) { navigateToPrefilledEditor( { id: '', timeRange, - attributes: currentAttributes, + attributes: injectedAttributes, }, { openInNewTab: true, @@ -111,5 +140,5 @@ export const useLensAttributes = ({ }; }; - return { attributes, injectData, getExtraActions, error }; + return { attributes, getExtraActions, error }; }; diff --git a/x-pack/plugins/infra/public/pages/metrics/hosts/components/chart/lens_wrapper.tsx b/x-pack/plugins/infra/public/pages/metrics/hosts/components/chart/lens_wrapper.tsx new file mode 100644 index 0000000000000..9985db0751fd4 --- /dev/null +++ b/x-pack/plugins/infra/public/pages/metrics/hosts/components/chart/lens_wrapper.tsx @@ -0,0 +1,93 @@ +/* + * Copyright 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, useState } from 'react'; + +import { Action } from '@kbn/ui-actions-plugin/public'; +import { ViewMode } from '@kbn/embeddable-plugin/public'; +import { BrushTriggerEvent } from '@kbn/charts-plugin/public'; +import { EuiFlexGroup } from '@elastic/eui'; +import { EuiFlexItem } from '@elastic/eui'; +import { EuiLoadingChart } from '@elastic/eui'; +import { Filter, Query, TimeRange } from '@kbn/es-query'; +import { useKibanaContextForPlugin } from '../../../../../hooks/use_kibana'; +import { useIntersectedOnce } from '../../../../../hooks/use_intersection_once'; +import { LensAttributes } from '../../../../../common/visualizations'; + +export interface Props { + id: string; + attributes: LensAttributes | null; + dateRange: TimeRange; + query: Query; + filters: Filter[]; + extraActions: Action[]; + lastReloadRequestTime?: number; + style?: React.CSSProperties; + onBrushEnd?: (data: BrushTriggerEvent['data']) => void; +} + +export const LensWrapper = ({ + attributes, + dateRange, + filters, + id, + query, + extraActions, + style, + onBrushEnd, + lastReloadRequestTime, +}: Props) => { + const intersectionRef = React.useRef(null); + + const [currentLastReloadRequestTime, setCurrentLastReloadRequestTime] = useState< + number | undefined + >(lastReloadRequestTime); + const { + services: { lens }, + } = useKibanaContextForPlugin(); + const { intersectedOnce, intersection } = useIntersectedOnce(intersectionRef, { + threshold: 1, + }); + + const EmbeddableComponent = lens.EmbeddableComponent; + + useEffect(() => { + if ((intersection?.intersectionRatio ?? 0) === 1) { + setCurrentLastReloadRequestTime(lastReloadRequestTime); + } + }, [intersection?.intersectionRatio, lastReloadRequestTime]); + + const isReady = attributes && intersectedOnce; + + return ( +
+ {!isReady ? ( + + + + + + ) : ( + + )} +
+ ); +}; diff --git a/x-pack/plugins/infra/public/pages/metrics/hosts/components/kpi_charts/kpi_chart.tsx b/x-pack/plugins/infra/public/pages/metrics/hosts/components/chart/metric_chart_wrapper.tsx similarity index 65% rename from x-pack/plugins/infra/public/pages/metrics/hosts/components/kpi_charts/kpi_chart.tsx rename to x-pack/plugins/infra/public/pages/metrics/hosts/components/chart/metric_chart_wrapper.tsx index dc2a4d7d72c86..9df937983ae1e 100644 --- a/x-pack/plugins/infra/public/pages/metrics/hosts/components/kpi_charts/kpi_chart.tsx +++ b/x-pack/plugins/infra/public/pages/metrics/hosts/components/chart/metric_chart_wrapper.tsx @@ -4,7 +4,7 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import React, { useMemo } from 'react'; +import React, { useEffect, useMemo, useRef } from 'react'; import { Chart, Metric, @@ -12,13 +12,15 @@ import { type MetricWNumber, type MetricWTrend, } from '@elastic/charts'; - import { EuiPanel } from '@elastic/eui'; import styled from 'styled-components'; import { EuiLoadingChart } from '@elastic/eui'; import { EuiFlexGroup } from '@elastic/eui'; import { EuiFlexItem } from '@elastic/eui'; import { EuiToolTip } from '@elastic/eui'; +import { EuiProgress } from '@elastic/eui'; +import { css } from '@emotion/react'; +import { useEuiTheme } from '@elastic/eui'; import type { SnapshotNode, SnapshotNodeMetric } from '../../../../../../common/http_api'; import { createInventoryMetricFormatter } from '../../../inventory_view/lib/create_inventory_metric_formatter'; import type { SnapshotMetricType } from '../../../../../../common/inventory_models/types'; @@ -47,7 +49,7 @@ interface Props extends ChartBaseProps { const MIN_HEIGHT = 150; -export const KPIChart = ({ +export const MetricChartWrapper = ({ color, extra, id, @@ -63,12 +65,23 @@ export const KPIChart = ({ type, ...props }: Props) => { + const { euiTheme } = useEuiTheme(); + const loadedOnce = useRef(false); const metrics = useMemo(() => (nodes ?? [])[0]?.metrics ?? [], [nodes]); const metricsTimeseries = useMemo( () => (metrics ?? []).find((m) => m.name === type)?.timeseries, [metrics, type] ); + useEffect(() => { + if (!loadedOnce.current && !loading) { + loadedOnce.current = true; + } + return () => { + loadedOnce.current = false; + }; + }, [loading]); + const metricsValue = useMemo(() => { if (overrideValue) { return overrideValue; @@ -96,24 +109,39 @@ export const KPIChart = ({ return ( - {loading ? ( - - - - - - ) : ( - - - - - - )} +
+ {loading && ( + + )} + {loading && !loadedOnce.current ? ( + + + + + + ) : ( + + + + + + )} +
); }; diff --git a/x-pack/plugins/infra/public/pages/metrics/hosts/components/hosts_container.tsx b/x-pack/plugins/infra/public/pages/metrics/hosts/components/hosts_container.tsx index 9bae6b9ae1df0..e8e8a8a8e7c4f 100644 --- a/x-pack/plugins/infra/public/pages/metrics/hosts/components/hosts_container.tsx +++ b/x-pack/plugins/infra/public/pages/metrics/hosts/components/hosts_container.tsx @@ -13,9 +13,9 @@ import { useMetricsDataViewContext } from '../hooks/use_data_view'; import { UnifiedSearchBar } from './unified_search_bar'; import { HostsTable } from './hosts_table'; import { HostsViewProvider } from '../hooks/use_hosts_view'; -import { KPICharts } from './kpi_charts/kpi_charts'; import { Tabs } from './tabs/tabs'; import { AlertsQueryProvider } from '../hooks/use_alerts_query'; +import { KPIGrid } from './kpis/kpi_grid'; export const HostContainer = () => { const { dataView, loading, hasError } = useMetricsDataViewContext(); @@ -40,7 +40,7 @@ export const HostContainer = () => { - + diff --git a/x-pack/plugins/infra/public/pages/metrics/hosts/components/hosts_table.tsx b/x-pack/plugins/infra/public/pages/metrics/hosts/components/hosts_table.tsx index 798147bc1da0a..ca6f904ceea84 100644 --- a/x-pack/plugins/infra/public/pages/metrics/hosts/components/hosts_table.tsx +++ b/x-pack/plugins/infra/public/pages/metrics/hosts/components/hosts_table.tsx @@ -49,7 +49,7 @@ export const HostsTable = () => { if (loading) { return ( { - return ( - - - - - - - - - - - - - - - - - - ); -}; diff --git a/x-pack/plugins/infra/public/pages/metrics/hosts/components/kpi_charts/tile.tsx b/x-pack/plugins/infra/public/pages/metrics/hosts/components/kpi_charts/tile.tsx deleted file mode 100644 index 3f396ebcd7bf8..0000000000000 --- a/x-pack/plugins/infra/public/pages/metrics/hosts/components/kpi_charts/tile.tsx +++ /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 React from 'react'; -import type { SnapshotMetricType } from '../../../../../../common/inventory_models/types'; - -import { useSnapshot } from '../../../inventory_view/hooks/use_snaphot'; -import { useHostsViewContext } from '../../hooks/use_hosts_view'; -import { type ChartBaseProps, KPIChart } from './kpi_chart'; - -interface Props extends Omit { - type: SnapshotMetricType; -} -export const Tile = ({ type, ...props }: Props) => { - const { baseRequest } = useHostsViewContext(); - - const { nodes, loading } = useSnapshot( - { - ...baseRequest, - metrics: [{ type }], - groupBy: null, - includeTimeseries: true, - dropPartialBuckets: false, - }, - { abortable: true } - ); - - return ; -}; diff --git a/x-pack/plugins/infra/public/pages/metrics/hosts/components/kpi_charts/hosts_tile.tsx b/x-pack/plugins/infra/public/pages/metrics/hosts/components/kpis/hosts_tile.tsx similarity index 81% rename from x-pack/plugins/infra/public/pages/metrics/hosts/components/kpi_charts/hosts_tile.tsx rename to x-pack/plugins/infra/public/pages/metrics/hosts/components/kpis/hosts_tile.tsx index b6973327101f0..396c0bd72ad71 100644 --- a/x-pack/plugins/infra/public/pages/metrics/hosts/components/kpi_charts/hosts_tile.tsx +++ b/x-pack/plugins/infra/public/pages/metrics/hosts/components/kpis/hosts_tile.tsx @@ -7,14 +7,14 @@ import React from 'react'; import { useHostsViewContext } from '../../hooks/use_hosts_view'; -import { type ChartBaseProps, KPIChart } from './kpi_chart'; +import { type ChartBaseProps, MetricChartWrapper } from '../chart/metric_chart_wrapper'; export const HostsTile = ({ type, ...props }: ChartBaseProps) => { const { hostNodes, loading } = useHostsViewContext(); return ( - { + return ( + + + + + {KPI_CHARTS.map(({ ...chartProp }) => ( + + + + ))} + + ); +}; diff --git a/x-pack/plugins/infra/public/pages/metrics/hosts/components/kpis/tile.tsx b/x-pack/plugins/infra/public/pages/metrics/hosts/components/kpis/tile.tsx new file mode 100644 index 0000000000000..480e6c415dc45 --- /dev/null +++ b/x-pack/plugins/infra/public/pages/metrics/hosts/components/kpis/tile.tsx @@ -0,0 +1,137 @@ +/* + * Copyright 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 { Action } from '@kbn/ui-actions-plugin/public'; +import { BrushTriggerEvent } from '@kbn/charts-plugin/public'; +import { EuiIcon, EuiPanel } from '@elastic/eui'; +import { EuiFlexGroup } from '@elastic/eui'; +import { EuiFlexItem } from '@elastic/eui'; +import { EuiText } from '@elastic/eui'; +import { EuiI18n } from '@elastic/eui'; +import styled from 'styled-components'; +import { EuiToolTip } from '@elastic/eui'; +import { useLensAttributes } from '../../../../../hooks/use_lens_attributes'; +import { useMetricsDataViewContext } from '../../hooks/use_data_view'; +import { useUnifiedSearchContext } from '../../hooks/use_unified_search'; +import { HostsLensMetricChartFormulas } from '../../../../../common/visualizations'; +import { useHostsViewContext } from '../../hooks/use_hosts_view'; +import { LensWrapper } from '../chart/lens_wrapper'; + +export interface KPIChartProps { + title: string; + subtitle?: string; + trendLine?: boolean; + backgroundColor: string; + type: HostsLensMetricChartFormulas; + toolTip: string; +} + +const MIN_HEIGHT = 150; + +export const Tile = ({ + title, + subtitle, + type, + backgroundColor, + toolTip, + trendLine = false, +}: KPIChartProps) => { + const { searchCriteria, onSubmit } = useUnifiedSearchContext(); + const { dataView } = useMetricsDataViewContext(); + const { baseRequest } = useHostsViewContext(); + + const { attributes, getExtraActions, error } = useLensAttributes({ + type, + dataView, + options: { + title, + subtitle, + backgroundColor, + showTrendLine: trendLine, + showTitle: false, + }, + visualizationType: 'metricChart', + }); + + const filters = [...searchCriteria.filters, ...searchCriteria.panelFilters]; + const extraActionOptions = getExtraActions({ + timeRange: searchCriteria.dateRange, + filters, + query: searchCriteria.query, + }); + + const extraActions: Action[] = [extraActionOptions.openInLens]; + + const handleBrushEnd = ({ range }: BrushTriggerEvent['data']) => { + const [min, max] = range; + onSubmit({ + dateRange: { + from: new Date(min).toISOString(), + to: new Date(max).toISOString(), + mode: 'absolute', + }, + }); + }; + + return ( + + {error ? ( + + + + + + + + + + + ) : ( + + + + )} + + ); +}; + +const EuiPanelStyled = styled(EuiPanel)` + .echMetric { + border-radius: ${(p) => p.theme.eui.euiBorderRadius}; + pointer-events: none; + } +`; diff --git a/x-pack/plugins/infra/public/pages/metrics/hosts/components/tabs/metrics/metric_chart.tsx b/x-pack/plugins/infra/public/pages/metrics/hosts/components/tabs/metrics/metric_chart.tsx index 9563dbaa6169e..252bea5389e3a 100644 --- a/x-pack/plugins/infra/public/pages/metrics/hosts/components/tabs/metrics/metric_chart.tsx +++ b/x-pack/plugins/infra/public/pages/metrics/hosts/components/tabs/metrics/metric_chart.tsx @@ -5,26 +5,25 @@ * 2.0. */ import React from 'react'; -import { useKibana } from '@kbn/kibana-react-plugin/public'; import { Action } from '@kbn/ui-actions-plugin/public'; -import { ViewMode } from '@kbn/embeddable-plugin/public'; import { BrushTriggerEvent } from '@kbn/charts-plugin/public'; import { EuiIcon, EuiPanel } from '@elastic/eui'; import { EuiFlexGroup } from '@elastic/eui'; import { EuiFlexItem } from '@elastic/eui'; import { EuiText } from '@elastic/eui'; import { EuiI18n } from '@elastic/eui'; -import { InfraClientSetupDeps } from '../../../../../../types'; import { useLensAttributes } from '../../../../../../hooks/use_lens_attributes'; import { useMetricsDataViewContext } from '../../../hooks/use_data_view'; import { useUnifiedSearchContext } from '../../../hooks/use_unified_search'; -import { HostLensAttributesTypes } from '../../../../../../common/visualizations'; +import { HostsLensLineChartFormulas } from '../../../../../../common/visualizations'; import { useHostsViewContext } from '../../../hooks/use_hosts_view'; +import { LensWrapper } from '../../chart/lens_wrapper'; export interface MetricChartProps { title: string; - type: HostLensAttributesTypes; + type: HostsLensLineChartFormulas; breakdownSize: number; + render?: boolean; } const MIN_HEIGHT = 300; @@ -33,28 +32,25 @@ export const MetricChart = ({ title, type, breakdownSize }: MetricChartProps) => const { searchCriteria, onSubmit } = useUnifiedSearchContext(); const { dataView } = useMetricsDataViewContext(); const { baseRequest } = useHostsViewContext(); - const { - services: { lens }, - } = useKibana(); - const EmbeddableComponent = lens.EmbeddableComponent; - - const { injectData, getExtraActions, error } = useLensAttributes({ + const { attributes, getExtraActions, error } = useLensAttributes({ type, dataView, options: { + title, breakdownSize, }, + visualizationType: 'lineChart', }); - const injectedLensAttributes = injectData({ - filters: [...searchCriteria.filters, ...searchCriteria.panelFilters], + const filters = [...searchCriteria.filters, ...searchCriteria.panelFilters]; + const extraActionOptions = getExtraActions({ + timeRange: searchCriteria.dateRange, + filters, query: searchCriteria.query, - title, }); - const extraActionOptions = getExtraActions(injectedLensAttributes, searchCriteria.dateRange); - const extraAction: Action[] = [extraActionOptions.openInLens]; + const extraActions: Action[] = [extraActionOptions.openInLens]; const handleBrushEnd = ({ range }: BrushTriggerEvent['data']) => { const [min, max] = range; @@ -97,24 +93,17 @@ export const MetricChart = ({ title, type, breakdownSize }: MetricChartProps) => ) : ( - injectedLensAttributes && ( - - ) + )} ); diff --git a/x-pack/plugins/infra/public/types.ts b/x-pack/plugins/infra/public/types.ts index 0b961e0b8ed36..ac8c8316a7083 100644 --- a/x-pack/plugins/infra/public/types.ts +++ b/x-pack/plugins/infra/public/types.ts @@ -34,7 +34,7 @@ import type { // import type { OsqueryPluginStart } from '../../osquery/public'; import type { SpacesPluginStart } from '@kbn/spaces-plugin/public'; import type { IStorageWrapper } from '@kbn/kibana-utils-plugin/public'; -import { type TypedLensByValueInput, LensPublicStart } from '@kbn/lens-plugin/public'; +import { LensPublicStart } from '@kbn/lens-plugin/public'; import type { ChartsPluginStart } from '@kbn/charts-plugin/public'; import { CasesUiStart } from '@kbn/cases-plugin/public'; import type { UnwrapPromise } from '../common/utility_types'; @@ -112,12 +112,6 @@ export interface InfraHttpError extends IHttpFetchError { }; } -export type LensAttributes = TypedLensByValueInput['attributes']; - -export interface LensOptions { - breakdownSize: number; -} - export interface ExecutionTimeRange { gte: number; lte: number; diff --git a/x-pack/plugins/translations/translations/fr-FR.json b/x-pack/plugins/translations/translations/fr-FR.json index b8e6993bf9d6a..05a0176af89d8 100644 --- a/x-pack/plugins/translations/translations/fr-FR.json +++ b/x-pack/plugins/translations/translations/fr-FR.json @@ -17427,26 +17427,18 @@ "xpack.infra.hostsViewPage.landing.introTitle": "Présentation : Analyse de l'hôte", "xpack.infra.hostsViewPage.landing.learnMore": "En savoir plus", "xpack.infra.hostsViewPage.landing.tryTheFeatureMessage": "Il s'agit d'une version préliminaire de la fonctionnalité, et nous souhaiterions vivement connaître votre avis tandis que nous continuons\n à la développer et à l'améliorer. Pour accéder à cette fonctionnalité, il suffit de l'activer ci-dessous. Ne passez pas à côté\n de cette nouvelle et puissante fonctionnalité ajoutée à notre plateforme... Essayez-la aujourd'hui même !", - "xpack.infra.hostsViewPage.metricTrend.cpu.a11y.description": "Graphique linéaire affichant la tendance de l'indicateur principal sur la durée.", - "xpack.infra.hostsViewPage.metricTrend.cpu.a11y.title": "Utilisation CPU sur la durée.", "xpack.infra.hostsViewPage.metricTrend.cpu.subtitle": "Moyenne", "xpack.infra.hostsViewPage.metricTrend.cpu.title": "Utilisation CPU", "xpack.infra.hostsViewPage.metricTrend.cpu.tooltip": "Moyenne de pourcentage de temps CPU utilisé dans les états autres que Inactif et IOWait, normalisée par le nombre de cœurs de processeur. Inclut le temps passé à la fois sur l'espace utilisateur et sur l'espace du noyau. 100 % signifie que tous les processeurs de l'hôte sont occupés.", "xpack.infra.hostsViewPage.metricTrend.hostCount.a11y.title": "Utilisation CPU sur la durée.", "xpack.infra.hostsViewPage.metricTrend.hostCount.title": "Hôtes", "xpack.infra.hostsViewPage.metricTrend.hostCount.tooltip": "Nombre d'hôtes renvoyé par vos critères de recherche actuels.", - "xpack.infra.hostsViewPage.metricTrend.memory.a11yDescription": "Graphique linéaire affichant la tendance de l'indicateur principal sur la durée.", - "xpack.infra.hostsViewPage.metricTrend.memory.a11yTitle": "Utilisation de la mémoire sur la durée.", "xpack.infra.hostsViewPage.metricTrend.memory.subtitle": "Moyenne", "xpack.infra.hostsViewPage.metricTrend.memory.title": "Utilisation mémoire", "xpack.infra.hostsViewPage.metricTrend.memory.tooltip": "Moyenne de pourcentage d'utilisation de la mémoire principale, en excluant le cache de pages. Cela inclut la mémoire résidente pour tous les processus, plus la mémoire utilisée par les structures et le code du noyau, à l'exception du cache de pages. Un niveau élevé indique une situation de saturation de la mémoire pour un hôte. 100 % signifie que la mémoire principale est entièrement remplie par de la mémoire ne pouvant pas être récupérée, sauf en l'échangeant.", - "xpack.infra.hostsViewPage.metricTrend.rx.a11y.description": "Graphique linéaire affichant la tendance de l'indicateur principal sur la durée.", - "xpack.infra.hostsViewPage.metricTrend.rx.a11y.title": "Réseau entrant (RX) sur la durée.", "xpack.infra.hostsViewPage.metricTrend.rx.subtitle": "Moyenne", "xpack.infra.hostsViewPage.metricTrend.rx.title": "Réseau entrant (RX)", "xpack.infra.hostsViewPage.metricTrend.rx.tooltip": "Nombre d'octets qui ont été reçus par seconde sur les interfaces publiques des hôtes.", - "xpack.infra.hostsViewPage.metricTrend.tx.a11.title": "Utilisation de réseau sortant (TX) sur la durée.", - "xpack.infra.hostsViewPage.metricTrend.tx.a11y.description": "Graphique linéaire affichant la tendance de l'indicateur principal sur la durée.", "xpack.infra.hostsViewPage.metricTrend.tx.subtitle": "Moyenne", "xpack.infra.hostsViewPage.metricTrend.tx.title": "Réseau sortant (TX)", "xpack.infra.hostsViewPage.metricTrend.tx.tooltip": "Nombre d'octets qui ont été envoyés par seconde sur les interfaces publiques des hôtes", @@ -38121,4 +38113,4 @@ "xpack.painlessLab.title": "Painless Lab", "xpack.painlessLab.walkthroughButtonLabel": "Présentation" } -} +} \ 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 7d3317df1bf37..6eaa5a4584e7d 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -17426,26 +17426,18 @@ "xpack.infra.hostsViewPage.landing.introTitle": "導入:ホスト分析", "xpack.infra.hostsViewPage.landing.learnMore": "詳細", "xpack.infra.hostsViewPage.landing.tryTheFeatureMessage": "この機能は初期バージョンであり、今後継続する中で、開発、改善するうえで皆様からのフィードバックをお願いします\n 。機能にアクセスするには、以下を有効にします。プラットフォームに\n 追加されたこの強力な新機能をお見逃しなく。今すぐお試しください!", - "xpack.infra.hostsViewPage.metricTrend.cpu.a11y.description": "主要なメトリックの経時的な傾向を示す折れ線グラフ。", - "xpack.infra.hostsViewPage.metricTrend.cpu.a11y.title": "経時的なCPU使用状況。", "xpack.infra.hostsViewPage.metricTrend.cpu.subtitle": "平均", "xpack.infra.hostsViewPage.metricTrend.cpu.title": "CPU 使用状況", "xpack.infra.hostsViewPage.metricTrend.cpu.tooltip": "アイドルおよびIOWait以外の状態で費やされたCPU時間の割合の平均値を、CPUコア数で正規化したもの。ユーザースペースとカーネルスペースの両方で費やされた時間が含まれます。100%はホストのすべてのCPUがビジー状態であることを示します。", "xpack.infra.hostsViewPage.metricTrend.hostCount.a11y.title": "経時的なCPU使用状況。", "xpack.infra.hostsViewPage.metricTrend.hostCount.title": "ホスト", "xpack.infra.hostsViewPage.metricTrend.hostCount.tooltip": "現在の検索条件から返されたホストの数です。", - "xpack.infra.hostsViewPage.metricTrend.memory.a11yDescription": "主要なメトリックの経時的な傾向を示す折れ線グラフ。", - "xpack.infra.hostsViewPage.metricTrend.memory.a11yTitle": "経時的なメモリ使用状況。", "xpack.infra.hostsViewPage.metricTrend.memory.subtitle": "平均", "xpack.infra.hostsViewPage.metricTrend.memory.title": "メモリー使用状況", "xpack.infra.hostsViewPage.metricTrend.memory.tooltip": "ページキャッシュを除いたメインメモリの割合の平均値。これには、すべてのプロセスの常駐メモリと、ページキャッシュを離れてカーネル構造とコードによって使用されるメモリが含まれます。高レベルは、ホストのメモリが飽和状態にあることを示します。100%とは、メインメモリがすべてスワップアウト以外の、再利用不可能なメモリで満たされていることを意味します。", - "xpack.infra.hostsViewPage.metricTrend.rx.a11y.description": "主要なメトリックの経時的な傾向を示す折れ線グラフ。", - "xpack.infra.hostsViewPage.metricTrend.rx.a11y.title": "経時的なネットワーク受信(RX)。", "xpack.infra.hostsViewPage.metricTrend.rx.subtitle": "平均", "xpack.infra.hostsViewPage.metricTrend.rx.title": "ネットワーク受信(RX)", "xpack.infra.hostsViewPage.metricTrend.rx.tooltip": "ホストのパブリックインターフェースで1秒間に受信したバイト数。", - "xpack.infra.hostsViewPage.metricTrend.tx.a11.title": "経時的なネットワーク送信(TX)。", - "xpack.infra.hostsViewPage.metricTrend.tx.a11y.description": "主要なメトリックの経時的な傾向を示す折れ線グラフ。", "xpack.infra.hostsViewPage.metricTrend.tx.subtitle": "平均", "xpack.infra.hostsViewPage.metricTrend.tx.title": "ネットワーク送信(TX)", "xpack.infra.hostsViewPage.metricTrend.tx.tooltip": "ホストのパブリックインターフェースで1秒間に送信したバイト数", @@ -38089,4 +38081,4 @@ "xpack.painlessLab.title": "Painless Lab", "xpack.painlessLab.walkthroughButtonLabel": "実地検証" } -} +} \ 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 dce328063bf26..fb3cdb9c46c01 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -17427,26 +17427,18 @@ "xpack.infra.hostsViewPage.landing.introTitle": "即将引入:主机分析", "xpack.infra.hostsViewPage.landing.learnMore": "了解详情", "xpack.infra.hostsViewPage.landing.tryTheFeatureMessage": "这是早期版本的功能,我们需要您的反馈,\n 以便继续开发和改进该功能。要访问该功能,直接在下面启用即可。请抓紧时间,\n 了解新添加到我们平台中的这项强大功能 - 立即试用!", - "xpack.infra.hostsViewPage.metricTrend.cpu.a11y.description": "显示一段时间的主要指标趋势的折线图。", - "xpack.infra.hostsViewPage.metricTrend.cpu.a11y.title": "一段时间的 CPU 使用率。", "xpack.infra.hostsViewPage.metricTrend.cpu.subtitle": "平均值", "xpack.infra.hostsViewPage.metricTrend.cpu.title": "CPU 使用", "xpack.infra.hostsViewPage.metricTrend.cpu.tooltip": "CPU 在空闲和 IOWait 状态以外所花费时间的平均百分比,按 CPU 核心数进行标准化。包括在用户空间和内核空间上花费的时间。100% 表示主机的所有 CPU 都处于忙碌状态。", "xpack.infra.hostsViewPage.metricTrend.hostCount.a11y.title": "一段时间的 CPU 使用率。", "xpack.infra.hostsViewPage.metricTrend.hostCount.title": "主机", "xpack.infra.hostsViewPage.metricTrend.hostCount.tooltip": "当前搜索条件返回的主机数。", - "xpack.infra.hostsViewPage.metricTrend.memory.a11yDescription": "显示一段时间的主要指标趋势的折线图。", - "xpack.infra.hostsViewPage.metricTrend.memory.a11yTitle": "一段时间的内存使用率。", "xpack.infra.hostsViewPage.metricTrend.memory.subtitle": "平均值", "xpack.infra.hostsViewPage.metricTrend.memory.title": "内存使用", "xpack.infra.hostsViewPage.metricTrend.memory.tooltip": "主内存使用率(不包括页面缓存)的平均百分比。这包括所有进程的常驻内存,加上由内核结构和代码使用的内存,但不包括页面缓存。高比率表明主机出现内存饱和情况。100% 表示主内存被完全占用,除了进行换出外无法回收内存。", - "xpack.infra.hostsViewPage.metricTrend.rx.a11y.description": "显示一段时间的主要指标趋势的折线图。", - "xpack.infra.hostsViewPage.metricTrend.rx.a11y.title": "一段时间的网络入站数据 (RX)。", "xpack.infra.hostsViewPage.metricTrend.rx.subtitle": "平均值", "xpack.infra.hostsViewPage.metricTrend.rx.title": "网络入站数据 (RX)", "xpack.infra.hostsViewPage.metricTrend.rx.tooltip": "主机的公共接口上每秒接收的字节数。", - "xpack.infra.hostsViewPage.metricTrend.tx.a11.title": "一段时间的网络出站数据 (TX) 使用量。", - "xpack.infra.hostsViewPage.metricTrend.tx.a11y.description": "显示一段时间的主要指标趋势的折线图。", "xpack.infra.hostsViewPage.metricTrend.tx.subtitle": "平均值", "xpack.infra.hostsViewPage.metricTrend.tx.title": "网络出站数据 (TX)", "xpack.infra.hostsViewPage.metricTrend.tx.tooltip": "主机的公共接口上每秒发送的字节数", @@ -38116,4 +38108,4 @@ "xpack.painlessLab.title": "Painless 实验室", "xpack.painlessLab.walkthroughButtonLabel": "指导" } -} +} \ No newline at end of file diff --git a/x-pack/test/functional/apps/infra/hosts_view.ts b/x-pack/test/functional/apps/infra/hosts_view.ts index b22bb934109dc..3e229fcdc1142 100644 --- a/x-pack/test/functional/apps/infra/hosts_view.ts +++ b/x-pack/test/functional/apps/infra/hosts_view.ts @@ -367,9 +367,9 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { [ { metric: 'hosts', value: '6' }, { metric: 'cpu', value: '0.8%' }, - { metric: 'memory', value: '16.8%' }, - { metric: 'tx', value: '0 bit/s' }, - { metric: 'rx', value: '0 bit/s' }, + { metric: 'memory', value: '16.81%' }, + { metric: 'tx', value: 'N/A' }, + { metric: 'rx', value: 'N/A' }, ].forEach(({ metric, value }) => { it(`${metric} tile should show ${value}`, async () => { const tileValue = await pageObjects.infraHostsView.getMetricsTrendTileValue(metric); @@ -497,9 +497,9 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { [ { metric: 'hosts', value: '3' }, { metric: 'cpu', value: '0.8%' }, - { metric: 'memory', value: '16.2%' }, - { metric: 'tx', value: '0 bit/s' }, - { metric: 'rx', value: '0 bit/s' }, + { metric: 'memory', value: '16.25%' }, + { metric: 'tx', value: 'N/A' }, + { metric: 'rx', value: 'N/A' }, ].map(async ({ metric, value }) => { const tileValue = await pageObjects.infraHostsView.getMetricsTrendTileValue(metric); expect(tileValue).to.eql(value); From fc3496902a722f768481657f1f61f3806f3d9baa Mon Sep 17 00:00:00 2001 From: Ying Mao Date: Wed, 19 Apr 2023 10:08:05 -0400 Subject: [PATCH 41/78] [Response Ops][Alerting] Aliasing context and state variables for detection rules in alert summary mode (#154864) Toward enabling per-action-alerts for detection rules https://github.com/elastic/kibana/pull/154977 ## Summary This PR provides the necessary changes for detection rules to fully onboard onto alerting framework alert summaries. * Aliases detection rule context and state variables for summary action variables. This provides backwards compatibility with the `context.alerts`, `context.results_link` and `state.signals_count` action variables that are currently used by detection rules. * Calculates time bounds for summary alerts that can be passed back to the view in app URL generator. This allows rule types to generate view in app URLs limited to the timeframe that will match the summary alerts time range * For throttled summary alerts, the time range is generated as `now - throttle duration` * For per execution summary alerts, we use the `previousStartedAt` time from the task state if available and the schedule duration if not available. This is because some rules write out alerts with `@timestamp: task.startedAt` so just using `now - schedule duration` may not capture those alerts due to task manager schedule delays. --------- Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../task_runner/execution_handler.test.ts | 71 +++++++++++++++++++ .../server/task_runner/execution_handler.ts | 15 +++- .../task_runner/rule_action_helper.test.ts | 60 ++++++++++++++++ .../server/task_runner/rule_action_helper.ts | 30 ++++++++ .../server/task_runner/task_runner.ts | 1 + .../transform_action_params.test.ts | 31 +++++++- .../task_runner/transform_action_params.ts | 14 ++++ .../alerting/server/task_runner/types.ts | 1 + x-pack/plugins/alerting/server/types.ts | 3 + 9 files changed, 221 insertions(+), 5 deletions(-) diff --git a/x-pack/plugins/alerting/server/task_runner/execution_handler.test.ts b/x-pack/plugins/alerting/server/task_runner/execution_handler.test.ts index de3e05dae82ce..a83211aa9d040 100644 --- a/x-pack/plugins/alerting/server/task_runner/execution_handler.test.ts +++ b/x-pack/plugins/alerting/server/task_runner/execution_handler.test.ts @@ -21,6 +21,7 @@ import { RuleTypeParams, RuleTypeState, SanitizedRule, + GetViewInAppRelativeUrlFnOpts, } from '../types'; import { RuleRunMetricsStore } from '../lib/rule_run_metrics_store'; import { alertingEventLoggerMock } from '../lib/alerting_event_logger/alerting_event_logger.mock'; @@ -80,6 +81,7 @@ const rule = { contextVal: 'My other {{context.value}} goes here', stateVal: 'My other {{state.value}} goes here', }, + schedule: { interval: '1m' }, notifyWhen: 'onActiveAlert', actions: [ { @@ -116,6 +118,7 @@ const defaultExecutionParams = { ruleLabel: 'rule-label', request: {} as KibanaRequest, alertingEventLogger, + previousStartedAt: null, taskInstance: { params: { spaceId: 'test1', alertId: '1' }, } as unknown as ConcreteTaskInstance, @@ -1447,6 +1450,25 @@ describe('Execution Handler', () => { ], } as unknown as SanitizedRule; + const summaryRuleWithUrl = { + ...rule, + actions: [ + { + id: '1', + group: null, + actionTypeId: 'test', + frequency: { + summary: true, + notifyWhen: 'onActiveAlert', + throttle: null, + }, + params: { + val: 'rule url: {{rule.url}}', + }, + }, + ], + } as unknown as SanitizedRule; + it('populates the rule.url in the action params when the base url and rule id are specified', async () => { const execParams = { ...defaultExecutionParams, @@ -1474,6 +1496,55 @@ describe('Execution Handler', () => { `); }); + it('populates the rule.url with start and stop time when available', async () => { + clock.reset(); + clock.tick(90000); + getSummarizedAlertsMock.mockResolvedValue({ + new: { + count: 2, + data: [ + mockAAD, + { + ...mockAAD, + '@timestamp': '2022-12-07T15:45:41.4672Z', + alert: { instance: { id: 'all' } }, + }, + ], + }, + ongoing: { count: 0, data: [] }, + recovered: { count: 0, data: [] }, + }); + const execParams = { + ...defaultExecutionParams, + ruleType: { + ...ruleType, + getViewInAppRelativeUrl: (opts: GetViewInAppRelativeUrlFnOpts) => + `/app/test/rule/${opts.rule.id}?start=${opts.start ?? 0}&end=${opts.end ?? 0}`, + }, + rule: summaryRuleWithUrl, + taskRunnerContext: { + ...defaultExecutionParams.taskRunnerContext, + kibanaBaseUrl: 'http://localhost:12345', + }, + }; + + const executionHandler = new ExecutionHandler(generateExecutionParams(execParams)); + await executionHandler.run(generateAlert({ id: 1 })); + + expect(injectActionParamsMock.mock.calls[0]).toMatchInlineSnapshot(` + Array [ + Object { + "actionParams": Object { + "val": "rule url: http://localhost:12345/s/test1/app/test/rule/1?start=30000&end=90000", + }, + "actionTypeId": "test", + "ruleId": "1", + "spaceId": "test1", + }, + ] + `); + }); + it('populates the rule.url without the space specifier when the spaceId is the string "default"', async () => { const execParams = { ...defaultExecutionParams, diff --git a/x-pack/plugins/alerting/server/task_runner/execution_handler.ts b/x-pack/plugins/alerting/server/task_runner/execution_handler.ts index 18c06c0b36f56..083225de1f371 100644 --- a/x-pack/plugins/alerting/server/task_runner/execution_handler.ts +++ b/x-pack/plugins/alerting/server/task_runner/execution_handler.ts @@ -40,6 +40,7 @@ import { import { generateActionHash, getSummaryActionsFromTaskState, + getSummaryActionTimeBounds, isActionOnInterval, isSummaryAction, isSummaryActionOnInterval, @@ -91,6 +92,7 @@ export class ExecutionHandler< private actionsClient: PublicMethodsOf; private ruleTypeActionGroups?: Map; private mutedAlertIdsSet: Set = new Set(); + private previousStartedAt: Date | null; constructor({ rule, @@ -104,6 +106,7 @@ export class ExecutionHandler< ruleConsumer, executionId, ruleLabel, + previousStartedAt, actionsClient, }: ExecutionHandlerOptions< Params, @@ -130,6 +133,7 @@ export class ExecutionHandler< this.ruleTypeActionGroups = new Map( ruleType.actionGroups.map((actionGroup) => [actionGroup.id, actionGroup.name]) ); + this.previousStartedAt = previousStartedAt; this.mutedAlertIdsSet = new Set(rule.mutedInstanceIds); } @@ -205,6 +209,11 @@ export class ExecutionHandler< ruleRunMetricsStore.incrementNumberOfTriggeredActionsByConnectorType(actionTypeId); if (isSummaryAction(action) && summarizedAlerts) { + const { start, end } = getSummaryActionTimeBounds( + action, + this.rule.schedule, + this.previousStartedAt + ); const actionToRun = { ...action, params: injectActionParams({ @@ -221,7 +230,7 @@ export class ExecutionHandler< actionsPlugin, actionTypeId, kibanaBaseUrl: this.taskRunnerContext.kibanaBaseUrl, - ruleUrl: this.buildRuleUrl(spaceId), + ruleUrl: this.buildRuleUrl(spaceId, start, end), }), }), }; @@ -419,13 +428,13 @@ export class ExecutionHandler< return alert.getScheduledActionOptions()?.actionGroup || this.ruleType.recoveryActionGroup.id; } - private buildRuleUrl(spaceId: string): string | undefined { + private buildRuleUrl(spaceId: string, start?: number, end?: number): string | undefined { if (!this.taskRunnerContext.kibanaBaseUrl) { return; } const relativePath = this.ruleType.getViewInAppRelativeUrl - ? this.ruleType.getViewInAppRelativeUrl({ rule: this.rule }) + ? this.ruleType.getViewInAppRelativeUrl({ rule: this.rule, start, end }) : `${triggersActionsRoute}${getRuleDetailsRoute(this.rule.id)}`; try { diff --git a/x-pack/plugins/alerting/server/task_runner/rule_action_helper.test.ts b/x-pack/plugins/alerting/server/task_runner/rule_action_helper.test.ts index 244110472cec2..872672801b52c 100644 --- a/x-pack/plugins/alerting/server/task_runner/rule_action_helper.test.ts +++ b/x-pack/plugins/alerting/server/task_runner/rule_action_helper.test.ts @@ -14,8 +14,12 @@ import { isSummaryAction, isSummaryActionOnInterval, isSummaryActionThrottled, + getSummaryActionTimeBounds, } from './rule_action_helper'; +const now = '2021-05-13T12:33:37.000Z'; +Date.now = jest.fn().mockReturnValue(new Date(now)); + const mockOldAction: RuleAction = { id: '1', group: 'default', @@ -309,4 +313,60 @@ describe('rule_action_helper', () => { ).toBe(false); }); }); + + describe('getSummaryActionTimeBounds', () => { + test('returns undefined start and end action is not summary action', () => { + expect(getSummaryActionTimeBounds(mockAction, { interval: '1m' }, null)).toEqual({ + start: undefined, + end: undefined, + }); + }); + + test('returns start and end for summary action with throttle', () => { + const { start, end } = getSummaryActionTimeBounds( + mockSummaryAction, + { interval: '1m' }, + null + ); + expect(end).toEqual(1620909217000); + expect(end).toEqual(new Date(now).valueOf()); + expect(start).toEqual(1620822817000); + // start is end - throttle interval (1d) + expect(start).toEqual(new Date('2021-05-12T12:33:37.000Z').valueOf()); + }); + + test('returns start and end for summary action without throttle with previousStartedAt', () => { + const { start, end } = getSummaryActionTimeBounds( + { + ...mockSummaryAction, + frequency: { summary: true, notifyWhen: 'onActiveAlert', throttle: null }, + }, + { interval: '1m' }, + new Date('2021-05-13T12:31:57.000Z') + ); + + expect(end).toEqual(1620909217000); + expect(end).toEqual(new Date(now).valueOf()); + expect(start).toEqual(1620909117000); + // start is previous started at time + expect(start).toEqual(new Date('2021-05-13T12:31:57.000Z').valueOf()); + }); + + test('returns start and end for summary action without throttle without previousStartedAt', () => { + const { start, end } = getSummaryActionTimeBounds( + { + ...mockSummaryAction, + frequency: { summary: true, notifyWhen: 'onActiveAlert', throttle: null }, + }, + { interval: '1m' }, + null + ); + + expect(end).toEqual(1620909217000); + expect(end).toEqual(new Date(now).valueOf()); + expect(start).toEqual(1620909157000); + // start is end - schedule interval (1m) + expect(start).toEqual(new Date('2021-05-13T12:32:37.000Z').valueOf()); + }); + }); }); diff --git a/x-pack/plugins/alerting/server/task_runner/rule_action_helper.ts b/x-pack/plugins/alerting/server/task_runner/rule_action_helper.ts index 2918d346511ca..5150170a52bcd 100644 --- a/x-pack/plugins/alerting/server/task_runner/rule_action_helper.ts +++ b/x-pack/plugins/alerting/server/task_runner/rule_action_helper.ts @@ -7,6 +7,7 @@ import { Logger } from '@kbn/logging'; import { + IntervalSchedule, parseDuration, RuleAction, RuleNotifyWhenTypeValues, @@ -99,3 +100,32 @@ export const getSummaryActionsFromTaskState = ({ } }, {}); }; + +export const getSummaryActionTimeBounds = ( + action: RuleAction, + ruleSchedule: IntervalSchedule, + previousStartedAt: Date | null +): { start?: number; end?: number } => { + if (!isSummaryAction(action)) { + return { start: undefined, end: undefined }; + } + let startDate: Date; + const now = Date.now(); + + if (isActionOnInterval(action)) { + // If action is throttled, set time bounds using throttle interval + const throttleMills = parseDuration(action.frequency!.throttle!); + startDate = new Date(now - throttleMills); + } else { + // If action is not throttled, set time bounds to previousStartedAt - now + // If previousStartedAt is null, use the rule schedule interval + if (previousStartedAt) { + startDate = previousStartedAt; + } else { + const scheduleMillis = parseDuration(ruleSchedule.interval); + startDate = new Date(now - scheduleMillis); + } + } + + return { start: startDate.valueOf(), end: now.valueOf() }; +}; 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 45d22a8fbf2ac..466a785de4003 100644 --- a/x-pack/plugins/alerting/server/task_runner/task_runner.ts +++ b/x-pack/plugins/alerting/server/task_runner/task_runner.ts @@ -458,6 +458,7 @@ export class TaskRunner< ruleConsumer: this.ruleConsumer!, executionId: this.executionId, ruleLabel, + previousStartedAt: previousStartedAt ? new Date(previousStartedAt) : null, alertingEventLogger: this.alertingEventLogger, actionsClient: await this.context.actionsPlugin.getActionsClientWithRequest(fakeRequest), }); diff --git a/x-pack/plugins/alerting/server/task_runner/transform_action_params.test.ts b/x-pack/plugins/alerting/server/task_runner/transform_action_params.test.ts index a240105f7cab2..cd581afafa266 100644 --- a/x-pack/plugins/alerting/server/task_runner/transform_action_params.test.ts +++ b/x-pack/plugins/alerting/server/task_runner/transform_action_params.test.ts @@ -626,13 +626,13 @@ describe('transformSummaryActionParams', () => { new: { count: 1, data: [mockAAD] }, ongoing: { count: 0, data: [] }, recovered: { count: 0, data: [] }, - all: { count: 0, data: [] }, + all: { count: 1, data: [mockAAD] }, }, rule: { id: '1', name: 'test-rule', tags: ['test-tag'], - params: {}, + params: { foo: 'bar', fooBar: true }, } as SanitizedRule, ruleTypeId: 'rule-type-id', actionId: 'action-id', @@ -657,6 +657,33 @@ describe('transformSummaryActionParams', () => { `); }); + test('renders aliased context values', () => { + const actionParams = { + message: + 'Value "{{context.alerts}}", "{{context.results_link}}" and "{{context.rule}}" exist', + }; + + const result = transformSummaryActionParams({ ...params, actionParams }); + expect(result).toMatchInlineSnapshot(` + Object { + "message": "Value \\"{\\"@timestamp\\":\\"2022-12-07T15:38:43.472Z\\",\\"event\\":{\\"kind\\":\\"signal\\",\\"action\\":\\"active\\"},\\"kibana\\":{\\"version\\":\\"8.7.0\\",\\"space_ids\\":[\\"default\\"],\\"alert\\":{\\"instance\\":{\\"id\\":\\"*\\"},\\"uuid\\":\\"2d3e8fe5-3e8b-4361-916e-9eaab0bf2084\\",\\"status\\":\\"active\\",\\"workflow_status\\":\\"open\\",\\"reason\\":\\"system.cpu is 90% in the last 1 min for all hosts. Alert when > 50%.\\",\\"time_range\\":{\\"gte\\":\\"2022-01-01T12:00:00.000Z\\"},\\"start\\":\\"2022-12-07T15:23:13.488Z\\",\\"duration\\":{\\"us\\":100000},\\"flapping\\":false,\\"rule\\":{\\"category\\":\\"Metric threshold\\",\\"consumer\\":\\"alerts\\",\\"execution\\":{\\"uuid\\":\\"c35db7cc-5bf7-46ea-b43f-b251613a5b72\\"},\\"name\\":\\"test-rule\\",\\"producer\\":\\"infrastructure\\",\\"rule_type_id\\":\\"metrics.alert.threshold\\",\\"uuid\\":\\"0de91960-7643-11ed-b719-bb9db8582cb6\\",\\"tags\\":[]}}}}\\", \\"http://ruleurl\\" and \\"{\\"foo\\":\\"bar\\",\\"foo_bar\\":true,\\"name\\":\\"test-rule\\",\\"id\\":\\"1\\"}\\" exist", + } + `); + }); + + test('renders aliased state values', () => { + const actionParams = { + message: 'Value "{{state.signals_count}}" exists', + }; + + const result = transformSummaryActionParams({ ...params, actionParams }); + expect(result).toMatchInlineSnapshot(` + Object { + "message": "Value \\"1\\" exists", + } + `); + }); + test('renders alerts values', () => { const actionParams = { message: diff --git a/x-pack/plugins/alerting/server/task_runner/transform_action_params.ts b/x-pack/plugins/alerting/server/task_runner/transform_action_params.ts index 9f037d39b75d1..b4a3727863169 100644 --- a/x-pack/plugins/alerting/server/task_runner/transform_action_params.ts +++ b/x-pack/plugins/alerting/server/task_runner/transform_action_params.ts @@ -6,6 +6,7 @@ */ import { PluginStartContract as ActionsPluginStartContract } from '@kbn/actions-plugin/server'; +import { mapKeys, snakeCase } from 'lodash/fp'; import { RuleActionParams, AlertInstanceState, @@ -143,6 +144,19 @@ export function transformSummaryActionParams({ const variables = { kibanaBaseUrl, date: new Date().toISOString(), + // For backwards compatibility with security solutions rules + context: { + alerts: alerts.all.data ?? [], + results_link: ruleUrl, + rule: mapKeys(snakeCase, { + ...rule.params, + name: rule.name, + id: rule.id, + }), + }, + state: { + signals_count: alerts.all.count ?? 0, + }, rule: { params: rule.params, id: rule.id, diff --git a/x-pack/plugins/alerting/server/task_runner/types.ts b/x-pack/plugins/alerting/server/task_runner/types.ts index f2d04812115a8..4a1343add9249 100644 --- a/x-pack/plugins/alerting/server/task_runner/types.ts +++ b/x-pack/plugins/alerting/server/task_runner/types.ts @@ -85,6 +85,7 @@ export interface ExecutionHandlerOptions< ruleConsumer: string; executionId: string; ruleLabel: string; + previousStartedAt: Date | null; actionsClient: PublicMethodsOf; } diff --git a/x-pack/plugins/alerting/server/types.ts b/x-pack/plugins/alerting/server/types.ts index 1a23f623a54d4..c578a05c468a7 100644 --- a/x-pack/plugins/alerting/server/types.ts +++ b/x-pack/plugins/alerting/server/types.ts @@ -168,6 +168,9 @@ export interface CombinedSummarizedAlerts extends SummarizedAlerts { export type GetSummarizedAlertsFn = (opts: GetSummarizedAlertsFnOpts) => Promise; export interface GetViewInAppRelativeUrlFnOpts { rule: Omit, 'viewInAppRelativeUrl'>; + // Optional time bounds + start?: number; + end?: number; } export type GetViewInAppRelativeUrlFn = ( opts: GetViewInAppRelativeUrlFnOpts From 8d96fc82cfac0da292058ada8e067d78dee6585b Mon Sep 17 00:00:00 2001 From: Jonathan Buttner <56361221+jonathan-buttner@users.noreply.github.com> Date: Wed, 19 Apr 2023 10:10:22 -0400 Subject: [PATCH 42/78] [Cases] Delete file attachments when files do not exist (#155088) This PR modifies the bulk delete files API to support deleting the case attachments even when the file does not exist. We could run into this scenario if a user deleted the file outside of cases first and then attempts to delete the case attachment. --- package.json | 2 + .../client/attachments/bulk_delete.test.ts | 99 +++++++++++++++ .../server/client/attachments/bulk_delete.ts | 119 +++++++++++++----- .../cases/server/client/files/index.test.ts | 9 ++ .../cases/server/client/files/index.ts | 9 +- .../internal/bulk_delete_file_attachments.ts | 51 ++++++-- yarn.lock | 15 ++- 7 files changed, 264 insertions(+), 40 deletions(-) create mode 100644 x-pack/plugins/cases/server/client/attachments/bulk_delete.test.ts diff --git a/package.json b/package.json index d36fc1284122b..f898970efe8e7 100644 --- a/package.json +++ b/package.json @@ -850,6 +850,7 @@ "p-limit": "^3.0.1", "p-map": "^4.0.0", "p-retry": "^4.2.0", + "p-settle": "4.1.1", "papaparse": "^5.2.0", "pbf": "3.2.1", "pdfjs-dist": "^2.13.216", @@ -1426,6 +1427,7 @@ "nyc": "^15.1.0", "oboe": "^2.1.4", "openapi-types": "^10.0.0", + "p-reflect": "2.1.0", "pbf": "3.2.1", "peggy": "^1.2.0", "picomatch": "^2.3.1", diff --git a/x-pack/plugins/cases/server/client/attachments/bulk_delete.test.ts b/x-pack/plugins/cases/server/client/attachments/bulk_delete.test.ts new file mode 100644 index 0000000000000..49a12c904eb63 --- /dev/null +++ b/x-pack/plugins/cases/server/client/attachments/bulk_delete.test.ts @@ -0,0 +1,99 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { loggerMock } from '@kbn/logging-mocks'; +import pReflect from 'p-reflect'; +import type { File } from '@kbn/files-plugin/common'; +import { FileNotFoundError } from '@kbn/files-plugin/server/file_service/errors'; +import { retrieveFilesIgnoringNotFound } from './bulk_delete'; + +describe('bulk_delete', () => { + describe('retrieveFilesIgnoringNotFound', () => { + const mockLogger = loggerMock.create(); + + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('returns an empty array when the results is an empty array', () => { + expect(retrieveFilesIgnoringNotFound([], [], mockLogger)).toEqual([]); + }); + + it('returns a fulfilled file', async () => { + expect(retrieveFilesIgnoringNotFound([await createFakeFile()], ['abc'], mockLogger)).toEqual([ + {}, + ]); + }); + + it('logs a warning when encountering a file not found error', async () => { + const fileNotFound = await pReflect(Promise.reject(new FileNotFoundError('not found'))); + + expect(retrieveFilesIgnoringNotFound([fileNotFound], ['abc'], mockLogger)).toEqual([]); + expect(mockLogger.warn).toBeCalledTimes(1); + expect(mockLogger.warn.mock.calls[0]).toMatchInlineSnapshot(` + Array [ + "Failed to find file id: abc: Error: not found", + ] + `); + }); + + it('logs a warning without the fileId when the results length is different from the file ids', async () => { + const fileNotFound = await pReflect(Promise.reject(new FileNotFoundError('not found'))); + + expect(retrieveFilesIgnoringNotFound([fileNotFound], ['abc', '123'], mockLogger)).toEqual([]); + expect(mockLogger.warn).toBeCalledTimes(1); + expect(mockLogger.warn.mock.calls[0]).toMatchInlineSnapshot(` + Array [ + "Failed to find file: Error: not found", + ] + `); + }); + + it('throws when encountering an error that is not a file not found', async () => { + const otherError = new Error('other error'); + const otherErrorResult = await pReflect(Promise.reject(new Error('other error'))); + + expect.assertions(2); + + expect(() => + retrieveFilesIgnoringNotFound([otherErrorResult], ['abc'], mockLogger) + ).toThrowError(otherError); + expect(mockLogger.warn).not.toBeCalled(); + }); + + it('throws when encountering an error that is not a file not found after a valid file', async () => { + const otherError = new Error('other error'); + const otherErrorResult = await pReflect(Promise.reject(otherError)); + const fileResult = await createFakeFile(); + + expect.assertions(2); + + expect(() => + retrieveFilesIgnoringNotFound([fileResult, otherErrorResult], ['1', '2'], mockLogger) + ).toThrowError(otherError); + expect(mockLogger.warn).not.toBeCalled(); + }); + + it('throws a new error when encountering an error that is a string', async () => { + // this produces an error because .reject() must be passed an error but I want to test a string just in case + // eslint-disable-next-line prefer-promise-reject-errors + const otherErrorResult = await pReflect(Promise.reject('string error')); + const fileResult = await createFakeFile(); + + expect.assertions(2); + + expect(() => + retrieveFilesIgnoringNotFound([fileResult, otherErrorResult], ['1', '2'], mockLogger) + ).toThrowErrorMatchingInlineSnapshot(`"Failed to retrieve file id: 2: string error"`); + expect(mockLogger.warn).not.toBeCalled(); + }); + }); +}); + +const createFakeFile = () => { + return pReflect(Promise.resolve({} as File)); +}; diff --git a/x-pack/plugins/cases/server/client/attachments/bulk_delete.ts b/x-pack/plugins/cases/server/client/attachments/bulk_delete.ts index 75cdf6f42e05f..9f9aaaf59c736 100644 --- a/x-pack/plugins/cases/server/client/attachments/bulk_delete.ts +++ b/x-pack/plugins/cases/server/client/attachments/bulk_delete.ts @@ -10,8 +10,10 @@ import { pipe } from 'fp-ts/lib/pipeable'; import { fold } from 'fp-ts/lib/Either'; import { identity } from 'fp-ts/lib/function'; -import pMap from 'p-map'; +import type { PromiseResult, PromiseRejectedResult } from 'p-settle'; +import pSettle from 'p-settle'; import { partition } from 'lodash'; +import type { Logger } from '@kbn/core/server'; import type { File, FileJSON } from '@kbn/files-plugin/common'; import type { FileServiceStart } from '@kbn/files-plugin/server'; import { FileNotFoundError } from '@kbn/files-plugin/server/file_service/errors'; @@ -46,7 +48,12 @@ export const bulkDeleteFileAttachments = async ( await casesClient.cases.resolve({ id: caseId, includeComments: false }); - const fileEntities = await getFileEntities(caseId, request.ids, fileService); + const fileEntities = await getFileEntities({ + caseId, + fileIds: request.ids, + fileService, + logger, + }); // It's possible for this to return an empty array if there was an error creating file attachments in which case the // file would be present but the case attachment would not @@ -67,7 +74,10 @@ export const bulkDeleteFileAttachments = async ( }); await Promise.all([ - deleteFiles(request.ids, fileService), + deleteFiles( + fileEntities.map((entity) => entity.id), + fileService + ), attachmentService.bulkDelete({ attachmentIds: fileAttachments.map((so) => so.id), refresh: false, @@ -84,43 +94,53 @@ export const bulkDeleteFileAttachments = async ( user, }); } catch (error) { - let errorToTrack = error; - - // if it's an error from the file service let's put it in a boom so we don't loose the status code of a 404 - if (error instanceof FileNotFoundError) { - errorToTrack = Boom.notFound(error.message); - } - throw createCaseError({ message: `Failed to delete file attachments for case: ${caseId}: ${error}`, - error: errorToTrack, + error, logger, }); } }; -const getFileEntities = async ( - caseId: BulkDeleteFileArgs['caseId'], - fileIds: BulkDeleteFileArgs['fileIds'], - fileService: FileServiceStart -) => { - const files = await getFiles(caseId, fileIds, fileService); +const getFileEntities = async ({ + caseId, + fileIds, + fileService, + logger, +}: { + caseId: BulkDeleteFileArgs['caseId']; + fileIds: BulkDeleteFileArgs['fileIds']; + fileService: FileServiceStart; + logger: Logger; +}) => { + const files = await getFiles({ caseId, fileIds, fileService, logger }); const fileEntities = createFileEntities(files); return fileEntities; }; -const getFiles = async ( - caseId: BulkDeleteFileArgs['caseId'], - fileIds: BulkDeleteFileArgs['fileIds'], - fileService: FileServiceStart -): Promise => { +const getFiles = async ({ + caseId, + fileIds, + fileService, + logger, +}: { + caseId: BulkDeleteFileArgs['caseId']; + fileIds: BulkDeleteFileArgs['fileIds']; + fileService: FileServiceStart; + logger: Logger; +}): Promise => { // it's possible that we're trying to delete a file when an attachment wasn't created (for example if the create // attachment request failed) - const files = await pMap(fileIds, async (fileId: string) => fileService.getById({ id: fileId }), { - concurrency: MAX_CONCURRENT_SEARCHES, - }); + const fileSettleResults = await pSettle( + fileIds.map(async (fileId) => fileService.getById({ id: fileId })), + { + concurrency: MAX_CONCURRENT_SEARCHES, + } + ); + + const files = retrieveFilesIgnoringNotFound(fileSettleResults, fileIds, logger); const [validFiles, invalidFiles] = partition(files, (file) => { return ( @@ -137,9 +157,52 @@ const getFiles = async ( throw Boom.badRequest(`Failed to delete files because filed ids were invalid: ${invalidIds}`); } - if (validFiles.length <= 0) { - throw Boom.badRequest('Failed to find files to delete'); + return validFiles.map((fileInfo) => fileInfo.data); +}; + +export const retrieveFilesIgnoringNotFound = ( + results: Array>>, + fileIds: BulkDeleteFileArgs['fileIds'], + logger: Logger +) => { + const files: File[] = []; + + results.forEach((result, index) => { + if (result.isFulfilled) { + files.push(result.value); + } else if (result.reason instanceof FileNotFoundError) { + const warningMessage = getFileNotFoundErrorMessage({ + resultsLength: results.length, + fileIds, + index, + result, + }); + + logger.warn(warningMessage); + } else if (result.reason instanceof Error) { + throw result.reason; + } else { + throw new Error(`Failed to retrieve file id: ${fileIds[index]}: ${result.reason}`); + } + }); + + return files; +}; + +const getFileNotFoundErrorMessage = ({ + resultsLength, + fileIds, + index, + result, +}: { + resultsLength: number; + fileIds: BulkDeleteFileArgs['fileIds']; + index: number; + result: PromiseRejectedResult; +}) => { + if (resultsLength === fileIds.length) { + return `Failed to find file id: ${fileIds[index]}: ${result.reason}`; } - return validFiles.map((fileInfo) => fileInfo.data); + return `Failed to find file: ${result.reason}`; }; diff --git a/x-pack/plugins/cases/server/client/files/index.test.ts b/x-pack/plugins/cases/server/client/files/index.test.ts index e1727f5885d4a..853f7debabad9 100644 --- a/x-pack/plugins/cases/server/client/files/index.test.ts +++ b/x-pack/plugins/cases/server/client/files/index.test.ts @@ -51,6 +51,15 @@ describe('server files', () => { }); describe('deleteFiles', () => { + it('does not call delete when the file ids is empty', async () => { + const fileServiceMock = createFileServiceMock(); + + expect.assertions(1); + await deleteFiles([], fileServiceMock); + + expect(fileServiceMock.delete).not.toBeCalled(); + }); + it('calls delete twice with the ids passed in', async () => { const fileServiceMock = createFileServiceMock(); diff --git a/x-pack/plugins/cases/server/client/files/index.ts b/x-pack/plugins/cases/server/client/files/index.ts index 31e0bcf8fb2dd..2a400b8037d6d 100644 --- a/x-pack/plugins/cases/server/client/files/index.ts +++ b/x-pack/plugins/cases/server/client/files/index.ts @@ -33,7 +33,12 @@ export const createFileEntities = (files: FileEntityInfo[]): OwnerEntity[] => { return fileEntities; }; -export const deleteFiles = async (fileIds: string[], fileService: FileServiceStart) => - pMap(fileIds, async (fileId: string) => fileService.delete({ id: fileId }), { +export const deleteFiles = async (fileIds: string[], fileService: FileServiceStart) => { + if (fileIds.length <= 0) { + return; + } + + return pMap(fileIds, async (fileId: string) => fileService.delete({ id: fileId }), { concurrency: MAX_CONCURRENT_SEARCHES, }); +}; diff --git a/x-pack/test/cases_api_integration/security_and_spaces/tests/common/internal/bulk_delete_file_attachments.ts b/x-pack/test/cases_api_integration/security_and_spaces/tests/common/internal/bulk_delete_file_attachments.ts index 2a65a0a2fbdc3..bd2a4b8f85456 100644 --- a/x-pack/test/cases_api_integration/security_and_spaces/tests/common/internal/bulk_delete_file_attachments.ts +++ b/x-pack/test/cases_api_integration/security_and_spaces/tests/common/internal/bulk_delete_file_attachments.ts @@ -121,15 +121,6 @@ export default ({ getService }: FtrProviderContext): void => { }); }); - it('fails to delete a file when the file does not exist', async () => { - await bulkDeleteFileAttachments({ - supertest, - caseId: postedCase.id, - fileIds: ['abc'], - expectedHttpCode: 404, - }); - }); - it('returns a 400 when the fileIds is an empty array', async () => { await bulkDeleteFileAttachments({ supertest, @@ -254,6 +245,17 @@ export default ({ getService }: FtrProviderContext): void => { await deleteAllCaseItems(es); }); + it('returns a 204 when the file does not exist', async () => { + const postedCase = await createCase(supertest, getPostCaseRequest()); + + await bulkDeleteFileAttachments({ + supertest, + caseId: postedCase.id, + fileIds: ['abc'], + expectedHttpCode: 204, + }); + }); + it('deletes a file when the owner is not formatted as an array of strings', async () => { const postedCase = await createCase(supertest, getPostCaseRequest()); @@ -412,6 +414,37 @@ export default ({ getService }: FtrProviderContext): void => { await deleteAllCaseItems(es); }); + it('deletes the attachment even when the file does not exist', async () => { + const postedCase = await createCase( + supertest, + getPostCaseRequest({ owner: 'securitySolution' }) + ); + + const caseWithAttachments = await bulkCreateAttachments({ + supertest, + caseId: postedCase.id, + params: [ + getFilesAttachmentReq({ + externalReferenceId: 'abc', + owner: 'securitySolution', + }), + ], + }); + + await bulkDeleteFileAttachments({ + supertest, + caseId: postedCase.id, + fileIds: ['abc'], + }); + + await getComment({ + supertest, + caseId: postedCase.id, + commentId: caseWithAttachments.comments![0].id, + expectedHttpCode: 404, + }); + }); + it('deletes a single file', async () => { const postedCase = await createCase( supertest, diff --git a/yarn.lock b/yarn.lock index bd8668fa91fc1..772067b69cdca 100644 --- a/yarn.lock +++ b/yarn.lock @@ -22312,7 +22312,7 @@ p-limit@^1.1.0: dependencies: p-try "^1.0.0" -p-limit@^2.0.0, p-limit@^2.2.0, p-limit@^2.3.0: +p-limit@^2.0.0, p-limit@^2.2.0, p-limit@^2.2.2, p-limit@^2.3.0: version "2.3.0" resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.3.0.tgz#3dd33c647a214fdfffd835933eb086da0dc21db1" integrity sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w== @@ -22373,6 +22373,11 @@ p-map@^4.0.0: dependencies: aggregate-error "^3.0.0" +p-reflect@2.1.0, p-reflect@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/p-reflect/-/p-reflect-2.1.0.tgz#5d67c7b3c577c4e780b9451fc9129675bd99fe67" + integrity sha512-paHV8NUz8zDHu5lhr/ngGWQiW067DK/+IbJ+RfZ4k+s8y4EKyYCz8pGYWjxCg35eHztpJAt+NUgvN4L+GCbPlg== + p-retry@^4.2.0, p-retry@^4.5.0: version "4.5.0" resolved "https://registry.yarnpkg.com/p-retry/-/p-retry-4.5.0.tgz#6685336b3672f9ee8174d3769a660cb5e488521d" @@ -22381,6 +22386,14 @@ p-retry@^4.2.0, p-retry@^4.5.0: "@types/retry" "^0.12.0" retry "^0.12.0" +p-settle@4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/p-settle/-/p-settle-4.1.1.tgz#37fbceb2b02c9efc28658fc8d36949922266035f" + integrity sha512-6THGh13mt3gypcNMm0ADqVNCcYa3BK6DWsuJWFCuEKP1rpY+OKGp7gaZwVmLspmic01+fsg/fN57MfvDzZ/PuQ== + dependencies: + p-limit "^2.2.2" + p-reflect "^2.1.0" + p-timeout@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/p-timeout/-/p-timeout-2.0.1.tgz#d8dd1979595d2dc0139e1fe46b8b646cb3cdf038" From f2d880cba240104d8049bdb54e8f2ad1d7c9bbf6 Mon Sep 17 00:00:00 2001 From: Jonathan Buttner <56361221+jonathan-buttner@users.noreply.github.com> Date: Wed, 19 Apr 2023 10:10:37 -0400 Subject: [PATCH 43/78] [Cases] Adding new config options to docker script (#155194) This PR adds the follow cases config options from this PR to the docker script. https://github.com/elastic/kibana/pull/154013 ``` xpack.cases.files.allowedMimeTypes xpack.cases.files.maxSize ``` --- .../docker_generator/resources/base/bin/kibana-docker | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker b/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker index 6801686f90f57..b08d26b8c657d 100755 --- a/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker +++ b/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker @@ -253,6 +253,8 @@ kibana_vars=( xpack.banners.placement xpack.banners.textColor xpack.banners.textContent + xpack.cases.files.allowedMimeTypes + xpack.cases.files.maxSize xpack.code.disk.thresholdEnabled xpack.code.disk.watermarkLow xpack.code.indexRepoFrequencyMs From bc10169e16541887a5d1e70c2b1812b08bf2ba44 Mon Sep 17 00:00:00 2001 From: Rickyanto Ang Date: Wed, 19 Apr 2023 07:25:35 -0700 Subject: [PATCH 44/78] [Cloud Security] Message update for index-timeout status (#155223) ## Summary This PR is for rephrasing the message the user see when status = index-timeout ![waiting](https://user-images.githubusercontent.com/8703149/232987730-ddf3c3c1-9611-4cbf-8bef-b912d519ee5e.png) --- .../public/components/no_findings_states.tsx | 6 +++--- .../public/components/no_vulnerabilities_states.tsx | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/x-pack/plugins/cloud_security_posture/public/components/no_findings_states.tsx b/x-pack/plugins/cloud_security_posture/public/components/no_findings_states.tsx index 3c6a286ba0e33..897de7df552b9 100644 --- a/x-pack/plugins/cloud_security_posture/public/components/no_findings_states.tsx +++ b/x-pack/plugins/cloud_security_posture/public/components/no_findings_states.tsx @@ -117,7 +117,7 @@ const IndexTimeout = () => (

} @@ -125,13 +125,13 @@ const IndexTimeout = () => (

), diff --git a/x-pack/plugins/cloud_security_posture/public/components/no_vulnerabilities_states.tsx b/x-pack/plugins/cloud_security_posture/public/components/no_vulnerabilities_states.tsx index 61c9fc8c407dd..48f790bf4c332 100644 --- a/x-pack/plugins/cloud_security_posture/public/components/no_vulnerabilities_states.tsx +++ b/x-pack/plugins/cloud_security_posture/public/components/no_vulnerabilities_states.tsx @@ -129,13 +129,13 @@ const IndexTimeout = () => (

), From fa7bde1296f8dd7bf48bfcbc476d74017ba71d61 Mon Sep 17 00:00:00 2001 From: Michael Olorunnisola Date: Wed, 19 Apr 2023 10:30:25 -0400 Subject: [PATCH 45/78] [Security Solution][Investigations] - add highlighted fields (#155247) ## Summary Adding highlighted fields as requested here: https://github.com/elastic/security-team/issues/6348 Decision was made to have the fields be global highlighted fields. --- .../event_details/get_alert_summary_rows.tsx | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/get_alert_summary_rows.tsx b/x-pack/plugins/security_solution/public/common/components/event_details/get_alert_summary_rows.tsx index 793cba6810645..f591fad6a6629 100644 --- a/x-pack/plugins/security_solution/public/common/components/event_details/get_alert_summary_rows.tsx +++ b/x-pack/plugins/security_solution/public/common/components/event_details/get_alert_summary_rows.tsx @@ -47,6 +47,19 @@ const alwaysDisplayedFields: EventSummaryField[] = [ { id: 'agent.id', overrideField: AGENT_STATUS_FIELD_NAME, label: i18n.AGENT_STATUS }, { id: 'user.name' }, { id: 'rule.name' }, + { id: 'cloud.provider' }, + { id: 'cloud.region' }, + { id: 'cloud.provider' }, + { id: 'cloud.region' }, + { id: 'orchestrator.cluster.id' }, + { id: 'orchestrator.cluster.name' }, + { id: 'container.image.name' }, + { id: 'container.image.tag' }, + { id: 'orchestrator.namespace' }, + { id: 'orchestrator.resource.parent.type' }, + { id: 'orchestrator.resource.type' }, + { id: 'process.executable' }, + { id: 'file.path' }, { id: ALERT_RULE_TYPE, label: i18n.RULE_TYPE }, ]; From ddf9ab6c13815bff3464fefeab8244f0064b2498 Mon Sep 17 00:00:00 2001 From: "Joey F. Poon" Date: Wed, 19 Apr 2023 07:34:21 -0700 Subject: [PATCH 46/78] [Fleet] fix message signing service encryption bug (#154934) ## Summary fixes: adding an encryption key after generating a key pair would cause the private key to become unable to decrypt. ### 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 ### For maintainers - [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) --- .../security/message_signing_service.test.ts | 3 +- .../security/message_signing_service.ts | 115 +++++++++--------- 2 files changed, 62 insertions(+), 56 deletions(-) diff --git a/x-pack/plugins/fleet/server/services/security/message_signing_service.test.ts b/x-pack/plugins/fleet/server/services/security/message_signing_service.test.ts index e3ad1d82bdee7..fd0b3c8f2ca2b 100644 --- a/x-pack/plugins/fleet/server/services/security/message_signing_service.test.ts +++ b/x-pack/plugins/fleet/server/services/security/message_signing_service.test.ts @@ -153,7 +153,8 @@ describe('MessageSigningService', () => { MESSAGE_SIGNING_KEYS_SAVED_OBJECT_TYPE, keyPairObj.id, { - passphrase: expect.any(String), + ...keyPairObj.attributes, + passphrase: keyPairObj.attributes.passphrase_plain, passphrase_plain: '', } ); diff --git a/x-pack/plugins/fleet/server/services/security/message_signing_service.ts b/x-pack/plugins/fleet/server/services/security/message_signing_service.ts index 443f35ec06ed2..ab63b746286e3 100644 --- a/x-pack/plugins/fleet/server/services/security/message_signing_service.ts +++ b/x-pack/plugins/fleet/server/services/security/message_signing_service.ts @@ -8,7 +8,10 @@ import { generateKeyPairSync, createSign, randomBytes } from 'crypto'; import type { KibanaRequest } from '@kbn/core-http-server'; -import type { SavedObjectsClientContract } from '@kbn/core-saved-objects-api-server'; +import type { + SavedObjectsClientContract, + SavedObjectsFindResult, +} from '@kbn/core-saved-objects-api-server'; import type { EncryptedSavedObjectsClient } from '@kbn/encrypted-saved-objects-plugin/server'; import { SECURITY_EXTENSION_ID } from '@kbn/core-saved-objects-server'; @@ -45,35 +48,12 @@ export class MessageSigningService implements MessageSigningServiceInterface { publicKey: string; passphrase: string; }> { - let passphrase = providedPassphrase || this.generatePassphrase(); - - const currentKeyPair = await this.getCurrentKeyPair(); - if ( - currentKeyPair.privateKey && - currentKeyPair.publicKey && - (currentKeyPair.passphrase || currentKeyPair.passphrasePlain) - ) { - passphrase = currentKeyPair.passphrase || currentKeyPair.passphrasePlain; - - // newly configured encryption key, encrypt the passphrase - if (currentKeyPair.passphrasePlain && this.isEncryptionAvailable) { - await this.soClient.update( - MESSAGE_SIGNING_KEYS_SAVED_OBJECT_TYPE, - currentKeyPair.id, - { - passphrase, - passphrase_plain: '', - } - ); - } - - return { - privateKey: currentKeyPair.privateKey, - publicKey: currentKeyPair.publicKey, - passphrase, - }; + const existingKeyPair = await this.checkForExistingKeyPair(); + if (existingKeyPair) { + return existingKeyPair; } + const passphrase = providedPassphrase || this.generatePassphrase(); const keyPair = generateKeyPairSync('ec', { namedCurve: 'prime256v1', privateKeyEncoding: { @@ -176,13 +156,9 @@ export class MessageSigningService implements MessageSigningServiceInterface { return this._soClient; } - private async getCurrentKeyPair(): Promise<{ - id: string; - privateKey: string; - publicKey: string; - passphrase: string; - passphrasePlain: string; - }> { + private async getCurrentKeyPairObj(): Promise< + SavedObjectsFindResult | undefined + > { const finder = await this.esoClient.createPointInTimeFinderDecryptedAsInternalUser({ type: MESSAGE_SIGNING_KEYS_SAVED_OBJECT_TYPE, @@ -190,30 +166,59 @@ export class MessageSigningService implements MessageSigningServiceInterface { sortField: 'created_at', sortOrder: 'desc', }); - let keyPair = { - id: '', - privateKey: '', - publicKey: '', - passphrase: '', - passphrasePlain: '', - }; + let soDoc: SavedObjectsFindResult | undefined; for await (const result of finder.find()) { - const savedObject = result.saved_objects[0]; - const attributes = savedObject?.attributes; - if (!attributes?.private_key) { - break; - } - keyPair = { - id: savedObject.id, - privateKey: attributes.private_key, - publicKey: attributes.public_key, - passphrase: attributes.passphrase, - passphrasePlain: attributes.passphrase_plain, - }; + soDoc = result.saved_objects[0]; break; } + finder.close(); + + return soDoc; + } + + private async checkForExistingKeyPair(): Promise< + | { + privateKey: string; + publicKey: string; + passphrase: string; + } + | undefined + > { + const currentKeyPair = await this.getCurrentKeyPairObj(); + if (!currentKeyPair) { + return; + } - return keyPair; + const { attributes } = currentKeyPair; + if (!attributes) { + return; + } + + const { + private_key: privateKey, + public_key: publicKey, + passphrase: passphraseEncrypted, + passphrase_plain: passphrasePlain, + } = attributes; + const passphrase = passphraseEncrypted || passphrasePlain; + if (!privateKey || !publicKey || !passphrase) { + return; + } + + // newly configured encryption key, encrypt the passphrase + if (passphrasePlain && this.isEncryptionAvailable) { + await this.soClient.update( + MESSAGE_SIGNING_KEYS_SAVED_OBJECT_TYPE, + currentKeyPair?.id, + { ...attributes, passphrase, passphrase_plain: '' } + ); + } + + return { + privateKey, + publicKey, + passphrase, + }; } private generatePassphrase(): string { From 271d9aa68cf93ae9a3d398e909a0c4fb06a409b5 Mon Sep 17 00:00:00 2001 From: Dzmitry Lemechko Date: Wed, 19 Apr 2023 16:39:35 +0200 Subject: [PATCH 47/78] [kbn-failed-test-reporter-cli] truncate report message to fix github api call failure (#155141) ## Summary We recently had a [failure](https://buildkite.com/elastic/kibana-on-merge/builds/29020#018792bc-498a-46d1-9343-fb791823d92e) while calling Github API to open new issue: ``` 2023-04-18 07:56:26 CEST ERROR Error: [post https://api.github.com/repos/elastic/kibana/issues] 422 Unprocessable Entity Error: {"message":"Validation Failed","errors":[{"resource":"Issue","code":"custom","field":"body","message":"body is too long"},{"resource":"Issue","code":"custom","field":"body","message":"body is too long (maximum is 65536 characters)"}],"documentation_url":"https://docs.github.com/rest/reference/issues#create-an-issue"} 2023-04-18 07:56:26 CEST at GithubApi.request (github_api.ts:177:17) 2023-04-18 07:56:26 CEST at processTicksAndRejections (node:internal/process/task_queues:96:5) 2023-04-18 07:56:26 CEST at GithubApi.createIssue (github_api.ts:106:18) 2023-04-18 07:56:26 CEST at createFailureIssue (report_failure.ts:39:10) 2023-04-18 07:56:26 CEST at description (failed_tests_reporter_cli.ts:153:28) 2023-04-18 07:56:26 CEST at run.ts:70:7 2023-04-18 07:56:26 CEST at withProcRunner (with_proc_runner.ts:29:5) 2023-04-18 07:56:26 CEST at run (run.ts:69:5) ``` It seems like some test might have a very long failure message that reaches Github body length limit and causes a failure. Since mocha truncates failure message to 8192 chars, I thought it might be a good option for Github issues as well (Having too long message is not very useful anyway) --- .../failed_tests_reporter/report_failure.ts | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/packages/kbn-failed-test-reporter-cli/failed_tests_reporter/report_failure.ts b/packages/kbn-failed-test-reporter-cli/failed_tests_reporter/report_failure.ts index e2750f15b8720..ed282b44325e3 100644 --- a/packages/kbn-failed-test-reporter-cli/failed_tests_reporter/report_failure.ts +++ b/packages/kbn-failed-test-reporter-cli/failed_tests_reporter/report_failure.ts @@ -19,12 +19,24 @@ export async function createFailureIssue( ) { const title = `Failing test: ${failure.classname} - ${failure.name}`; + // Github API body length maximum is 65536 characters + // Let's keep consistency with Mocha output that is truncated to 8192 characters + const failureMaxCharacters = 8192; + + const failureBody = + failure.failure.length <= failureMaxCharacters + ? failure.failure + : [ + failure.failure.substring(0, failureMaxCharacters), + `[report_failure] output truncated to ${failureMaxCharacters} characters`, + ].join('\n'); + const body = updateIssueMetadata( [ 'A test failed on a tracked branch', '', '```', - failure.failure, + failureBody, '```', '', `First failure: [CI Build - ${branch}](${buildUrl})`, From 6d8d1708fef7751f5cd7be6ff2da7cf69cb4b742 Mon Sep 17 00:00:00 2001 From: Jatin Kathuria Date: Wed, 19 Apr 2023 16:58:48 +0200 Subject: [PATCH 48/78] [Control Group] Add override options of openAddDataControlFlyout (#153007) ## Summary This PR adds the option to provide, custom props when calling `openAddDataControlFlyout`. For example, users of the API may want different placeholder than default for any new options list that is added. Currently, in security solution, we need a custom placeholder for newly added controls. Hence, this PR. Please let me know if this is not the best way to do this. https://user-images.githubusercontent.com/7485038/232775198-a111d711-ffba-4d26-8c70-c2af08008e05.mov --- .../controls_example/public/edit_example.tsx | 33 +++++++++++++++++-- src/plugins/controls/common/index.ts | 2 +- src/plugins/controls/common/types.ts | 5 +++ .../control_group/editor/control_editor.tsx | 6 +++- .../editor/open_add_data_control_flyout.tsx | 10 +++++- .../controls/public/control_group/index.ts | 1 + .../controls/public/control_group/types.ts | 2 ++ src/plugins/controls/public/index.ts | 1 + 8 files changed, 55 insertions(+), 5 deletions(-) diff --git a/examples/controls_example/public/edit_example.tsx b/examples/controls_example/public/edit_example.tsx index cf5430cad48f1..21689eb13ab09 100644 --- a/examples/controls_example/public/edit_example.tsx +++ b/examples/controls_example/public/edit_example.tsx @@ -21,18 +21,23 @@ import { EuiTitle, } from '@elastic/eui'; import { ViewMode } from '@kbn/embeddable-plugin/public'; +import { OPTIONS_LIST_CONTROL, RANGE_SLIDER_CONTROL } from '@kbn/controls-plugin/common'; import { LazyControlGroupRenderer, ControlGroupContainer, ControlGroupInput, + ACTION_EDIT_CONTROL, + ACTION_DELETE_CONTROL, } from '@kbn/controls-plugin/public'; import { withSuspense } from '@kbn/presentation-util-plugin/public'; -import { ACTION_EDIT_CONTROL, ACTION_DELETE_CONTROL } from '@kbn/controls-plugin/public'; +import { ControlInputTransform } from '@kbn/controls-plugin/common/types'; const ControlGroupRenderer = withSuspense(LazyControlGroupRenderer); const INPUT_KEY = 'kbnControls:saveExample:input'; +const WITH_CUSTOM_PLACEHOLDER = 'Custom Placeholder'; + export const EditExample = () => { const [isSaving, setIsSaving] = useState(false); const [isLoading, setIsLoading] = useState(false); @@ -85,6 +90,7 @@ export const EditExample = () => { setToggleIconIdToSelectedMapIcon({ [ACTION_EDIT_CONTROL]: disabledActions.includes(ACTION_EDIT_CONTROL), [ACTION_DELETE_CONTROL]: disabledActions.includes(ACTION_DELETE_CONTROL), + [WITH_CUSTOM_PLACEHOLDER]: false, }); } catch (e) { // ignore parse errors @@ -94,6 +100,24 @@ export const EditExample = () => { return input; } + const controlInputTransform: ControlInputTransform = (newState, type) => { + if (type === OPTIONS_LIST_CONTROL && toggleIconIdToSelectedMapIcon[WITH_CUSTOM_PLACEHOLDER]) { + return { + ...newState, + placeholder: 'Custom Placeholder', + }; + } + + if (type === RANGE_SLIDER_CONTROL) { + return { + ...newState, + value: ['0', '4'], + }; + } + + return newState; + }; + return ( <> @@ -111,7 +135,7 @@ export const EditExample = () => { iconType="plusInCircle" isDisabled={controlGroup === undefined} onClick={() => { - controlGroup!.openAddDataControlFlyout(); + controlGroup!.openAddDataControlFlyout(controlInputTransform); }} > Add control @@ -132,6 +156,11 @@ export const EditExample = () => { label: 'Disable delete action', value: ACTION_DELETE_CONTROL, }, + { + id: WITH_CUSTOM_PLACEHOLDER, + label: WITH_CUSTOM_PLACEHOLDER, + value: WITH_CUSTOM_PLACEHOLDER, + }, ]} idToSelectedMap={toggleIconIdToSelectedMapIcon} type="multi" diff --git a/src/plugins/controls/common/index.ts b/src/plugins/controls/common/index.ts index 346cb53ce4244..de492adb399f3 100644 --- a/src/plugins/controls/common/index.ts +++ b/src/plugins/controls/common/index.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -export type { ControlWidth } from './types'; +export type { ControlWidth, ControlInputTransform } from './types'; // Control Group exports export { diff --git a/src/plugins/controls/common/types.ts b/src/plugins/controls/common/types.ts index 5f37ef2c72871..7eb0d69f98a4f 100644 --- a/src/plugins/controls/common/types.ts +++ b/src/plugins/controls/common/types.ts @@ -32,3 +32,8 @@ export type DataControlInput = ControlInput & { fieldName: string; dataViewId: string; }; + +export type ControlInputTransform = ( + newState: Partial, + controlType: string +) => Partial; diff --git a/src/plugins/controls/public/control_group/editor/control_editor.tsx b/src/plugins/controls/public/control_group/editor/control_editor.tsx index 5cc94acaebcf1..90285c7cca005 100644 --- a/src/plugins/controls/public/control_group/editor/control_editor.tsx +++ b/src/plugins/controls/public/control_group/editor/control_editor.tsx @@ -97,6 +97,7 @@ export const ControlEditor = ({ const { useEmbeddableSelector: select } = useControlGroupContainerContext(); const editorConfig = select((state) => state.componentState.editorConfig); + const customFilterPredicate = select((state) => state.componentState.fieldFilterPredicate); const [defaultTitle, setDefaultTitle] = useState(); const [currentTitle, setCurrentTitle] = useState(title ?? ''); @@ -195,7 +196,10 @@ export const ControlEditor = ({ )} Boolean(fieldRegistry?.[field.name])} + filterPredicate={(field: DataViewField) => { + const customPredicate = customFilterPredicate ? customFilterPredicate(field) : true; + return Boolean(fieldRegistry?.[field.name]) && customPredicate; + }} selectedFieldName={selectedField} dataView={selectedDataView} onSelectField={(field) => { diff --git a/src/plugins/controls/public/control_group/editor/open_add_data_control_flyout.tsx b/src/plugins/controls/public/control_group/editor/open_add_data_control_flyout.tsx index 0b44c09fbaabf..e9e89d3828729 100644 --- a/src/plugins/controls/public/control_group/editor/open_add_data_control_flyout.tsx +++ b/src/plugins/controls/public/control_group/editor/open_add_data_control_flyout.tsx @@ -8,6 +8,7 @@ import React from 'react'; import { toMountPoint } from '@kbn/kibana-react-plugin/public'; +import { ControlInputTransform } from '../../../common/types'; import type { AddDataControlProps, AddOptionsListControlProps, @@ -24,7 +25,10 @@ import { DEFAULT_CONTROL_WIDTH, } from '../../../common/control_group/control_group_constants'; -export function openAddDataControlFlyout(this: ControlGroupContainer) { +export function openAddDataControlFlyout( + this: ControlGroupContainer, + controlInputTransform?: ControlInputTransform +) { const { overlays: { openFlyout, openConfirm }, controls: { getControlFactory }, @@ -76,6 +80,10 @@ export function openAddDataControlFlyout(this: ControlGroupContainer) { controlInput = factory.presaveTransformFunction(controlInput); } + if (controlInputTransform) { + controlInput = controlInputTransform({ ...controlInput }, type); + } + if (type === OPTIONS_LIST_CONTROL) { this.addOptionsListControl(controlInput as AddOptionsListControlProps); return; diff --git a/src/plugins/controls/public/control_group/index.ts b/src/plugins/controls/public/control_group/index.ts index 745e41ec474b1..d56fc619885c2 100644 --- a/src/plugins/controls/public/control_group/index.ts +++ b/src/plugins/controls/public/control_group/index.ts @@ -19,6 +19,7 @@ export { ACTION_EDIT_CONTROL, ACTION_DELETE_CONTROL } from './actions'; export { type AddDataControlProps, type AddOptionsListControlProps, + type AddRangeSliderControlProps, controlGroupInputBuilder, } from './control_group_input_builder'; diff --git a/src/plugins/controls/public/control_group/types.ts b/src/plugins/controls/public/control_group/types.ts index 98455b5aab527..5fea9dcfb9628 100644 --- a/src/plugins/controls/public/control_group/types.ts +++ b/src/plugins/controls/public/control_group/types.ts @@ -6,6 +6,7 @@ * Side Public License, v 1. */ +import { DataViewField } from '@kbn/data-views-plugin/common'; import { ContainerOutput } from '@kbn/embeddable-plugin/public'; import { ReduxEmbeddableState } from '@kbn/presentation-util-plugin/public'; import { ControlGroupInput } from '../../common/control_group/types'; @@ -34,6 +35,7 @@ export interface ControlGroupSettings { hideWidthSettings?: boolean; hideAdditionalSettings?: boolean; }; + fieldFilterPredicate?: (field: DataViewField) => boolean; } export { diff --git a/src/plugins/controls/public/index.ts b/src/plugins/controls/public/index.ts index 1e9df544bd1c9..62378f5abbf26 100644 --- a/src/plugins/controls/public/index.ts +++ b/src/plugins/controls/public/index.ts @@ -35,6 +35,7 @@ export { export { type AddDataControlProps, type AddOptionsListControlProps, + type AddRangeSliderControlProps, type ControlGroupContainer, ControlGroupContainerFactory, type ControlGroupInput, From b40b89e711c5397237e44e456e0365e9d55b8f8b Mon Sep 17 00:00:00 2001 From: Marco Antonio Ghiani Date: Wed, 19 Apr 2023 17:30:45 +0200 Subject: [PATCH 49/78] [Logs UI] Redirect Logs UI to Discover when in serverless mode (#154145) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 📓 Summary Closes #153890 The implementation creates a new LogsApp service where we should keep any logic concerned with what `target_app` parameter is configured and the actions related to a specific configuration. I thought it could be a good approach to avoid drilling down the global config till we need it and keep it cleaner by injecting only the service with predefined actions. In this first case, we create a redirect to discover using its locator, and the exposed method can be used anywhere across the app for triggering the redirect. ## 🧪 Testing ### Normal behaviour When Kibana is used as always, we want to keep the current behaviour and the user will stay on the Logs UI pages. - Launch the Kibana dev environment with `yarn start` - Navigate to Logs UI - Verify the navigation works normally and that no redirect to Discover occurs ### Serverless behaviour When Kibana is used in serverless mode, we want to redirect any user landing to Logs UI to the Discover page, configuring the same data view or creating an ad-hoc one starting from the index pattern - Launch the Kibana dev environment with `yarn serverless-oblt` - Navigate to Logs UI - Verify to be redirected to Discover and a temporary data view is created from the current index pattern --------- Co-authored-by: Marco Antonio Ghiani Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> --- config/serverless.oblt.yml | 1 + config/serverless.yml | 2 +- .../test_suites/core_plugins/rendering.ts | 1 + x-pack/plugins/infra/common/constants.ts | 1 + .../common/log_views/resolved_log_view.ts | 1 + .../infra/common/plugin_config_types.ts | 4 + x-pack/plugins/infra/kibana.jsonc | 1 + .../infra/public/apps/discover_app.tsx | 105 ++++++++++++++++ x-pack/plugins/infra/public/plugin.ts | 112 +++++++++++------- x-pack/plugins/infra/public/types.ts | 2 + ...nventory_metric_threshold_executor.test.ts | 3 + .../metric_threshold_executor.test.ts | 6 +- .../infra/server/lib/sources/sources.test.ts | 7 +- x-pack/plugins/infra/server/plugin.ts | 5 + x-pack/plugins/infra/tsconfig.json | 1 + 15 files changed, 202 insertions(+), 50 deletions(-) create mode 100644 x-pack/plugins/infra/public/apps/discover_app.tsx diff --git a/config/serverless.oblt.yml b/config/serverless.oblt.yml index e69de29bb2d1d..ba76648238348 100644 --- a/config/serverless.oblt.yml +++ b/config/serverless.oblt.yml @@ -0,0 +1 @@ +xpack.infra.logs.app_target: discover diff --git a/config/serverless.yml b/config/serverless.yml index 80e4ab584f9f6..d38863072d8a0 100644 --- a/config/serverless.yml +++ b/config/serverless.yml @@ -1,2 +1,2 @@ xpack.fleet.enableExperimental: ['fleetServerStandalone'] -xpack.fleet.internal.ILMPoliciesDisabled: true +xpack.fleet.internal.disableILMPolicies: true diff --git a/test/plugin_functional/test_suites/core_plugins/rendering.ts b/test/plugin_functional/test_suites/core_plugins/rendering.ts index 6106f430dbafd..ad66d01098665 100644 --- a/test/plugin_functional/test_suites/core_plugins/rendering.ts +++ b/test/plugin_functional/test_suites/core_plugins/rendering.ts @@ -201,6 +201,7 @@ export default function ({ getService }: PluginFunctionalProviderContext) { 'xpack.graph.savePolicy (alternatives)', 'xpack.ilm.ui.enabled (boolean)', 'xpack.index_management.ui.enabled (boolean)', + 'xpack.infra.logs.app_target (alternatives)', 'xpack.infra.sources.default.fields.message (array)', 'xpack.license_management.ui.enabled (boolean)', 'xpack.maps.preserveDrawingBuffer (boolean)', diff --git a/x-pack/plugins/infra/common/constants.ts b/x-pack/plugins/infra/common/constants.ts index 9fcb488e02fda..d428ebd3e4909 100644 --- a/x-pack/plugins/infra/common/constants.ts +++ b/x-pack/plugins/infra/common/constants.ts @@ -16,6 +16,7 @@ export const LOGS_FEATURE_ID = 'logs'; export type InfraFeatureId = typeof METRICS_FEATURE_ID | typeof LOGS_FEATURE_ID; export const TIMESTAMP_FIELD = '@timestamp'; +export const MESSAGE_FIELD = 'message'; export const TIEBREAKER_FIELD = '_doc'; export const HOST_FIELD = 'host.name'; export const CONTAINER_FIELD = 'container.id'; diff --git a/x-pack/plugins/infra/common/log_views/resolved_log_view.ts b/x-pack/plugins/infra/common/log_views/resolved_log_view.ts index 2fc2fd7aa2374..391c6be18fb9d 100644 --- a/x-pack/plugins/infra/common/log_views/resolved_log_view.ts +++ b/x-pack/plugins/infra/common/log_views/resolved_log_view.ts @@ -55,6 +55,7 @@ const resolveLegacyReference = async ( .create( { id: `log-view-${logViewId}`, + name: logViewAttributes.name, title: indices, timeFieldName: TIMESTAMP_FIELD, allowNoIndex: true, diff --git a/x-pack/plugins/infra/common/plugin_config_types.ts b/x-pack/plugins/infra/common/plugin_config_types.ts index 59ed36c9b3279..2c3a3bd2efeb7 100644 --- a/x-pack/plugins/infra/common/plugin_config_types.ts +++ b/x-pack/plugins/infra/common/plugin_config_types.ts @@ -17,6 +17,9 @@ export interface InfraConfig { inventory: { compositeSize: number; }; + logs: { + app_target: 'logs-ui' | 'discover'; + }; sources?: { default?: { fields?: { @@ -28,6 +31,7 @@ export interface InfraConfig { export const publicConfigKeys = { sources: true, + logs: true, } as const; export type InfraPublicConfigKey = keyof { diff --git a/x-pack/plugins/infra/kibana.jsonc b/x-pack/plugins/infra/kibana.jsonc index 1243275a4a416..abf1d2f8766f2 100644 --- a/x-pack/plugins/infra/kibana.jsonc +++ b/x-pack/plugins/infra/kibana.jsonc @@ -14,6 +14,7 @@ "charts", "data", "dataViews", + "discover", "embeddable", "features", "lens", diff --git a/x-pack/plugins/infra/public/apps/discover_app.tsx b/x-pack/plugins/infra/public/apps/discover_app.tsx new file mode 100644 index 0000000000000..807b64845cdc7 --- /dev/null +++ b/x-pack/plugins/infra/public/apps/discover_app.tsx @@ -0,0 +1,105 @@ +/* + * Copyright 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 { interpret } from 'xstate'; +import type { DiscoverStart } from '@kbn/discover-plugin/public'; +import { createKbnUrlStateStorage } from '@kbn/kibana-utils-plugin/public'; +import { AppMountParameters, CoreStart } from '@kbn/core/public'; +import type { InfraClientStartDeps, InfraClientStartExports } from '../types'; +import type { LogViewColumnConfiguration, ResolvedLogView } from '../../common/log_views'; +import { + createLogViewStateMachine, + DEFAULT_LOG_VIEW, + initializeFromUrl, +} from '../observability_logs/log_view_state'; +import { MESSAGE_FIELD, TIMESTAMP_FIELD } from '../../common/constants'; + +export const renderApp = ( + core: CoreStart, + plugins: InfraClientStartDeps, + pluginStart: InfraClientStartExports, + params: AppMountParameters +) => { + const { discover } = plugins; + const { logViews } = pluginStart; + + const machine = createLogViewStateMachine({ + initialContext: { logViewReference: DEFAULT_LOG_VIEW }, + logViews: logViews.client, + initializeFromUrl: createInitializeFromUrl(core, params), + }); + + const service = interpret(machine) + .onTransition((state) => { + if ( + state.matches('checkingStatus') || + state.matches('resolvedPersistedLogView') || + state.matches('resolvedInlineLogView') + ) { + return redirectToDiscover(discover, state.context.resolvedLogView); + } else if ( + state.matches('loadingFailed') || + state.matches('resolutionFailed') || + state.matches('checkingStatusFailed') + ) { + return redirectToDiscover(discover); + } + }) + .start(); + + return () => { + // Stop machine interpreter after navigation + service.stop(); + }; +}; + +const redirectToDiscover = (discover: DiscoverStart, resolvedLogView?: ResolvedLogView) => { + const navigationOptions = { replace: true }; + + if (!resolvedLogView) { + return discover.locator?.navigate({}, navigationOptions); + } + + const columns = parseColumns(resolvedLogView.columns); + const dataViewSpec = resolvedLogView.dataViewReference.toSpec(); + + return discover.locator?.navigate( + { + columns, + dataViewId: dataViewSpec.id, + dataViewSpec, + }, + navigationOptions + ); +}; + +/** + * Helpers + */ + +const parseColumns = (columns: ResolvedLogView['columns']) => { + return columns.map(getColumnValue).filter(Boolean) as string[]; +}; + +const getColumnValue = (column: LogViewColumnConfiguration) => { + if ('messageColumn' in column) return MESSAGE_FIELD; + if ('timestampColumn' in column) return TIMESTAMP_FIELD; + if ('fieldColumn' in column) return column.fieldColumn.field; + + return null; +}; + +const createInitializeFromUrl = (core: CoreStart, params: AppMountParameters) => { + const toastsService = core.notifications.toasts; + + const urlStateStorage = createKbnUrlStateStorage({ + history: params.history, + useHash: false, + useHashQuery: false, + }); + + return initializeFromUrl({ toastsService, urlStateStorage }); +}; diff --git a/x-pack/plugins/infra/public/plugin.ts b/x-pack/plugins/infra/public/plugin.ts index cae0cbd62bae4..8931a6fd32cb7 100644 --- a/x-pack/plugins/infra/public/plugin.ts +++ b/x-pack/plugins/infra/public/plugin.ts @@ -7,6 +7,7 @@ import { AppMountParameters, + AppNavLinkStatus, AppUpdater, CoreStart, DEFAULT_APP_CATEGORIES, @@ -136,54 +137,73 @@ export class Plugin implements InfraClientPluginClass { new LogStreamEmbeddableFactoryDefinition(core.getStartServices) ); - core.application.register({ - id: 'logs', - title: i18n.translate('xpack.infra.logs.pluginTitle', { - defaultMessage: 'Logs', - }), - euiIconType: 'logoObservability', - order: 8100, - appRoute: '/app/logs', - // !! Need to be kept in sync with the routes in x-pack/plugins/infra/public/pages/logs/page_content.tsx - deepLinks: [ - { - id: 'stream', - title: i18n.translate('xpack.infra.logs.index.streamTabTitle', { - defaultMessage: 'Stream', - }), - path: '/stream', - }, - { - id: 'anomalies', - title: i18n.translate('xpack.infra.logs.index.anomaliesTabTitle', { - defaultMessage: 'Anomalies', - }), - path: '/anomalies', - }, - { - id: 'log-categories', - title: i18n.translate('xpack.infra.logs.index.logCategoriesBetaBadgeTitle', { - defaultMessage: 'Categories', - }), - path: '/log-categories', - }, - { - id: 'settings', - title: i18n.translate('xpack.infra.logs.index.settingsTabTitle', { - defaultMessage: 'Settings', - }), - path: '/settings', + if (this.config.logs.app_target === 'discover') { + core.application.register({ + id: 'logs-to-discover', + title: '', + navLinkStatus: AppNavLinkStatus.hidden, + appRoute: '/app/logs', + mount: async (params: AppMountParameters) => { + // mount callback should not use setup dependencies, get start dependencies instead + const [coreStart, plugins, pluginStart] = await core.getStartServices(); + + const { renderApp } = await import('./apps/discover_app'); + + return renderApp(coreStart, plugins, pluginStart, params); }, - ], - category: DEFAULT_APP_CATEGORIES.observability, - mount: async (params: AppMountParameters) => { - // mount callback should not use setup dependencies, get start dependencies instead - const [coreStart, pluginsStart, pluginStart] = await core.getStartServices(); - const { renderApp } = await import('./apps/logs_app'); + }); + } - return renderApp(coreStart, pluginsStart, pluginStart, params); - }, - }); + if (this.config.logs.app_target === 'logs-ui') { + core.application.register({ + id: 'logs', + title: i18n.translate('xpack.infra.logs.pluginTitle', { + defaultMessage: 'Logs', + }), + euiIconType: 'logoObservability', + order: 8100, + appRoute: '/app/logs', + // !! Need to be kept in sync with the routes in x-pack/plugins/infra/public/pages/logs/page_content.tsx + deepLinks: [ + { + id: 'stream', + title: i18n.translate('xpack.infra.logs.index.streamTabTitle', { + defaultMessage: 'Stream', + }), + path: '/stream', + }, + { + id: 'anomalies', + title: i18n.translate('xpack.infra.logs.index.anomaliesTabTitle', { + defaultMessage: 'Anomalies', + }), + path: '/anomalies', + }, + { + id: 'log-categories', + title: i18n.translate('xpack.infra.logs.index.logCategoriesBetaBadgeTitle', { + defaultMessage: 'Categories', + }), + path: '/log-categories', + }, + { + id: 'settings', + title: i18n.translate('xpack.infra.logs.index.settingsTabTitle', { + defaultMessage: 'Settings', + }), + path: '/settings', + }, + ], + category: DEFAULT_APP_CATEGORIES.observability, + mount: async (params: AppMountParameters) => { + // mount callback should not use setup dependencies, get start dependencies instead + const [coreStart, plugins, pluginStart] = await core.getStartServices(); + + const { renderApp } = await import('./apps/logs_app'); + return renderApp(coreStart, plugins, pluginStart, params); + }, + }); + } // !! Need to be kept in sync with the routes in x-pack/plugins/infra/public/pages/metrics/index.tsx const infraDeepLinks = [ diff --git a/x-pack/plugins/infra/public/types.ts b/x-pack/plugins/infra/public/types.ts index ac8c8316a7083..9d5cc8abbfbe1 100644 --- a/x-pack/plugins/infra/public/types.ts +++ b/x-pack/plugins/infra/public/types.ts @@ -37,6 +37,7 @@ import type { IStorageWrapper } from '@kbn/kibana-utils-plugin/public'; import { LensPublicStart } from '@kbn/lens-plugin/public'; import type { ChartsPluginStart } from '@kbn/charts-plugin/public'; import { CasesUiStart } from '@kbn/cases-plugin/public'; +import { DiscoverStart } from '@kbn/discover-plugin/public'; import type { UnwrapPromise } from '../common/utility_types'; import type { SourceProviderProps, @@ -79,6 +80,7 @@ export interface InfraClientStartDeps { charts: ChartsPluginStart; data: DataPublicPluginStart; dataViews: DataViewsPublicPluginStart; + discover: DiscoverStart; embeddable?: EmbeddableStart; lens: LensPublicStart; ml: MlPluginStart; diff --git a/x-pack/plugins/infra/server/lib/alerting/inventory_metric_threshold/inventory_metric_threshold_executor.test.ts b/x-pack/plugins/infra/server/lib/alerting/inventory_metric_threshold/inventory_metric_threshold_executor.test.ts index de3ef3cf68d0c..0860da5c7a184 100644 --- a/x-pack/plugins/infra/server/lib/alerting/inventory_metric_threshold/inventory_metric_threshold_executor.test.ts +++ b/x-pack/plugins/infra/server/lib/alerting/inventory_metric_threshold/inventory_metric_threshold_executor.test.ts @@ -99,6 +99,9 @@ const createMockStaticConfiguration = (sources: any) => ({ inventory: { compositeSize: 2000, }, + logs: { + app_target: 'logs-ui', + }, sources, }); diff --git a/x-pack/plugins/infra/server/lib/alerting/metric_threshold/metric_threshold_executor.test.ts b/x-pack/plugins/infra/server/lib/alerting/metric_threshold/metric_threshold_executor.test.ts index a24a02b2df5e8..9298b7583ef3d 100644 --- a/x-pack/plugins/infra/server/lib/alerting/metric_threshold/metric_threshold_executor.test.ts +++ b/x-pack/plugins/infra/server/lib/alerting/metric_threshold/metric_threshold_executor.test.ts @@ -34,6 +34,7 @@ import { import { Evaluation } from './lib/evaluate_rule'; import type { LogMeta, Logger } from '@kbn/logging'; import { DEFAULT_FLAPPING_SETTINGS } from '@kbn/alerting-plugin/common'; +import { InfraConfig } from '../../../../common/plugin_config_types'; jest.mock('./lib/evaluate_rule', () => ({ evaluateRule: jest.fn() })); @@ -1818,7 +1819,7 @@ describe('The metric threshold alert type', () => { }); }); -const createMockStaticConfiguration = (sources: any) => ({ +const createMockStaticConfiguration = (sources: any): InfraConfig => ({ alerting: { inventory_threshold: { group_by_page_size: 100, @@ -1830,6 +1831,9 @@ const createMockStaticConfiguration = (sources: any) => ({ inventory: { compositeSize: 2000, }, + logs: { + app_target: 'logs-ui', + }, sources, }); diff --git a/x-pack/plugins/infra/server/lib/sources/sources.test.ts b/x-pack/plugins/infra/server/lib/sources/sources.test.ts index 22e6d4912a402..a8d78d1ba09f1 100644 --- a/x-pack/plugins/infra/server/lib/sources/sources.test.ts +++ b/x-pack/plugins/infra/server/lib/sources/sources.test.ts @@ -6,6 +6,7 @@ */ import { SavedObject } from '@kbn/core/server'; +import { InfraConfig } from '../../types'; import { infraSourceConfigurationSavedObjectName } from './saved_object_type'; import { InfraSources } from './sources'; @@ -108,7 +109,7 @@ describe('the InfraSources lib', () => { }); }); -const createMockStaticConfiguration = (sources: any) => ({ +const createMockStaticConfiguration = (sources: any): InfraConfig => ({ alerting: { inventory_threshold: { group_by_page_size: 10000, @@ -117,10 +118,12 @@ const createMockStaticConfiguration = (sources: any) => ({ group_by_page_size: 10000, }, }, - enabled: true, inventory: { compositeSize: 2000, }, + logs: { + app_target: 'logs-ui', + }, sources, }); diff --git a/x-pack/plugins/infra/server/plugin.ts b/x-pack/plugins/infra/server/plugin.ts index 0fb0aad42d0cc..7e7507c1d2e45 100644 --- a/x-pack/plugins/infra/server/plugin.ts +++ b/x-pack/plugins/infra/server/plugin.ts @@ -58,6 +58,11 @@ import { UsageCollector } from './usage/usage_collector'; export const config: PluginConfigDescriptor = { schema: schema.object({ + logs: schema.object({ + app_target: schema.oneOf([schema.literal('logs-ui'), schema.literal('discover')], { + defaultValue: 'logs-ui', + }), + }), alerting: schema.object({ inventory_threshold: schema.object({ group_by_page_size: schema.number({ defaultValue: 5_000 }), diff --git a/x-pack/plugins/infra/tsconfig.json b/x-pack/plugins/infra/tsconfig.json index 6d7327a7d9731..dd0632788e847 100644 --- a/x-pack/plugins/infra/tsconfig.json +++ b/x-pack/plugins/infra/tsconfig.json @@ -59,6 +59,7 @@ "@kbn/shared-ux-prompt-not-found", "@kbn/shared-ux-router", "@kbn/shared-ux-link-redirect-app", + "@kbn/discover-plugin", "@kbn/observability-alert-details", "@kbn/observability-shared-plugin", "@kbn/ui-theme" From e35a1d46d95bacda63656de43dc63a429904f4a2 Mon Sep 17 00:00:00 2001 From: Paul Tavares <56442535+paul-tavares@users.noreply.github.com> Date: Wed, 19 Apr 2023 11:40:37 -0400 Subject: [PATCH 50/78] [Security Solution][Endpoint] Show consistent Endpoint Agent Status across security solution (#154961) ## Summary - PR refactors the display of the Endpoint Host Agent status across the multiple places in Security solution so that it uses 1 common component. The Status of an Endpoint Host also displays the isolation state of the endpoint along with any other Actions that might be pending against it. - The refactor also address a prior issue where new added response actions were not accounted for when the component displays (on hover in a popover) the itemized count of pending response actions against the host. The new implementation will display the summary of all pending actions going forward as they are added without having to remember to update the Component. - The Endpoint host agent pending actions display was also adjusted to ensure that isolation state is primary shown when there are multiple pending actions, so that its always visible to the user the state of isolation (see GIF below) As a result of the refactor, several redundant components were deleted. Pages that display Endpoint Host Agent status, and thus impacted by these changes, are: - Endpoint List - Endpoint Details flyout - Responder - Alert Details - Host Details --- .../endpoint_metadata_generator.ts | 29 +- .../service/response_actions/constants.ts | 12 + .../common/endpoint/types/actions.ts | 5 +- .../common/endpoint/types/index.ts | 10 +- .../security_solution/hosts/common/index.ts | 4 +- .../agent_pending_action_status_badge.tsx | 100 ----- .../components/endpoint/agent_status.tsx | 26 -- .../endpoint_agent_status.test.tsx | 366 ++++++++++++++++++ .../endpoint_agent_status.tsx | 343 ++++++++++++++++ .../endpoint/endpoint_agent_status}/index.ts | 4 +- .../endpoint_host_isolation_status.test.tsx | 138 ------- .../endpoint_host_isolation_status.tsx | 167 -------- .../endpoint/host_isolation/index.ts | 1 - .../summary/host_panel/host_panel.test.tsx | 24 +- ...dpoint_agent_and_isolation_status.test.tsx | 47 --- .../endpoint_agent_and_isolation_status.tsx | 61 --- .../status_action.tsx | 11 +- .../components/header_endpoint_info.tsx | 26 +- .../management/pages/endpoint_hosts/mocks.ts | 2 +- .../pages/endpoint_hosts/store/reducer.ts | 1 + .../pages/endpoint_hosts/store/selectors.ts | 61 +-- .../management/pages/endpoint_hosts/types.ts | 3 + .../components/endpoint_agent_status.test.tsx | 90 ----- .../view/components/endpoint_agent_status.tsx | 49 --- .../view/details/endpoint_details_content.tsx | 32 +- .../pages/endpoint_hosts/view/index.tsx | 20 +- .../endpoint_overview/index.test.tsx | 12 +- .../host_overview/endpoint_overview/index.tsx | 22 +- .../body/renderers/agent_statuses.tsx | 60 --- .../body/renderers/formatted_field.tsx | 13 +- .../timeline/body/renderers/translations.ts | 13 - .../factory/hosts/details/helpers.ts | 1 + .../translations/translations/fr-FR.json | 12 - .../translations/translations/ja-JP.json | 12 - .../translations/translations/zh-CN.json | 12 - 35 files changed, 886 insertions(+), 903 deletions(-) delete mode 100644 x-pack/plugins/security_solution/public/common/components/endpoint/agent_pending_action_status_badge.tsx delete mode 100644 x-pack/plugins/security_solution/public/common/components/endpoint/agent_status.tsx create mode 100644 x-pack/plugins/security_solution/public/common/components/endpoint/endpoint_agent_status/endpoint_agent_status.test.tsx create mode 100644 x-pack/plugins/security_solution/public/common/components/endpoint/endpoint_agent_status/endpoint_agent_status.tsx rename x-pack/plugins/security_solution/public/{management/components/endpoint_agent_and_isolation_status => common/components/endpoint/endpoint_agent_status}/index.ts (57%) delete mode 100644 x-pack/plugins/security_solution/public/common/components/endpoint/host_isolation/endpoint_host_isolation_status.test.tsx delete mode 100644 x-pack/plugins/security_solution/public/common/components/endpoint/host_isolation/endpoint_host_isolation_status.tsx delete mode 100644 x-pack/plugins/security_solution/public/management/components/endpoint_agent_and_isolation_status/endpoint_agent_and_isolation_status.test.tsx delete mode 100644 x-pack/plugins/security_solution/public/management/components/endpoint_agent_and_isolation_status/endpoint_agent_and_isolation_status.tsx delete mode 100644 x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/components/endpoint_agent_status.test.tsx delete mode 100644 x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/components/endpoint_agent_status.tsx delete mode 100644 x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/agent_statuses.tsx diff --git a/x-pack/plugins/security_solution/common/endpoint/data_generators/endpoint_metadata_generator.ts b/x-pack/plugins/security_solution/common/endpoint/data_generators/endpoint_metadata_generator.ts index e040fa4811677..e5a44c46f5afc 100644 --- a/x-pack/plugins/security_solution/common/endpoint/data_generators/endpoint_metadata_generator.ts +++ b/x-pack/plugins/security_solution/common/endpoint/data_generators/endpoint_metadata_generator.ts @@ -12,8 +12,8 @@ import { merge, set } from 'lodash'; import { gte } from 'semver'; import type { EndpointCapabilities } from '../service/response_actions/constants'; import { BaseDataGenerator } from './base_data_generator'; -import type { HostMetadataInterface, OSFields } from '../types'; -import { EndpointStatus, HostPolicyResponseActionStatus } from '../types'; +import type { HostMetadataInterface, OSFields, HostInfoInterface } from '../types'; +import { EndpointStatus, HostPolicyResponseActionStatus, HostStatus } from '../types'; export interface GetCustomEndpointMetadataGeneratorOptions { /** Version for agent/endpoint. Defaults to the stack version */ @@ -184,6 +184,31 @@ export class EndpointMetadataGenerator extends BaseDataGenerator { return merge(hostMetadataDoc, overrides); } + /** Generates the complete `HostInfo` as returned by a call to the Endpoint host details api */ + generateHostInfo(overrides: DeepPartial = {}): HostInfoInterface { + const hostInfo: HostInfoInterface = { + metadata: this.generate(), + host_status: HostStatus.HEALTHY, + policy_info: { + endpoint: { + id: 'policy-123', + revision: 4, + }, + agent: { + applied: { + id: 'policy-123', + revision: 4, + }, + configured: { + id: 'policy-123', + revision: 4, + }, + }, + }, + }; + return merge(hostInfo, overrides); + } + protected randomOsFields(): OSFields { return this.randomChoice([ EndpointMetadataGenerator.windowsOSFields, diff --git a/x-pack/plugins/security_solution/common/endpoint/service/response_actions/constants.ts b/x-pack/plugins/security_solution/common/endpoint/service/response_actions/constants.ts index b304dcd819d8b..ea7ee057dc273 100644 --- a/x-pack/plugins/security_solution/common/endpoint/service/response_actions/constants.ts +++ b/x-pack/plugins/security_solution/common/endpoint/service/response_actions/constants.ts @@ -76,6 +76,18 @@ export const commandToRBACMap: Record +>({ + isolate: 'isolate', + unisolate: 'release', + execute: 'execute', + 'get-file': 'get-file', + 'running-processes': 'processes', + 'kill-process': 'kill-process', + 'suspend-process': 'suspend-process', +}); + // 4 hrs in seconds // 4 * 60 * 60 export const DEFAULT_EXECUTE_ACTION_TIMEOUT = 14400; diff --git a/x-pack/plugins/security_solution/common/endpoint/types/actions.ts b/x-pack/plugins/security_solution/common/endpoint/types/actions.ts index 58c07459de441..d9082f8aa1d8c 100644 --- a/x-pack/plugins/security_solution/common/endpoint/types/actions.ts +++ b/x-pack/plugins/security_solution/common/endpoint/types/actions.ts @@ -307,8 +307,9 @@ export interface ResponseActionApiResponse { export interface EndpointPendingActions { agent_id: string; - pending_actions: { - /** Number of actions pending for each type. The `key` could be one of the `RESPONSE_ACTION_COMMANDS` values. */ + /** Number of actions pending for each type */ + pending_actions: Partial> & { + // Defined any other key just in case we get back some other actions [key: string]: number; }; } 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 75899b5422039..4ab2e1e3d2ed0 100644 --- a/x-pack/plugins/security_solution/common/endpoint/types/index.ts +++ b/x-pack/plugins/security_solution/common/endpoint/types/index.ts @@ -473,8 +473,10 @@ export type PolicyInfo = Immutable<{ id: string; }>; -export type HostInfo = Immutable<{ - metadata: HostMetadata; +// Host Information as returned by the Host Details API. +// NOTE: `HostInfo` type is the original and defined as Immutable. +export interface HostInfoInterface { + metadata: HostMetadataInterface; host_status: HostStatus; policy_info?: { agent: { @@ -492,7 +494,9 @@ export type HostInfo = Immutable<{ */ endpoint: PolicyInfo; }; -}>; +} + +export type HostInfo = Immutable; // Host metadata document streamed up to ES by the Endpoint running on host machines. // NOTE: `HostMetadata` type is the original and defined as Immutable. If needing to diff --git a/x-pack/plugins/security_solution/common/search_strategy/security_solution/hosts/common/index.ts b/x-pack/plugins/security_solution/common/search_strategy/security_solution/hosts/common/index.ts index 7f708ab3f6111..2e433b8cfd45f 100644 --- a/x-pack/plugins/security_solution/common/search_strategy/security_solution/hosts/common/index.ts +++ b/x-pack/plugins/security_solution/common/search_strategy/security_solution/hosts/common/index.ts @@ -7,7 +7,7 @@ import type { CloudEcs, HostEcs, OsEcs } from '@kbn/securitysolution-ecs'; import type { Hit, Hits, Maybe, SearchHit, StringOrNumber, TotalValue } from '../../../common'; -import type { EndpointPendingActions, HostStatus } from '../../../../endpoint/types'; +import type { EndpointPendingActions, HostInfo, HostStatus } from '../../../../endpoint/types'; import type { CommonFields } from '../..'; export enum HostPolicyResponseActionStatus { @@ -33,6 +33,8 @@ export interface EndpointFields { elasticAgentStatus?: Maybe; fleetAgentId?: Maybe; id?: Maybe; + /** The complete Endpoint Host Details information (which also includes some of the fields above */ + hostInfo?: HostInfo; } interface AgentFields { diff --git a/x-pack/plugins/security_solution/public/common/components/endpoint/agent_pending_action_status_badge.tsx b/x-pack/plugins/security_solution/public/common/components/endpoint/agent_pending_action_status_badge.tsx deleted file mode 100644 index ee318996b97fe..0000000000000 --- a/x-pack/plugins/security_solution/public/common/components/endpoint/agent_pending_action_status_badge.tsx +++ /dev/null @@ -1,100 +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, { memo } from 'react'; -import { EuiBadge, EuiFlexGroup, EuiFlexItem, EuiTextColor, EuiToolTip } from '@elastic/eui'; -import { FormattedMessage } from '@kbn/i18n-react'; -import type { EndpointHostIsolationStatusProps } from './host_isolation'; - -export const AgentPendingActionStatusBadge = memo< - { 'data-test-subj'?: string } & Pick ->(({ 'data-test-subj': dataTestSubj, pendingActions }) => { - return ( - - -

- -
- {!!pendingActions.pendingIsolate && ( - - - - - {pendingActions.pendingIsolate} - - )} - {!!pendingActions.pendingUnIsolate && ( - - - - - {pendingActions.pendingUnIsolate} - - )} - {!!pendingActions.pendingKillProcess && ( - - - - - {pendingActions.pendingKillProcess} - - )} - {!!pendingActions.pendingSuspendProcess && ( - - - - - {pendingActions.pendingSuspendProcess} - - )} - {!!pendingActions.pendingRunningProcesses && ( - - - - - {pendingActions.pendingRunningProcesses} - - )} -
- } - > - - prev + curr, 0), - }} - /> - - - - ); -}); - -AgentPendingActionStatusBadge.displayName = 'AgentPendingActionStatusBadge'; diff --git a/x-pack/plugins/security_solution/public/common/components/endpoint/agent_status.tsx b/x-pack/plugins/security_solution/public/common/components/endpoint/agent_status.tsx deleted file mode 100644 index 150189e273cb3..0000000000000 --- a/x-pack/plugins/security_solution/public/common/components/endpoint/agent_status.tsx +++ /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 React from 'react'; -import { EuiBadge } from '@elastic/eui'; -import type { HostStatus } from '../../../../common/endpoint/types'; -import { HOST_STATUS_TO_BADGE_COLOR } from '../../../management/pages/endpoint_hosts/view/host_constants'; -import { getAgentStatusText } from './agent_status_text'; - -export const AgentStatus = React.memo(({ hostStatus }: { hostStatus: HostStatus }) => { - return ( - - {getAgentStatusText(hostStatus)} - - ); -}); - -AgentStatus.displayName = 'AgentStatus'; diff --git a/x-pack/plugins/security_solution/public/common/components/endpoint/endpoint_agent_status/endpoint_agent_status.test.tsx b/x-pack/plugins/security_solution/public/common/components/endpoint/endpoint_agent_status/endpoint_agent_status.test.tsx new file mode 100644 index 0000000000000..bf6f85712b6c7 --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/endpoint/endpoint_agent_status/endpoint_agent_status.test.tsx @@ -0,0 +1,366 @@ +/* + * Copyright 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 { AppContextTestRender } from '../../../mock/endpoint'; +import { createAppRootMockRenderer } from '../../../mock/endpoint'; +import type { + EndpointAgentStatusByIdProps, + EndpointAgentStatusProps, +} from './endpoint_agent_status'; +import { EndpointAgentStatus, EndpointAgentStatusById } from './endpoint_agent_status'; +import type { + EndpointPendingActions, + HostInfoInterface, +} from '../../../../../common/endpoint/types'; +import { HostStatus } from '../../../../../common/endpoint/types'; +import React from 'react'; +import { EndpointActionGenerator } from '../../../../../common/endpoint/data_generators/endpoint_action_generator'; +import { EndpointDocGenerator } from '../../../../../common/endpoint/generate_data'; +import { composeHttpHandlerMocks } from '../../../mock/endpoint/http_handler_mock_factory'; +import type { EndpointMetadataHttpMocksInterface } from '../../../../management/pages/endpoint_hosts/mocks'; +import { endpointMetadataHttpMocks } from '../../../../management/pages/endpoint_hosts/mocks'; +import type { ResponseActionsHttpMocksInterface } from '../../../../management/mocks/response_actions_http_mocks'; +import { responseActionsHttpMocks } from '../../../../management/mocks/response_actions_http_mocks'; +import { waitFor, within, fireEvent } from '@testing-library/react'; +import { getEmptyValue } from '../../empty_value'; +import { clone, set } from 'lodash'; + +type AgentStatusApiMocksInterface = EndpointMetadataHttpMocksInterface & + ResponseActionsHttpMocksInterface; + +// API mocks composed from the endpoint metadata API mock and the response actions API mocks +const agentStatusApiMocks = composeHttpHandlerMocks([ + endpointMetadataHttpMocks, + responseActionsHttpMocks, +]); + +describe('When showing Endpoint Agent Status', () => { + const ENDPOINT_ISOLATION_OBJ_PATH = 'metadata.Endpoint.state.isolation'; + + let appTestContext: AppContextTestRender; + let render: () => ReturnType; + let renderResult: ReturnType; + let endpointDetails: HostInfoInterface; + let actionsSummary: EndpointPendingActions; + let apiMocks: ReturnType; + + const triggerTooltip = () => { + fireEvent.mouseOver(renderResult.getByTestId('test-actionStatuses-tooltipTrigger')); + }; + + beforeEach(() => { + appTestContext = createAppRootMockRenderer(); + apiMocks = agentStatusApiMocks(appTestContext.coreStart.http); + + const actionGenerator = new EndpointActionGenerator('seed'); + + actionsSummary = actionGenerator.generateAgentPendingActionsSummary(); + actionsSummary.pending_actions = {}; + apiMocks.responseProvider.agentPendingActionsSummary.mockImplementation(() => { + return { + data: [actionsSummary], + }; + }); + + const metadataGenerator = new EndpointDocGenerator('seed'); + + endpointDetails = { + metadata: metadataGenerator.generateHostMetadata(), + host_status: HostStatus.HEALTHY, + } as HostInfoInterface; + apiMocks.responseProvider.metadataDetails.mockImplementation(() => endpointDetails); + }); + + describe('and using `EndpointAgentStatus` component', () => { + let renderProps: EndpointAgentStatusProps; + + beforeEach(() => { + renderProps = { + 'data-test-subj': 'test', + endpointHostInfo: endpointDetails, + }; + + render = () => { + renderResult = appTestContext.render(); + return renderResult; + }; + }); + + it('should display status', () => { + const { getByTestId } = render(); + + expect(getByTestId('test').textContent).toEqual('Healthy'); + }); + + it('should display status and isolated', () => { + set(endpointDetails, ENDPOINT_ISOLATION_OBJ_PATH, true); + const { getByTestId } = render(); + + expect(getByTestId('test').textContent).toEqual('HealthyIsolated'); + }); + + it('should display status and isolated and display other pending actions in tooltip', async () => { + set(endpointDetails, ENDPOINT_ISOLATION_OBJ_PATH, true); + actionsSummary.pending_actions = { + 'get-file': 2, + execute: 6, + }; + const { getByTestId } = render(); + + await waitFor(() => { + expect(apiMocks.responseProvider.agentPendingActionsSummary).toHaveBeenCalled(); + }); + + expect(getByTestId('test').textContent).toEqual('HealthyIsolated'); + + triggerTooltip(); + + await waitFor(() => { + expect( + within(renderResult.baseElement).getByTestId('test-actionStatuses-tooltipContent') + .textContent + ).toEqual('Pending actions:execute6get-file2'); + }); + }); + + it('should display status and action count', async () => { + actionsSummary.pending_actions = { + 'get-file': 2, + execute: 6, + }; + const { getByTestId } = render(); + + await waitFor(() => { + expect(apiMocks.responseProvider.agentPendingActionsSummary).toHaveBeenCalled(); + }); + + expect(getByTestId('test').textContent).toEqual('Healthy8 actions pending'); + }); + + it('should display status and isolating', async () => { + actionsSummary.pending_actions = { + isolate: 1, + }; + const { getByTestId } = render(); + + await waitFor(() => { + expect(apiMocks.responseProvider.agentPendingActionsSummary).toHaveBeenCalled(); + }); + + expect(getByTestId('test').textContent).toEqual('HealthyIsolating'); + }); + + it('should display status and isolating and have tooltip with other pending actions', async () => { + actionsSummary.pending_actions = { + isolate: 1, + 'kill-process': 1, + }; + const { getByTestId } = render(); + + await waitFor(() => { + expect(apiMocks.responseProvider.agentPendingActionsSummary).toHaveBeenCalled(); + }); + + expect(getByTestId('test').textContent).toEqual('HealthyIsolating'); + + triggerTooltip(); + + await waitFor(() => { + expect( + within(renderResult.baseElement).getByTestId('test-actionStatuses-tooltipContent') + .textContent + ).toEqual('Pending actions:isolate1kill-process1'); + }); + }); + + it('should display status and releasing', async () => { + actionsSummary.pending_actions = { + unisolate: 1, + }; + set(endpointDetails, ENDPOINT_ISOLATION_OBJ_PATH, true); + const { getByTestId } = render(); + + await waitFor(() => { + expect(apiMocks.responseProvider.agentPendingActionsSummary).toHaveBeenCalled(); + }); + + expect(getByTestId('test').textContent).toEqual('HealthyReleasing'); + }); + + it('should display status and releasing and show other pending actions in tooltip', async () => { + actionsSummary.pending_actions = { + unisolate: 1, + 'kill-process': 1, + }; + set(endpointDetails, ENDPOINT_ISOLATION_OBJ_PATH, true); + const { getByTestId } = render(); + + await waitFor(() => { + expect(apiMocks.responseProvider.agentPendingActionsSummary).toHaveBeenCalled(); + }); + + expect(getByTestId('test').textContent).toEqual('HealthyReleasing'); + + triggerTooltip(); + + await waitFor(() => { + expect( + within(renderResult.baseElement).getByTestId('test-actionStatuses-tooltipContent') + .textContent + ).toEqual('Pending actions:kill-process1release1'); + }); + }); + + it('should show individual action count in tooltip (including unknown actions) sorted asc', async () => { + actionsSummary.pending_actions = { + isolate: 1, + 'get-file': 2, + execute: 6, + 'kill-process': 1, + foo: 2, + }; + const { getByTestId } = render(); + + await waitFor(() => { + expect(apiMocks.responseProvider.agentPendingActionsSummary).toHaveBeenCalled(); + }); + + expect(getByTestId('test').textContent).toEqual('HealthyIsolating'); + + triggerTooltip(); + + await waitFor(() => { + expect( + within(renderResult.baseElement).getByTestId('test-actionStatuses-tooltipContent') + .textContent + ).toEqual('Pending actions:execute6foo2get-file2isolate1kill-process1'); + }); + }); + + it('should still display status and isolation state if action summary api fails', async () => { + set(endpointDetails, ENDPOINT_ISOLATION_OBJ_PATH, true); + apiMocks.responseProvider.agentPendingActionsSummary.mockImplementation(() => { + throw new Error('test error'); + }); + + const { getByTestId } = render(); + + await waitFor(() => { + expect(apiMocks.responseProvider.agentPendingActionsSummary).toHaveBeenCalled(); + }); + + expect(getByTestId('test').textContent).toEqual('HealthyIsolated'); + }); + + describe('and `autoRefresh` prop is set to true', () => { + beforeEach(() => { + renderProps.autoRefresh = true; + jest.useFakeTimers(); + }); + + afterEach(() => { + jest.useRealTimers(); + }); + + it('should keep actions up to date when autoRefresh is true', async () => { + apiMocks.responseProvider.agentPendingActionsSummary.mockReturnValueOnce({ + data: [actionsSummary], + }); + + const { getByTestId } = render(); + + await waitFor(() => { + expect(apiMocks.responseProvider.agentPendingActionsSummary).toHaveBeenCalled(); + }); + + expect(getByTestId('test').textContent).toEqual('Healthy'); + + apiMocks.responseProvider.agentPendingActionsSummary.mockReturnValueOnce({ + data: [ + { + ...actionsSummary, + pending_actions: { + 'kill-process': 2, + 'running-processes': 2, + }, + }, + ], + }); + + jest.runOnlyPendingTimers(); + + await waitFor(() => { + expect(getByTestId('test').textContent).toEqual('Healthy4 actions pending'); + }); + }); + }); + }); + + describe('And when using EndpointAgentStatusById', () => { + let renderProps: EndpointAgentStatusByIdProps; + + beforeEach(() => { + jest.useFakeTimers(); + + renderProps = { + 'data-test-subj': 'test', + endpointAgentId: '123', + }; + + render = () => { + renderResult = appTestContext.render(); + return renderResult; + }; + }); + + afterEach(() => { + jest.useRealTimers(); + }); + + it('should display status and isolated', async () => { + set(endpointDetails, ENDPOINT_ISOLATION_OBJ_PATH, true); + const { getByTestId } = render(); + + await waitFor(() => { + expect(getByTestId('test').textContent).toEqual('HealthyIsolated'); + }); + }); + + it('should display empty value if API call to host metadata fails', async () => { + apiMocks.responseProvider.metadataDetails.mockImplementation(() => { + throw new Error('test error'); + }); + const { getByTestId } = render(); + + await waitFor(() => { + expect(apiMocks.responseProvider.metadataDetails).toHaveBeenCalled(); + }); + + expect(getByTestId('test').textContent).toEqual(getEmptyValue()); + }); + + it('should keep agent status up to date when autoRefresh is true', async () => { + renderProps.autoFresh = true; + apiMocks.responseProvider.metadataDetails.mockReturnValueOnce(endpointDetails); + + const { getByTestId } = render(); + + await waitFor(() => { + expect(getByTestId('test').textContent).toEqual('Healthy'); + }); + + apiMocks.responseProvider.metadataDetails.mockReturnValueOnce( + set(clone(endpointDetails), 'metadata.Endpoint.state.isolation', true) + ); + jest.runOnlyPendingTimers(); + + await waitFor(() => { + expect(getByTestId('test').textContent).toEqual('HealthyIsolated'); + }); + }); + }); +}); diff --git a/x-pack/plugins/security_solution/public/common/components/endpoint/endpoint_agent_status/endpoint_agent_status.tsx b/x-pack/plugins/security_solution/public/common/components/endpoint/endpoint_agent_status/endpoint_agent_status.tsx new file mode 100644 index 0000000000000..e74cdcf41fd57 --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/endpoint/endpoint_agent_status/endpoint_agent_status.tsx @@ -0,0 +1,343 @@ +/* + * Copyright 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, { memo, useMemo } from 'react'; +import { + EuiBadge, + EuiFlexGroup, + EuiFlexItem, + EuiText, + EuiTextColor, + EuiToolTip, +} from '@elastic/eui'; +import styled from 'styled-components'; +import { FormattedMessage } from '@kbn/i18n-react'; +import { i18n } from '@kbn/i18n'; +import { DEFAULT_POLL_INTERVAL } from '../../../../management/common/constants'; +import { HOST_STATUS_TO_BADGE_COLOR } from '../../../../management/pages/endpoint_hosts/view/host_constants'; +import { getEmptyValue } from '../../empty_value'; +import type { ResponseActionsApiCommandNames } from '../../../../../common/endpoint/service/response_actions/constants'; +import { RESPONSE_ACTION_API_COMMANDS_TO_CONSOLE_COMMAND_MAP } from '../../../../../common/endpoint/service/response_actions/constants'; +import { useIsExperimentalFeatureEnabled } from '../../../hooks/use_experimental_features'; +import { useGetEndpointPendingActionsSummary } from '../../../../management/hooks/response_actions/use_get_endpoint_pending_actions_summary'; +import { useTestIdGenerator } from '../../../../management/hooks/use_test_id_generator'; +import type { HostInfo, EndpointPendingActions } from '../../../../../common/endpoint/types'; +import { useGetEndpointDetails } from '../../../../management/hooks'; +import { getAgentStatusText } from '../agent_status_text'; + +const TOOLTIP_CONTENT_STYLES: React.CSSProperties = Object.freeze({ width: 150 }); +const ISOLATING_LABEL = i18n.translate( + 'xpack.securitySolution.endpoint.agentAndActionsStatus.isIsolating', + { defaultMessage: 'Isolating' } +); +const RELEASING_LABEL = i18n.translate( + 'xpack.securitySolution.endpoint.agentAndActionsStatus.isUnIsolating', + { defaultMessage: 'Releasing' } +); +const ISOLATED_LABEL = i18n.translate( + 'xpack.securitySolution.endpoint.agentAndActionsStatus.isolated', + { defaultMessage: 'Isolated' } +); + +const EuiFlexGroupStyled = styled(EuiFlexGroup)` + .isolation-status { + margin-left: ${({ theme }) => theme.eui.euiSizeS}; + } +`; + +export interface EndpointAgentStatusProps { + endpointHostInfo: HostInfo; + /** + * If set to `true` (Default), then the endpoint isolation state and response actions count + * will be kept up to date by querying the API periodically. + * Only used if `pendingActions` is not defined. + */ + autoRefresh?: boolean; + /** + * The pending actions for the host (as return by the pending actions summary api). + * If undefined, then this component will call the API to retrieve that list of pending actions. + * NOTE: if this prop is defined, it will invalidate `autoRefresh` prop. + */ + pendingActions?: EndpointPendingActions['pending_actions']; + 'data-test-subj'?: string; +} + +/** + * Displays the status of an Endpoint agent along with its Isolation state or the number of pending + * response actions against it. + * + * TIP: if you only have the Endpoint's `agent.id`, then consider using `EndpointAgentStatusById`, + * which will call the needed APIs to get the information necessary to display the status. + */ +export const EndpointAgentStatus = memo( + ({ endpointHostInfo, autoRefresh = true, pendingActions, 'data-test-subj': dataTestSubj }) => { + const getTestId = useTestIdGenerator(dataTestSubj); + const { data: endpointPendingActions } = useGetEndpointPendingActionsSummary( + [endpointHostInfo.metadata.agent.id], + { + refetchInterval: autoRefresh ? DEFAULT_POLL_INTERVAL : false, + enabled: !pendingActions, + } + ); + + const [hasPendingActions, hostPendingActions] = useMemo< + [boolean, EndpointPendingActions['pending_actions']] + >(() => { + if (!endpointPendingActions && !pendingActions) { + return [false, {}]; + } + + const pending = pendingActions + ? pendingActions + : endpointPendingActions?.data[0].pending_actions ?? {}; + + return [Object.keys(pending).length > 0, pending]; + }, [endpointPendingActions, pendingActions]); + + const status = endpointHostInfo.host_status; + const isIsolated = Boolean(endpointHostInfo.metadata.Endpoint.state?.isolation); + + return ( + + + + {getAgentStatusText(status)} + + + {(isIsolated || hasPendingActions) && ( + + + + )} + + ); + } +); +EndpointAgentStatus.displayName = 'EndpointAgentStatus'; + +export interface EndpointAgentStatusByIdProps { + endpointAgentId: string; + /** + * If set to `true` (Default), then the endpoint status and isolation/action counts will + * be kept up to date by querying the API periodically + */ + autoFresh?: boolean; + 'data-test-subj'?: string; +} + +/** + * Given an Endpoint Agent Id, it will make the necessary API calls and then display the agent + * status using the `` component. + * + * NOTE: if the `HostInfo` is already available, consider using `` component + * instead in order to avoid duplicate API calls. + */ +export const EndpointAgentStatusById = memo( + ({ endpointAgentId, autoFresh, 'data-test-subj': dataTestSubj }) => { + const { data } = useGetEndpointDetails(endpointAgentId, { + refetchInterval: autoFresh ? DEFAULT_POLL_INTERVAL : false, + }); + + const emptyValue = ( + +

{getEmptyValue()}

+
+ ); + + if (!data) { + return emptyValue; + } + + return ( + + ); + } +); +EndpointAgentStatusById.displayName = 'EndpointAgentStatusById'; + +interface EndpointHostResponseActionsStatusProps { + /** The host's individual pending action list as return by the pending action summary api */ + pendingActions: EndpointPendingActions['pending_actions']; + /** Is host currently isolated */ + isIsolated: boolean; + 'data-test-subj'?: string; +} + +const EndpointHostResponseActionsStatus = memo( + ({ pendingActions, isIsolated, 'data-test-subj': dataTestSubj }) => { + const getTestId = useTestIdGenerator(dataTestSubj); + const isPendingStatusDisabled = useIsExperimentalFeatureEnabled( + 'disableIsolationUIPendingStatuses' + ); + + interface PendingActionsState { + actionList: Array<{ label: string; count: number }>; + totalPending: number; + wasReleasing: boolean; + wasIsolating: boolean; + hasMultipleActionTypesPending: boolean; + hasPendingIsolate: boolean; + hasPendingUnIsolate: boolean; + } + + const { + totalPending, + actionList, + wasReleasing, + wasIsolating, + hasMultipleActionTypesPending, + hasPendingIsolate, + hasPendingUnIsolate, + } = useMemo(() => { + const list: Array<{ label: string; count: number }> = []; + let actionTotal = 0; + let actionTypesCount = 0; + + Object.entries(pendingActions) + .sort() + .forEach(([actionName, actionCount]) => { + actionTotal += actionCount; + actionTypesCount += 1; + + list.push({ + count: actionCount, + label: + RESPONSE_ACTION_API_COMMANDS_TO_CONSOLE_COMMAND_MAP[ + actionName as ResponseActionsApiCommandNames + ] ?? actionName, + }); + }); + + const pendingIsolate = pendingActions.isolate ?? 0; + const pendingUnIsolate = pendingActions.unisolate ?? 0; + + return { + actionList: list, + totalPending: actionTotal, + wasReleasing: pendingIsolate === 0 && pendingUnIsolate > 0, + wasIsolating: pendingIsolate > 0 && pendingUnIsolate === 0, + hasMultipleActionTypesPending: actionTypesCount > 1, + hasPendingIsolate: pendingIsolate > 0, + hasPendingUnIsolate: pendingUnIsolate > 0, + }; + }, [pendingActions]); + + const badgeDisplayValue = useMemo(() => { + return hasPendingIsolate ? ( + ISOLATING_LABEL + ) : hasPendingUnIsolate ? ( + RELEASING_LABEL + ) : isIsolated ? ( + ISOLATED_LABEL + ) : ( + + ); + }, [hasPendingIsolate, hasPendingUnIsolate, isIsolated, totalPending]); + + const isolatedBadge = useMemo(() => { + return ( + + {ISOLATED_LABEL} + + ); + }, [dataTestSubj]); + + if (isPendingStatusDisabled) { + // If nothing is pending and host is not currently isolated, then render nothing + if (!isIsolated) { + return null; + } + + return isolatedBadge; + } + + // If nothing is pending + if (totalPending === 0) { + // and host is either releasing and or currently released, then render nothing + if ((!wasIsolating && wasReleasing) || !isIsolated) { + return null; + } + // else host was isolating or is isolated, then show isolation badge + else if ((!isIsolated && wasIsolating && !wasReleasing) || isIsolated) { + return isolatedBadge; + } + } + + // If there are different types of action pending + // --OR-- + // the only type of actions pending is NOT isolate/release, + // then show a summary with tooltip + if (hasMultipleActionTypesPending || (!hasPendingIsolate && !hasPendingUnIsolate)) { + return ( + + +
+ +
+ {actionList.map(({ count, label }) => { + return ( + + {label} + {count} + + ); + })} +
+ } + > + + {badgeDisplayValue} + + + + ); + } + + // show pending isolation badge if a single type of isolation action has pending numbers. + // We don't care about the count here because if there were more than 1 of the same type + // (ex. 3 isolate... 0 release), then the action status displayed is still the same - "isolating". + return ( + + + {badgeDisplayValue} + + + ); + } +); +EndpointHostResponseActionsStatus.displayName = 'EndpointHostResponseActionsStatus'; diff --git a/x-pack/plugins/security_solution/public/management/components/endpoint_agent_and_isolation_status/index.ts b/x-pack/plugins/security_solution/public/common/components/endpoint/endpoint_agent_status/index.ts similarity index 57% rename from x-pack/plugins/security_solution/public/management/components/endpoint_agent_and_isolation_status/index.ts rename to x-pack/plugins/security_solution/public/common/components/endpoint/endpoint_agent_status/index.ts index 8379f425733cb..1d94de32e333c 100644 --- a/x-pack/plugins/security_solution/public/management/components/endpoint_agent_and_isolation_status/index.ts +++ b/x-pack/plugins/security_solution/public/common/components/endpoint/endpoint_agent_status/index.ts @@ -5,5 +5,5 @@ * 2.0. */ -export { EndpointAgentAndIsolationStatus } from './endpoint_agent_and_isolation_status'; -export type { EndpointAgentAndIsolationStatusProps } from './endpoint_agent_and_isolation_status'; +export * from './endpoint_agent_status'; +export type { EndpointAgentStatusProps } from './endpoint_agent_status'; diff --git a/x-pack/plugins/security_solution/public/common/components/endpoint/host_isolation/endpoint_host_isolation_status.test.tsx b/x-pack/plugins/security_solution/public/common/components/endpoint/host_isolation/endpoint_host_isolation_status.test.tsx deleted file mode 100644 index 7f4cee7fa8973..0000000000000 --- a/x-pack/plugins/security_solution/public/common/components/endpoint/host_isolation/endpoint_host_isolation_status.test.tsx +++ /dev/null @@ -1,138 +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 type { EndpointHostIsolationStatusProps } from './endpoint_host_isolation_status'; -import { EndpointHostIsolationStatus } from './endpoint_host_isolation_status'; -import type { AppContextTestRender } from '../../../mock/endpoint'; -import { createAppRootMockRenderer } from '../../../mock/endpoint'; - -describe('when using the EndpointHostIsolationStatus component', () => { - let render: ( - renderProps?: Partial - ) => ReturnType; - let appContext: AppContextTestRender; - - beforeEach(() => { - appContext = createAppRootMockRenderer(); - - render = (renderProps = {}) => - appContext.render( - - ); - }); - - it('should render `null` if not isolated and nothing is pending', () => { - const renderResult = render(); - expect(renderResult.container.textContent).toBe(''); - }); - - it('should show `Isolated` when no pending actions and isolated', () => { - const { getByTestId } = render({ isIsolated: true }); - expect(getByTestId('test').textContent).toBe('Isolated'); - }); - - it.each([ - [ - 'Isolating', - { - pendingActions: { - pendingIsolate: 1, - }, - }, - ], - [ - 'Releasing', - { - pendingActions: { - pendingUnIsolate: 1, - }, - }, - ], - [ - // Because they are both of the same type and there are no other types, - // the status should be `isolating` - 'Isolating', - { - pendingActions: { - pendingIsolate: 2, - }, - }, - ], - [ - // Because they are both of the same type and there are no other types, - // the status should be `Releasing` - 'Releasing', - { - pendingActions: { - pendingUnIsolate: 2, - }, - }, - ], - [ - '10 actions pending', - { - isIsolated: true, - pendingActions: { - pendingIsolate: 2, - pendingUnIsolate: 2, - pendingKillProcess: 2, - pendingSuspendProcess: 2, - pendingRunningProcesses: 2, - }, - }, - ], - [ - '1 action pending', - { - isIsolated: true, - pendingActions: { - pendingKillProcess: 1, - }, - }, - ], - ])('should show %s}', (expectedLabel, componentProps) => { - const { getByTestId } = render(componentProps); - expect(getByTestId('test').textContent).toBe(expectedLabel); - // Validate that the text color is set to `subdued` - expect(getByTestId('test-pending').classList.toString().includes('subdued')).toBe(true); - }); - - describe('and the disableIsolationUIPendingStatuses experimental feature flag is true', () => { - beforeEach(() => { - appContext.setExperimentalFlag({ disableIsolationUIPendingStatuses: true }); - }); - - it('should render `null` if not isolated', () => { - const renderResult = render({ - pendingActions: { - pendingIsolate: 10, - pendingUnIsolate: 20, - }, - }); - expect(renderResult.container.textContent).toBe(''); - }); - - it('should show `Isolated` when no pending actions and isolated', () => { - const { getByTestId } = render({ - isIsolated: true, - pendingActions: { - pendingIsolate: 10, - pendingUnIsolate: 20, - }, - }); - expect(getByTestId('test').textContent).toBe('Isolated'); - }); - }); -}); diff --git a/x-pack/plugins/security_solution/public/common/components/endpoint/host_isolation/endpoint_host_isolation_status.tsx b/x-pack/plugins/security_solution/public/common/components/endpoint/host_isolation/endpoint_host_isolation_status.tsx deleted file mode 100644 index 650999029c545..0000000000000 --- a/x-pack/plugins/security_solution/public/common/components/endpoint/host_isolation/endpoint_host_isolation_status.tsx +++ /dev/null @@ -1,167 +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, { memo, useMemo, useRef, useEffect } from 'react'; -import { EuiBadge, EuiTextColor } from '@elastic/eui'; -import { FormattedMessage } from '@kbn/i18n-react'; -import { useTestIdGenerator } from '../../../../management/hooks/use_test_id_generator'; -import { useIsExperimentalFeatureEnabled } from '../../../hooks/use_experimental_features'; -import { AgentPendingActionStatusBadge } from '../agent_pending_action_status_badge'; - -export interface EndpointHostIsolationStatusProps { - isIsolated: boolean; - pendingActions: { - /** the count of pending isolate actions */ - pendingIsolate?: number; - /** the count of pending unisolate actions */ - pendingUnIsolate?: number; - pendingKillProcess?: number; - pendingSuspendProcess?: number; - pendingRunningProcesses?: number; - }; - 'data-test-subj'?: string; -} - -/** - * Component will display a host isolation status based on whether it is currently isolated or there are - * isolate/unisolate actions pending. If none of these are applicable, no UI component will be rendered - * (`null` is returned) - */ -export const EndpointHostIsolationStatus = memo( - ({ isIsolated, pendingActions, 'data-test-subj': dataTestSubj }) => { - const getTestId = useTestIdGenerator(dataTestSubj); - const isPendingStatusDisabled = useIsExperimentalFeatureEnabled( - 'disableIsolationUIPendingStatuses' - ); - - const { - pendingIsolate = 0, - pendingUnIsolate = 0, - pendingKillProcess = 0, - pendingSuspendProcess = 0, - pendingRunningProcesses = 0, - } = pendingActions; - - const wasReleasing = useRef(false); - const wasIsolating = useRef(false); - - const totalPending = useMemo( - () => - pendingIsolate + - pendingUnIsolate + - pendingKillProcess + - pendingSuspendProcess + - pendingRunningProcesses, - [ - pendingIsolate, - pendingKillProcess, - pendingRunningProcesses, - pendingSuspendProcess, - pendingUnIsolate, - ] - ); - - const hasMultipleActionTypesPending = useMemo(() => { - return ( - Object.values(pendingActions).reduce((countOfTypes, pendingActionCount) => { - if (pendingActionCount > 0) { - return countOfTypes + 1; - } - return countOfTypes; - }, 0) > 1 - ); - }, [pendingActions]); - - useEffect(() => { - wasReleasing.current = pendingIsolate === 0 && pendingUnIsolate > 0; - wasIsolating.current = pendingIsolate > 0 && pendingUnIsolate === 0; - }, [pendingIsolate, pendingUnIsolate]); - - return useMemo(() => { - if (isPendingStatusDisabled) { - // If nothing is pending and host is not currently isolated, then render nothing - if (!isIsolated) { - return null; - } - - return ( - - - - ); - } - - // If nothing is pending - if (totalPending === 0) { - // and host is either releasing and or currently released, then render nothing - if ((!wasIsolating.current && wasReleasing.current) || !isIsolated) { - return null; - } - // else host was isolating or is isolated, then show isolation badge - else if ((!isIsolated && wasIsolating.current && !wasReleasing.current) || isIsolated) { - return ( - - - - ); - } - } - - // If there are different types of action pending - // --OR-- - // the only type of actions pending is NOT isolate/release, - // then show a summary with tooltip - if (hasMultipleActionTypesPending || (!pendingIsolate && !pendingUnIsolate)) { - return ( - - ); - } - - // show pending isolation badge if a single type of isolation action has pending numbers. - // We don't care about the count here because if there were more than 1 of the same type - // (ex. 3 isolate... 0 release), then the action status displayed is still the same - "isolating". - return ( - - - {pendingIsolate ? ( - - ) : ( - - )} - - - ); - }, [ - isPendingStatusDisabled, - totalPending, - hasMultipleActionTypesPending, - pendingIsolate, - pendingUnIsolate, - dataTestSubj, - getTestId, - isIsolated, - pendingActions, - ]); - } -); - -EndpointHostIsolationStatus.displayName = 'EndpointHostIsolationStatus'; diff --git a/x-pack/plugins/security_solution/public/common/components/endpoint/host_isolation/index.ts b/x-pack/plugins/security_solution/public/common/components/endpoint/host_isolation/index.ts index 24b94cd6212b7..41763a6e88d37 100644 --- a/x-pack/plugins/security_solution/public/common/components/endpoint/host_isolation/index.ts +++ b/x-pack/plugins/security_solution/public/common/components/endpoint/host_isolation/index.ts @@ -8,5 +8,4 @@ export * from './isolate_success'; export * from './isolate_form'; export * from './unisolate_form'; -export * from './endpoint_host_isolation_status'; export * from './action_completion_return_button'; diff --git a/x-pack/plugins/security_solution/public/detections/pages/alert_details/tabs/summary/host_panel/host_panel.test.tsx b/x-pack/plugins/security_solution/public/detections/pages/alert_details/tabs/summary/host_panel/host_panel.test.tsx index edca07bd3426f..210c700c2eb37 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/alert_details/tabs/summary/host_panel/host_panel.test.tsx +++ b/x-pack/plugins/security_solution/public/detections/pages/alert_details/tabs/summary/host_panel/host_panel.test.tsx @@ -20,6 +20,28 @@ import { getTimelineEventData } from '../../../utils/get_timeline_event_data'; import { RiskSeverity } from '../../../../../../../common/search_strategy'; import { useRiskScore } from '../../../../../../explore/containers/risk_score'; +jest.mock('../../../../../../management/hooks', () => { + const Generator = jest.requireActual( + '../../../../../../../common/endpoint/data_generators/endpoint_metadata_generator' + ); + + return { + useGetEndpointDetails: jest.fn(() => { + return { + data: new Generator.EndpointMetadataGenerator('seed').generateHostInfo({ + metadata: { + Endpoint: { + state: { + isolation: true, + }, + }, + }, + }), + }; + }), + }; +}); + jest.mock('../../../../../../explore/containers/risk_score'); const mockUseRiskScore = useRiskScore as jest.Mock; @@ -76,7 +98,7 @@ describe('AlertDetailsPage - SummaryTab - HostPanel', () => { describe('Agent status', () => { it('should show healthy', () => { const { getByTestId } = render(); - expect(getByTestId('host-panel-agent-status')).toHaveTextContent('Healthy'); + expect(getByTestId('endpointHostAgentStatus').textContent).toEqual('HealthyIsolated'); }); }); diff --git a/x-pack/plugins/security_solution/public/management/components/endpoint_agent_and_isolation_status/endpoint_agent_and_isolation_status.test.tsx b/x-pack/plugins/security_solution/public/management/components/endpoint_agent_and_isolation_status/endpoint_agent_and_isolation_status.test.tsx deleted file mode 100644 index 742f5515e7ade..0000000000000 --- a/x-pack/plugins/security_solution/public/management/components/endpoint_agent_and_isolation_status/endpoint_agent_and_isolation_status.test.tsx +++ /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 { AppContextTestRender } from '../../../common/mock/endpoint'; -import { createAppRootMockRenderer } from '../../../common/mock/endpoint'; -import type { EndpointAgentAndIsolationStatusProps } from './endpoint_agent_and_isolation_status'; -import { EndpointAgentAndIsolationStatus } from './endpoint_agent_and_isolation_status'; -import { HostStatus } from '../../../../common/endpoint/types'; -import React from 'react'; - -describe('When using the EndpointAgentAndIsolationStatus component', () => { - let render: () => ReturnType; - let renderResult: ReturnType; - let renderProps: EndpointAgentAndIsolationStatusProps; - - beforeEach(() => { - const appTestContext = createAppRootMockRenderer(); - - renderProps = { - status: HostStatus.HEALTHY, - 'data-test-subj': 'test', - pendingActions: {}, - }; - - render = () => { - renderResult = appTestContext.render(); - return renderResult; - }; - }); - - it('should display host status only when `isIsolated` is undefined', () => { - render(); - - expect(renderResult.queryByTestId('test-isolationStatus')).toBeNull(); - }); - - it('should display pending status and pending counts', () => { - renderProps.isIsolated = true; - render(); - - expect(renderResult.getByTestId('test-isolationStatus')).toBeTruthy(); - }); -}); diff --git a/x-pack/plugins/security_solution/public/management/components/endpoint_agent_and_isolation_status/endpoint_agent_and_isolation_status.tsx b/x-pack/plugins/security_solution/public/management/components/endpoint_agent_and_isolation_status/endpoint_agent_and_isolation_status.tsx deleted file mode 100644 index 3e80d57d2d70b..0000000000000 --- a/x-pack/plugins/security_solution/public/management/components/endpoint_agent_and_isolation_status/endpoint_agent_and_isolation_status.tsx +++ /dev/null @@ -1,61 +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, { memo } from 'react'; -import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; -import styled from 'styled-components'; -import { useTestIdGenerator } from '../../hooks/use_test_id_generator'; -import type { HostStatus } from '../../../../common/endpoint/types'; -import { AgentStatus } from '../../../common/components/endpoint/agent_status'; -import type { EndpointHostIsolationStatusProps } from '../../../common/components/endpoint/host_isolation'; -import { EndpointHostIsolationStatus } from '../../../common/components/endpoint/host_isolation'; - -const EuiFlexGroupStyled = styled(EuiFlexGroup)` - .isolation-status { - margin-left: ${({ theme }) => theme.eui.euiSizeS}; - } -`; - -export interface EndpointAgentAndIsolationStatusProps - extends Pick { - status: HostStatus; - /** - * If defined with a boolean, then the isolation status will be shown along with the agent status. - * The `pendingIsolate` and `pendingUnIsolate` props will only be used when this prop is set to a - * `boolean` - */ - isIsolated?: boolean; - 'data-test-subj'?: string; -} - -export const EndpointAgentAndIsolationStatus = memo( - ({ status, isIsolated, pendingActions, 'data-test-subj': dataTestSubj }) => { - const getTestId = useTestIdGenerator(dataTestSubj); - return ( - - - - - {isIsolated !== undefined && ( - - - - )} - - ); - } -); -EndpointAgentAndIsolationStatus.displayName = 'EndpointAgentAndIsolationStatus'; diff --git a/x-pack/plugins/security_solution/public/management/components/endpoint_responder/command_render_components/status_action.tsx b/x-pack/plugins/security_solution/public/management/components/endpoint_responder/command_render_components/status_action.tsx index 94b7aa9dd5160..e901e9b1a116d 100644 --- a/x-pack/plugins/security_solution/public/management/components/endpoint_responder/command_render_components/status_action.tsx +++ b/x-pack/plugins/security_solution/public/management/components/endpoint_responder/command_render_components/status_action.tsx @@ -12,7 +12,6 @@ import { i18n } from '@kbn/i18n'; import type { IHttpFetchError } from '@kbn/core-http-browser'; import type { HostInfo, PendingActionsResponse } from '../../../../../common/endpoint/types'; import type { EndpointCommandDefinitionMeta } from '../types'; -import type { EndpointHostIsolationStatusProps } from '../../../../common/components/endpoint/host_isolation'; import { useGetEndpointPendingActionsSummary } from '../../../hooks/response_actions/use_get_endpoint_pending_actions_summary'; import { FormattedDate } from '../../../../common/components/formatted_date'; import { useGetEndpointDetails } from '../../../hooks'; @@ -53,12 +52,10 @@ export const EndpointStatusActionResult = memo< queryKey: [queryKey, endpointId], }); - const pendingIsolationActions = useMemo< - Pick< - Required, - 'pendingIsolate' | 'pendingUnIsolate' - > - >(() => { + const pendingIsolationActions = useMemo<{ + pendingIsolate: number; + pendingUnIsolate: number; + }>(() => { if (endpointPendingActions?.data.length) { const pendingActions = endpointPendingActions.data[0].pending_actions; diff --git a/x-pack/plugins/security_solution/public/management/components/endpoint_responder/components/header_endpoint_info.tsx b/x-pack/plugins/security_solution/public/management/components/endpoint_responder/components/header_endpoint_info.tsx index 2ffcf76b8ba01..b56746e7890a6 100644 --- a/x-pack/plugins/security_solution/public/management/components/endpoint_responder/components/header_endpoint_info.tsx +++ b/x-pack/plugins/security_solution/public/management/components/endpoint_responder/components/header_endpoint_info.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import React, { memo, useMemo } from 'react'; +import React, { memo } from 'react'; import { EuiFlexGroup, EuiFlexItem, @@ -17,8 +17,7 @@ import { import { euiStyled } from '@kbn/kibana-react-plugin/common'; import { FormattedMessage, FormattedRelative } from '@kbn/i18n-react'; import { useGetEndpointDetails } from '../../../hooks/endpoint/use_get_endpoint_details'; -import type { EndpointHostIsolationStatusProps } from '../../../../common/components/endpoint/host_isolation'; -import { EndpointAgentAndIsolationStatus } from '../../endpoint_agent_and_isolation_status'; +import { EndpointAgentStatus } from '../../../../common/components/endpoint/endpoint_agent_status'; import { useGetEndpointPendingActionsSummary } from '../../../hooks/response_actions/use_get_endpoint_pending_actions_summary'; import type { Platform } from './platforms'; import { PlatformIcon } from './platforms'; @@ -42,21 +41,6 @@ export const HeaderEndpointInfo = memo(({ endpointId }) refetchInterval: 10000, }); - const pendingActionRequests = useMemo< - Pick, 'pendingActions'> - >(() => { - const pendingActions = endpointPendingActions?.data?.[0].pending_actions; - return { - pendingActions: { - pendingIsolate: pendingActions?.isolate ?? 0, - pendingUnIsolate: pendingActions?.unisolate ?? 0, - pendingKillProcess: pendingActions?.['kill-process'] ?? 0, - pendingSuspendProcess: pendingActions?.['suspend-process'] ?? 0, - pendingRunningProcesses: pendingActions?.['running-processes'] ?? 0, - }, - }; - }, [endpointPendingActions?.data]); - if (isFetching && endpointPendingActions === undefined) { return ; } @@ -90,10 +74,8 @@ export const HeaderEndpointInfo = memo(({ endpointId }) - diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/mocks.ts b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/mocks.ts index 39bd07d071974..7030f13bdd0f9 100644 --- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/mocks.ts +++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/mocks.ts @@ -45,7 +45,7 @@ import { fleetGetPackagePoliciesListHttpMock, } from '../../mocks'; -type EndpointMetadataHttpMocksInterface = ResponseProvidersInterface<{ +export type EndpointMetadataHttpMocksInterface = ResponseProvidersInterface<{ metadataList: () => MetadataListResponse; metadataDetails: () => HostInfo; }>; diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/reducer.ts b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/reducer.ts index 29f0d81b96a97..8ad781c60dd20 100644 --- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/reducer.ts +++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/reducer.ts @@ -113,6 +113,7 @@ export const endpointListReducer: StateReducer = (state = initialEndpointPageSta ...state, endpointDetails: { ...state.endpointDetails, + hostInfo: action.payload, hostDetails: { ...state.endpointDetails.hostDetails, details: action.payload.metadata, diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/selectors.ts b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/selectors.ts index 6431bde743483..1b4c716c37462 100644 --- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/selectors.ts +++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/selectors.ts @@ -11,7 +11,7 @@ import { createSelector } from 'reselect'; import { matchPath } from 'react-router-dom'; import { decode } from '@kbn/rison'; import type { Query } from '@kbn/es-query'; -import type { Immutable, HostMetadata } from '../../../../../common/endpoint/types'; +import type { Immutable, EndpointPendingActions } from '../../../../../common/endpoint/types'; import { HostStatus } from '../../../../../common/endpoint/types'; import type { EndpointState, EndpointIndexUIQueryParams } from '../types'; import { extractListPaginationParams } from '../../../common/routing'; @@ -29,7 +29,6 @@ import { import type { ServerApiError } from '../../../../common/types'; import { isEndpointHostIsolated } from '../../../../common/utils/validators'; -import type { EndpointHostIsolationStatusProps } from '../../../../common/components/endpoint/host_isolation'; import { EndpointDetailsTabsTypes } from '../view/details/components/endpoint_details_tabs'; export const listData = (state: Immutable) => state.hosts; @@ -47,6 +46,9 @@ export const listError = (state: Immutable) => state.error; export const detailsData = (state: Immutable) => state.endpointDetails.hostDetails.details; +export const fullDetailsHostInfo = (state: Immutable) => + state.endpointDetails.hostInfo; + export const detailsLoading = (state: Immutable): boolean => state.endpointDetails.hostDetails.detailsLoading; @@ -266,53 +268,32 @@ export const getEndpointPendingActionsState = ( return state.endpointPendingActions; }; +export const getMetadataTransformStats = (state: Immutable) => + state.metadataTransformStats; + +export const metadataTransformStats = (state: Immutable) => + isLoadedResourceState(state.metadataTransformStats) ? state.metadataTransformStats.data : []; + +export const isMetadataTransformStatsLoading = (state: Immutable) => + isLoadingResourceState(state.metadataTransformStats); + /** - * Returns a function (callback) that can be used to retrieve the props for the `EndpointHostIsolationStatus` - * component for a given Endpoint + * Returns a function (callback) that can be used to retrieve the list of pending actions against + * an endpoint currently displayed in the endpoint list */ -export const getEndpointHostIsolationStatusPropsCallback: ( +export const getEndpointPendingActionsCallback: ( state: Immutable -) => (endpoint: HostMetadata) => EndpointHostIsolationStatusProps = createSelector( +) => (endpointId: string) => EndpointPendingActions['pending_actions'] = createSelector( getEndpointPendingActionsState, (pendingActionsState) => { - return (endpoint: HostMetadata) => { - let pendingIsolate = 0; - let pendingUnIsolate = 0; - let pendingKillProcess = 0; - let pendingSuspendProcess = 0; - let pendingRunningProcesses = 0; + return (endpointId: string) => { + let response: EndpointPendingActions['pending_actions'] = {}; if (isLoadedResourceState(pendingActionsState)) { - const endpointPendingActions = pendingActionsState.data.get(endpoint.elastic.agent.id); - - if (endpointPendingActions) { - pendingIsolate = endpointPendingActions?.isolate ?? 0; - pendingUnIsolate = endpointPendingActions?.unisolate ?? 0; - pendingKillProcess = endpointPendingActions?.['kill-process'] ?? 0; - pendingSuspendProcess = endpointPendingActions?.['suspend-process'] ?? 0; - pendingRunningProcesses = endpointPendingActions?.['running-processes'] ?? 0; - } + response = pendingActionsState.data.get(endpointId) ?? {}; } - return { - isIsolated: isEndpointHostIsolated(endpoint), - pendingActions: { - pendingIsolate, - pendingUnIsolate, - pendingKillProcess, - pendingSuspendProcess, - pendingRunningProcesses, - }, - }; + return response; }; } ); - -export const getMetadataTransformStats = (state: Immutable) => - state.metadataTransformStats; - -export const metadataTransformStats = (state: Immutable) => - isLoadedResourceState(state.metadataTransformStats) ? state.metadataTransformStats.data : []; - -export const isMetadataTransformStatsLoading = (state: Immutable) => - isLoadingResourceState(state.metadataTransformStats); diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/types.ts b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/types.ts index 8d7e6b0c4d10b..cdd5020226697 100644 --- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/types.ts +++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/types.ts @@ -36,6 +36,9 @@ export interface EndpointState { /** api error from retrieving host list */ error?: ServerApiError; endpointDetails: { + // Adding `hostInfo` to store full API response in order to support the + // refactoring effort with AgentStatus component + hostInfo?: HostInfo; hostDetails: { /** details data for a specific host */ details?: Immutable; diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/components/endpoint_agent_status.test.tsx b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/components/endpoint_agent_status.test.tsx deleted file mode 100644 index c4270e8736e83..0000000000000 --- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/components/endpoint_agent_status.test.tsx +++ /dev/null @@ -1,90 +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 type { AppContextTestRender } from '../../../../../common/mock/endpoint'; -import { createAppRootMockRenderer } from '../../../../../common/mock/endpoint'; -import { endpointPageHttpMock } from '../../mocks'; -import { act } from '@testing-library/react'; -import type { EndpointAgentStatusProps } from './endpoint_agent_status'; -import { EndpointAgentStatus } from './endpoint_agent_status'; -import type { HostMetadata } from '../../../../../../common/endpoint/types'; -import { HostStatus } from '../../../../../../common/endpoint/types'; -import { isLoadedResourceState } from '../../../../state'; -import { KibanaServices } from '../../../../../common/lib/kibana'; - -jest.mock('../../../../../common/lib/kibana'); - -describe('When using the EndpointAgentStatus component', () => { - let render: ( - props: EndpointAgentStatusProps - ) => Promise>; - let waitForAction: AppContextTestRender['middlewareSpy']['waitForAction']; - let renderResult: ReturnType; - let httpMocks: ReturnType; - let endpointMeta: HostMetadata; - - beforeEach(() => { - const mockedContext = createAppRootMockRenderer(); - - (KibanaServices.get as jest.Mock).mockReturnValue(mockedContext.startServices); - httpMocks = endpointPageHttpMock(mockedContext.coreStart.http); - waitForAction = mockedContext.middlewareSpy.waitForAction; - endpointMeta = httpMocks.responseProvider.metadataList().data[0].metadata; - render = async (props: EndpointAgentStatusProps) => { - renderResult = mockedContext.render(); - return renderResult; - }; - - act(() => { - mockedContext.history.push('/administration/endpoints'); - }); - }); - - it.each([ - ['Healthy', 'healthy'], - ['Unhealthy', 'unhealthy'], - ['Updating', 'updating'], - ['Offline', 'offline'], - ['Inactive', 'inactive'], - ['Unhealthy', 'someUnknownValueHere'], - ])('should show agent status of %s', async (expectedLabel, hostStatus) => { - await render({ hostStatus: hostStatus as HostStatus, endpointMetadata: endpointMeta }); - expect(renderResult.getByTestId('rowHostStatus').textContent).toEqual(expectedLabel); - }); - - // FIXME: un-skip test once Islation pending statuses are supported - describe.skip('and host is isolated or pending isolation', () => { - beforeEach(async () => { - // Ensure pending action api sets pending action for the test endpoint metadata - const pendingActionsResponseProvider = - httpMocks.responseProvider.pendingActions.getMockImplementation(); - httpMocks.responseProvider.pendingActions.mockImplementation((...args) => { - const response = pendingActionsResponseProvider!(...args); - response.data.some((pendingAction) => { - if (pendingAction.agent_id === endpointMeta.elastic.agent.id) { - pendingAction.pending_actions.isolate = 1; - return true; - } - return false; - }); - return response; - }); - - const loadingPendingActions = waitForAction('endpointPendingActionsStateChanged', { - validate: (action) => isLoadedResourceState(action.payload), - }); - - await render({ hostStatus: HostStatus.HEALTHY, endpointMetadata: endpointMeta }); - await loadingPendingActions; - }); - - it('should show host pending action', () => { - expect(renderResult.getByTestId('rowIsolationStatus').textContent).toEqual('Isolating'); - }); - }); -}); diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/components/endpoint_agent_status.tsx b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/components/endpoint_agent_status.tsx deleted file mode 100644 index 494545b237052..0000000000000 --- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/components/endpoint_agent_status.tsx +++ /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 React, { memo } from 'react'; -import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; -import styled from 'styled-components'; -import type { HostInfo, HostMetadata } from '../../../../../../common/endpoint/types'; -import { EndpointHostIsolationStatus } from '../../../../../common/components/endpoint/host_isolation'; -import { useEndpointSelector } from '../hooks'; -import { getEndpointHostIsolationStatusPropsCallback } from '../../store/selectors'; -import { AgentStatus } from '../../../../../common/components/endpoint/agent_status'; - -const EuiFlexGroupStyled = styled(EuiFlexGroup)` - .isolation-status { - margin-left: ${({ theme }) => theme.eui.euiSizeS}; - } -`; - -export interface EndpointAgentStatusProps { - hostStatus: HostInfo['host_status']; - endpointMetadata: HostMetadata; -} -export const EndpointAgentStatus = memo( - ({ endpointMetadata, hostStatus }) => { - const getEndpointIsolationStatusProps = useEndpointSelector( - getEndpointHostIsolationStatusPropsCallback - ); - - return ( - - - - - - - - - ); - } -); - -EndpointAgentStatus.displayName = 'EndpointAgentStatus'; diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/endpoint_details_content.tsx b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/endpoint_details_content.tsx index d142e1385e80d..b33f98078b9fb 100644 --- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/endpoint_details_content.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/endpoint_details_content.tsx @@ -17,17 +17,23 @@ import { } from '@elastic/eui'; import React, { memo, useMemo } from 'react'; import { FormattedMessage } from '@kbn/i18n-react'; +import { EndpointAgentStatus } from '../../../../../common/components/endpoint/endpoint_agent_status'; import { isPolicyOutOfDate } from '../../utils'; import type { HostInfo, HostMetadata, HostStatus } from '../../../../../../common/endpoint/types'; import { useEndpointSelector } from '../hooks'; -import { nonExistingPolicies, policyResponseStatus, uiQueryParams } from '../../store/selectors'; +import { + fullDetailsHostInfo, + getEndpointPendingActionsCallback, + nonExistingPolicies, + policyResponseStatus, + uiQueryParams, +} from '../../store/selectors'; import { POLICY_STATUS_TO_BADGE_COLOR } from '../host_constants'; import { FormattedDate } from '../../../../../common/components/formatted_date'; import { useNavigateByRouterEventHandler } from '../../../../../common/hooks/endpoint/use_navigate_by_router_event_handler'; import { getEndpointDetailsPath } from '../../../../common/routing'; import { EndpointPolicyLink } from '../../../../components/endpoint_policy_link'; import { OutOfDate } from '../components/out_of_date'; -import { EndpointAgentStatus } from '../components/endpoint_agent_status'; const EndpointDetailsContentStyled = styled.div` dl dt { @@ -63,8 +69,9 @@ export const EndpointDetailsContent = memo( const policyStatus = useEndpointSelector( policyResponseStatus ) as keyof typeof POLICY_STATUS_TO_BADGE_COLOR; - + const getHostPendingActions = useEndpointSelector(getEndpointPendingActionsCallback); const missingPolicies = useEndpointSelector(nonExistingPolicies); + const hostInfo = useEndpointSelector(fullDetailsHostInfo); const policyResponseRoutePath = useMemo(() => { // eslint-disable-next-line @typescript-eslint/naming-convention @@ -101,7 +108,14 @@ export const EndpointDetailsContent = memo( /> ), - description: , + description: hostInfo ? ( + + ) : ( + <> + ), }, { title: ( @@ -214,7 +228,15 @@ export const EndpointDetailsContent = memo( ), }, ]; - }, [details, hostStatus, policyStatus, policyStatusClickHandler, policyInfo, missingPolicies]); + }, [ + details, + getHostPendingActions, + hostInfo, + missingPolicies, + policyInfo, + policyStatus, + policyStatusClickHandler, + ]); return ( diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.tsx b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.tsx index 095f6ce65c9b3..95f63266d4778 100644 --- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.tsx @@ -29,6 +29,7 @@ import type { CreatePackagePolicyRouteState, AgentPolicyDetailsDeployAgentAction, } from '@kbn/fleet-plugin/public'; +import { EndpointAgentStatus } from '../../../../common/components/endpoint/endpoint_agent_status'; import { EndpointDetailsFlyout } from './details'; import * as selectors from '../store/selectors'; import { useEndpointSelector } from './hooks'; @@ -60,7 +61,6 @@ import { AdminSearchBar } from './components/search_bar'; import { AdministrationListPage } from '../../../components/administration_list_page'; import { LinkToApp } from '../../../../common/components/endpoint/link_to_app'; import { TableRowActions } from './components/table_row_actions'; -import { EndpointAgentStatus } from './components/endpoint_agent_status'; import { CallOut } from '../../../../common/components/callouts'; import { metadataTransformPrefix } from '../../../../../common/endpoint/constants'; import { WARNING_TRANSFORM_STATES, APP_UI_ID } from '../../../../../common/constants'; @@ -69,6 +69,7 @@ import { BackToExternalAppButton } from '../../../components/back_to_external_ap import { ManagementEmptyStateWrapper } from '../../../components/management_empty_state_wrapper'; import { useUserPrivileges } from '../../../../common/components/user_privileges'; import { useKibana } from '../../../../common/lib/kibana'; +import { getEndpointPendingActionsCallback } from '../store/selectors'; const MAX_PAGINATED_ITEM = 9999; const TRANSFORM_URL = '/data/transform'; @@ -127,6 +128,7 @@ export const EndpointList = () => { patternsError, metadataTransformStats, } = useEndpointSelector(selector); + const getHostPendingActions = useEndpointSelector(getEndpointPendingActionsCallback); const { canReadEndpointList, canAccessFleet, @@ -370,7 +372,11 @@ export const EndpointList = () => { }), render: (hostStatus: HostInfo['host_status'], endpointInfo) => { return ( - + ); }, }, @@ -536,7 +542,15 @@ export const EndpointList = () => { ], }, ]; - }, [queryParams, search, getAppUrl, canReadPolicyManagement, backToEndpointList, PAD_LEFT]); + }, [ + queryParams, + search, + getAppUrl, + getHostPendingActions, + canReadPolicyManagement, + backToEndpointList, + PAD_LEFT, + ]); const renderTableOrEmptyState = useMemo(() => { if (endpointsExist) { diff --git a/x-pack/plugins/security_solution/public/overview/components/host_overview/endpoint_overview/index.test.tsx b/x-pack/plugins/security_solution/public/overview/components/host_overview/endpoint_overview/index.test.tsx index d7591ed353fa9..cc24f8b0dbeba 100644 --- a/x-pack/plugins/security_solution/public/overview/components/host_overview/endpoint_overview/index.test.tsx +++ b/x-pack/plugins/security_solution/public/overview/components/host_overview/endpoint_overview/index.test.tsx @@ -16,6 +16,7 @@ import { EndpointOverview } from '.'; import type { EndpointFields } from '../../../../../common/search_strategy/security_solution/hosts'; import { HostPolicyResponseActionStatus } from '../../../../../common/search_strategy/security_solution/hosts'; import { HostStatus } from '../../../../../common/endpoint/types'; +import { EndpointMetadataGenerator } from '../../../../../common/endpoint/data_generators/endpoint_metadata_generator'; jest.mock('../../../../common/lib/kibana'); @@ -44,6 +45,15 @@ describe('EndpointOverview Component', () => { isolation: false, elasticAgentStatus: HostStatus.HEALTHY, pendingActions: {}, + hostInfo: new EndpointMetadataGenerator('seed').generateHostInfo({ + metadata: { + Endpoint: { + state: { + isolation: true, + }, + }, + }, + }), }; }); @@ -52,7 +62,7 @@ describe('EndpointOverview Component', () => { expect(findData.at(0).text()).toEqual(endpointData.endpointPolicy); expect(findData.at(1).text()).toEqual(endpointData.policyStatus); expect(findData.at(2).text()).toContain(endpointData.sensorVersion); // contain because drag adds a space - expect(findData.at(3).text()).toEqual('Healthy'); + expect(findData.at(3).text()).toEqual('HealthyIsolated'); }); test('it renders with null data', () => { diff --git a/x-pack/plugins/security_solution/public/overview/components/host_overview/endpoint_overview/index.tsx b/x-pack/plugins/security_solution/public/overview/components/host_overview/endpoint_overview/index.tsx index 43be986d78500..f62fa5627ebdf 100644 --- a/x-pack/plugins/security_solution/public/overview/components/host_overview/endpoint_overview/index.tsx +++ b/x-pack/plugins/security_solution/public/overview/components/host_overview/endpoint_overview/index.tsx @@ -9,6 +9,7 @@ import { EuiHealth } from '@elastic/eui'; import { getOr } from 'lodash/fp'; import React, { useCallback, useMemo } from 'react'; +import { EndpointAgentStatus } from '../../../../common/components/endpoint/endpoint_agent_status'; import { OverviewDescriptionList } from '../../../../common/components/overview_description_list'; import type { DescriptionList } from '../../../../../common/utility_types'; import { getEmptyTagValue } from '../../../../common/components/empty_value'; @@ -16,8 +17,6 @@ import { DefaultFieldRenderer } from '../../../../timelines/components/field_ren import * as i18n from './translations'; import type { EndpointFields } from '../../../../../common/search_strategy/security_solution/hosts'; import { HostPolicyResponseActionStatus } from '../../../../../common/search_strategy/security_solution/hosts'; -import { AgentStatus } from '../../../../common/components/endpoint/agent_status'; -import { EndpointHostIsolationStatus } from '../../../../common/components/endpoint/host_isolation'; interface Props { contextID?: string; @@ -77,20 +76,11 @@ export const EndpointOverview = React.memo(({ contextID, data }) => { { title: i18n.FLEET_AGENT_STATUS, description: - data != null && data.elasticAgentStatus ? ( - <> - - - + data != null && data.hostInfo ? ( + ) : ( getEmptyTagValue() ), diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/agent_statuses.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/agent_statuses.tsx deleted file mode 100644 index 5ef421d057a4a..0000000000000 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/agent_statuses.tsx +++ /dev/null @@ -1,60 +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 { EuiFlexGroup, EuiFlexItem, EuiText } from '@elastic/eui'; -import { EndpointHostIsolationStatus } from '../../../../../common/components/endpoint/host_isolation'; -import { useHostIsolationStatus } from '../../../../../detections/containers/detection_engine/alerts/use_host_isolation_status'; -import { AgentStatus } from '../../../../../common/components/endpoint/agent_status'; -import { EMPTY_STATUS } from './translations'; - -export const AgentStatuses = React.memo( - ({ - fieldName, - contextId, - eventId, - fieldType, - isAggregatable, - isDraggable, - value, - }: { - fieldName: string; - fieldType: string; - contextId: string; - eventId: string; - isAggregatable: boolean; - isDraggable: boolean; - value: string; - }) => { - const { isIsolated, agentStatus, pendingIsolation, pendingUnisolation } = - useHostIsolationStatus({ agentId: value }); - return ( - - {agentStatus !== undefined ? ( - - - - ) : ( - -

{EMPTY_STATUS}

-
- )} - - - -
- ); - } -); - -AgentStatuses.displayName = 'AgentStatuses'; diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/formatted_field.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/formatted_field.tsx index 1fe2a6b658791..bf216c55f721f 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/formatted_field.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/formatted_field.tsx @@ -12,6 +12,7 @@ import { EuiFlexGroup, EuiFlexItem, EuiToolTip } from '@elastic/eui'; import { isNumber, isEmpty } from 'lodash/fp'; import React from 'react'; +import { EndpointAgentStatusById } from '../../../../../common/components/endpoint/endpoint_agent_status'; import { INDICATOR_REFERENCE } from '../../../../../../common/cti/constants'; import { DefaultDraggable } from '../../../../../common/components/draggables'; import { Bytes, BYTES_FORMAT } from './bytes'; @@ -40,7 +41,6 @@ import { import { RenderRuleName, renderEventModule, renderUrl } from './formatted_field_helpers'; import { RuleStatus } from './rule_status'; import { HostName } from './host_name'; -import { AgentStatuses } from './agent_statuses'; import { UserName } from './user_name'; // simple black-list to prevent dragging and dropping fields such as message name @@ -240,14 +240,9 @@ const FormattedFieldValueComponent: React.FC<{ ); } else if (fieldName === AGENT_STATUS_FIELD_NAME) { return ( - ); } else if ( diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/translations.ts b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/translations.ts index d303fe45bba53..e58a4ceefd46c 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/translations.ts +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/translations.ts @@ -40,16 +40,3 @@ export const LINK_ELASTIC_ENDPOINT_SECURITY = i18n.translate( defaultMessage: 'Open in Endpoint Security', } ); - -export const EMPTY_STATUS = i18n.translate( - 'xpack.securitySolution.hostIsolation.agentStatuses.empty', - { - defaultMessage: '-', - } -); - -export const REASON_RENDERER_TITLE = (eventRendererName: string) => - i18n.translate('xpack.securitySolution.event.reason.reasonRendererTitle', { - values: { eventRendererName }, - defaultMessage: 'Event renderer: {eventRendererName} ', - }); diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/details/helpers.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/details/helpers.ts index aa07acd20c896..66f36a9052bb5 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/details/helpers.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/details/helpers.ts @@ -201,6 +201,7 @@ export const getHostEndpoint = async ( : {}; return { + hostInfo: endpointData, endpointPolicy: endpointData.metadata.Endpoint.policy.applied.name, policyStatus: endpointData.metadata.Endpoint.policy.applied.status, sensorVersion: endpointData.metadata.agent.version, diff --git a/x-pack/plugins/translations/translations/fr-FR.json b/x-pack/plugins/translations/translations/fr-FR.json index 05a0176af89d8..72cfaa355835f 100644 --- a/x-pack/plugins/translations/translations/fr-FR.json +++ b/x-pack/plugins/translations/translations/fr-FR.json @@ -27987,7 +27987,6 @@ "xpack.securitySolution.endpoint.hostIsolation.successfulIsolation.cases": "Cette action a été attachée {caseCount, plural, one {au cas suivant} other {aux cas suivants}} :", "xpack.securitySolution.endpoint.hostIsolation.unisolate.successfulMessage": "La libération de l'hôte {hostName} a été soumise avec succès", "xpack.securitySolution.endpoint.hostIsolation.unIsolateThisHost": "{hostName} est actuellement {isolated}. Voulez-vous vraiment {unisolate} cet hôte ?", - "xpack.securitySolution.endpoint.hostIsolationStatus.multiplePendingActions": "{count} {count, plural, one {action} other {actions}} en attente", "xpack.securitySolution.endpoint.list.hostStatusValue": "{hostStatus, select, healthy {Sain} unhealthy {Défectueux} updating {En cours de mise à jour} offline {Hors ligne} inactive {Inactif} unenrolled {Désinscrit} other {Défectueux}}", "xpack.securitySolution.endpoint.list.policy.revisionNumber": "rév. {revNumber}", "xpack.securitySolution.endpoint.list.totalCount": "Affichage de {totalItemCount, plural, one {# point de terminaison} other {# points de terminaison}}", @@ -28063,7 +28062,6 @@ "xpack.securitySolution.entityAnalytics.riskDashboard.nameTitle": "Nom de {riskEntity}", "xpack.securitySolution.entityAnalytics.riskDashboard.riskClassificationTitle": "Classification de risque de {riskEntity}", "xpack.securitySolution.entityAnalytics.riskDashboard.riskToolTip": "La classification de risque de {riskEntity} est déterminée par le score de risque de {riskEntityLowercase}. Les {riskEntity} classées comme Critique ou Élevée sont indiquées comme étant à risque.", - "xpack.securitySolution.event.reason.reasonRendererTitle": "Outils de rendu d'événement : {eventRendererName} ", "xpack.securitySolution.eventDetails.nestedColumnCheckboxAriaLabel": "Le champ {field} est un objet, et il est composé de champs imbriqués qui peuvent être ajoutés en tant que colonne", "xpack.securitySolution.eventDetails.viewColumnCheckboxAriaLabel": "Afficher la colonne {field}", "xpack.securitySolution.eventFilter.flyoutForm.creationSuccessToastTitle": "\"{name}\" a été ajouté à la liste de filtres d'événements.", @@ -30387,15 +30385,6 @@ "xpack.securitySolution.endpoint.hostisolation.unisolate": "libération", "xpack.securitySolution.endpoint.hostIsolation.unisolateHost": "Libérer l'hôte", "xpack.securitySolution.endpoint.hostIsolationExceptions.fleetIntegration.title": "Exceptions d'isolation de l'hôte", - "xpack.securitySolution.endpoint.hostIsolationStatus.isIsolating": "Isolation", - "xpack.securitySolution.endpoint.hostIsolationStatus.isolated": "Isolé", - "xpack.securitySolution.endpoint.hostIsolationStatus.isUnIsolating": "Libération", - "xpack.securitySolution.endpoint.hostIsolationStatus.tooltipPendingActions": "Actions en attente :", - "xpack.securitySolution.endpoint.hostIsolationStatus.tooltipPendingIsolate": "Isoler", - "xpack.securitySolution.endpoint.hostIsolationStatus.tooltipPendingKillProcess": "Arrêter le processus", - "xpack.securitySolution.endpoint.hostIsolationStatus.tooltipPendingRunningProcesses": "Processus", - "xpack.securitySolution.endpoint.hostIsolationStatus.tooltipPendingSuspendProcess": "Suspendre le processus", - "xpack.securitySolution.endpoint.hostIsolationStatus.tooltipPendingUnIsolate": "Libération", "xpack.securitySolution.endpoint.ingestManager.createPackagePolicy.environments": "protéger vos points de terminaison traditionnels ou vos environnements cloud dynamiques", "xpack.securitySolution.endpoint.ingestManager.createPackagePolicy.seeDocumentationLink": "documentation", "xpack.securitySolution.endpoint.list.actionmenu": "Ouvrir", @@ -31211,7 +31200,6 @@ "xpack.securitySolution.host.details.overview.platformTitle": "Plateforme", "xpack.securitySolution.host.details.overview.regionTitle": "Région", "xpack.securitySolution.host.details.versionLabel": "Version", - "xpack.securitySolution.hostIsolation.agentStatuses.empty": "-", "xpack.securitySolution.hostIsolationExceptions.cardActionDeleteLabel": "Supprimer l'exception", "xpack.securitySolution.hostIsolationExceptions.cardActionEditLabel": "Modifier l'exception", "xpack.securitySolution.hostIsolationExceptions.deleteModtalTitle": "Supprimer l'exception d'isolation de l'hôte", diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 6eaa5a4584e7d..5636aeac2d32d 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -27966,7 +27966,6 @@ "xpack.securitySolution.endpoint.hostIsolation.successfulIsolation.cases": "このアクションは次の{caseCount, plural, other {ケース}}に関連付けられました:", "xpack.securitySolution.endpoint.hostIsolation.unisolate.successfulMessage": "ホスト{hostName}でのリリースは正常に送信されました", "xpack.securitySolution.endpoint.hostIsolation.unIsolateThisHost": "{hostName}は現在{isolated}されています。このホストを{unisolate}しますか?", - "xpack.securitySolution.endpoint.hostIsolationStatus.multiplePendingActions": "{count}個の{count, plural, other {アクション}}が保留中", "xpack.securitySolution.endpoint.list.hostStatusValue": "{hostStatus, select, healthy {正常} unhealthy {異常} updating {更新中} offline {オフライン} inactive {非アクティブ} unenrolled {登録解除済み} other {異常}}", "xpack.securitySolution.endpoint.list.policy.revisionNumber": "rev. {revNumber}", "xpack.securitySolution.endpoint.list.totalCount": "{totalItemCount, plural, other {#個のエンドポイント}}を表示中", @@ -28042,7 +28041,6 @@ "xpack.securitySolution.entityAnalytics.riskDashboard.nameTitle": "{riskEntity}名", "xpack.securitySolution.entityAnalytics.riskDashboard.riskClassificationTitle": "{riskEntity}リスク分類", "xpack.securitySolution.entityAnalytics.riskDashboard.riskToolTip": "{riskEntity}リスク分類は、{riskEntityLowercase}リスクスコアによって決定されます。「重大」または「高」に分類された{riskEntity}は、リスクが高いことが表示されます。", - "xpack.securitySolution.event.reason.reasonRendererTitle": "イベントレンダラー:{eventRendererName} ", "xpack.securitySolution.eventDetails.nestedColumnCheckboxAriaLabel": "{field}フィールドはオブジェクトであり、列として追加できるネストされたフィールドに分解されます", "xpack.securitySolution.eventDetails.viewColumnCheckboxAriaLabel": "{field}列を表示", "xpack.securitySolution.eventFilter.flyoutForm.creationSuccessToastTitle": "\"{name}\"がイベントフィルターリストに追加されました。", @@ -30366,15 +30364,6 @@ "xpack.securitySolution.endpoint.hostisolation.unisolate": "リリース", "xpack.securitySolution.endpoint.hostIsolation.unisolateHost": "ホストのリリース", "xpack.securitySolution.endpoint.hostIsolationExceptions.fleetIntegration.title": "ホスト分離例外", - "xpack.securitySolution.endpoint.hostIsolationStatus.isIsolating": "分離中", - "xpack.securitySolution.endpoint.hostIsolationStatus.isolated": "分離済み", - "xpack.securitySolution.endpoint.hostIsolationStatus.isUnIsolating": "リリース中", - "xpack.securitySolution.endpoint.hostIsolationStatus.tooltipPendingActions": "保留中のアクション:", - "xpack.securitySolution.endpoint.hostIsolationStatus.tooltipPendingIsolate": "分離", - "xpack.securitySolution.endpoint.hostIsolationStatus.tooltipPendingKillProcess": "プロセスを終了", - "xpack.securitySolution.endpoint.hostIsolationStatus.tooltipPendingRunningProcesses": "プロセス", - "xpack.securitySolution.endpoint.hostIsolationStatus.tooltipPendingSuspendProcess": "プロセスを一時停止", - "xpack.securitySolution.endpoint.hostIsolationStatus.tooltipPendingUnIsolate": "リリース", "xpack.securitySolution.endpoint.ingestManager.createPackagePolicy.environments": "従来のエンドポイントや動的クラウド環境を保護", "xpack.securitySolution.endpoint.ingestManager.createPackagePolicy.seeDocumentationLink": "ドキュメンテーション", "xpack.securitySolution.endpoint.list.actionmenu": "開く", @@ -31190,7 +31179,6 @@ "xpack.securitySolution.host.details.overview.platformTitle": "プラットフォーム", "xpack.securitySolution.host.details.overview.regionTitle": "地域", "xpack.securitySolution.host.details.versionLabel": "バージョン", - "xpack.securitySolution.hostIsolation.agentStatuses.empty": "-", "xpack.securitySolution.hostIsolationExceptions.cardActionDeleteLabel": "例外の削除", "xpack.securitySolution.hostIsolationExceptions.cardActionEditLabel": "例外の編集", "xpack.securitySolution.hostIsolationExceptions.deleteModtalTitle": "ホスト分離例外を削除", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index fb3cdb9c46c01..28b56ee5b92ff 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -27982,7 +27982,6 @@ "xpack.securitySolution.endpoint.hostIsolation.successfulIsolation.cases": "此操作已附加到以下{caseCount, plural, other {案例}}:", "xpack.securitySolution.endpoint.hostIsolation.unisolate.successfulMessage": "已成功提交主机 {hostName} 的释放", "xpack.securitySolution.endpoint.hostIsolation.unIsolateThisHost": "{hostName} 当前 {isolated}。是否确定要{unisolate}此主机?", - "xpack.securitySolution.endpoint.hostIsolationStatus.multiplePendingActions": "{count} 个{count, plural, other {操作}}未决", "xpack.securitySolution.endpoint.list.hostStatusValue": "{hostStatus, select, healthy {运行正常} unhealthy {运行不正常} updating {正在更新} offline {脱机} inactive {非活动} unenrolled {未注册} other {运行不正常}}", "xpack.securitySolution.endpoint.list.policy.revisionNumber": "rev. {revNumber}", "xpack.securitySolution.endpoint.list.totalCount": "正在显示 {totalItemCount, plural, other {# 个终端}}", @@ -28058,7 +28057,6 @@ "xpack.securitySolution.entityAnalytics.riskDashboard.nameTitle": "{riskEntity} 名称", "xpack.securitySolution.entityAnalytics.riskDashboard.riskClassificationTitle": "{riskEntity} 风险分类", "xpack.securitySolution.entityAnalytics.riskDashboard.riskToolTip": "{riskEntity} 风险分类由 {riskEntityLowercase} 风险分数决定。分类为紧急或高的{riskEntity}主机即表示存在风险。", - "xpack.securitySolution.event.reason.reasonRendererTitle": "事件呈现器:{eventRendererName}", "xpack.securitySolution.eventDetails.nestedColumnCheckboxAriaLabel": "{field} 字段是对象,并分解为可以添加为列的嵌套字段", "xpack.securitySolution.eventDetails.viewColumnCheckboxAriaLabel": "查看 {field} 列", "xpack.securitySolution.eventFilter.flyoutForm.creationSuccessToastTitle": "“{name}”已添加到事件筛选列表。", @@ -30382,15 +30380,6 @@ "xpack.securitySolution.endpoint.hostisolation.unisolate": "释放", "xpack.securitySolution.endpoint.hostIsolation.unisolateHost": "释放主机", "xpack.securitySolution.endpoint.hostIsolationExceptions.fleetIntegration.title": "主机隔离例外", - "xpack.securitySolution.endpoint.hostIsolationStatus.isIsolating": "正在隔离", - "xpack.securitySolution.endpoint.hostIsolationStatus.isolated": "已隔离", - "xpack.securitySolution.endpoint.hostIsolationStatus.isUnIsolating": "正在释放", - "xpack.securitySolution.endpoint.hostIsolationStatus.tooltipPendingActions": "未决操作:", - "xpack.securitySolution.endpoint.hostIsolationStatus.tooltipPendingIsolate": "隔离", - "xpack.securitySolution.endpoint.hostIsolationStatus.tooltipPendingKillProcess": "结束进程", - "xpack.securitySolution.endpoint.hostIsolationStatus.tooltipPendingRunningProcesses": "进程", - "xpack.securitySolution.endpoint.hostIsolationStatus.tooltipPendingSuspendProcess": "挂起进程", - "xpack.securitySolution.endpoint.hostIsolationStatus.tooltipPendingUnIsolate": "释放", "xpack.securitySolution.endpoint.ingestManager.createPackagePolicy.environments": "保护您的传统终端或动态云环境", "xpack.securitySolution.endpoint.ingestManager.createPackagePolicy.seeDocumentationLink": "文档", "xpack.securitySolution.endpoint.list.actionmenu": "打开", @@ -31206,7 +31195,6 @@ "xpack.securitySolution.host.details.overview.platformTitle": "平台", "xpack.securitySolution.host.details.overview.regionTitle": "地区", "xpack.securitySolution.host.details.versionLabel": "版本", - "xpack.securitySolution.hostIsolation.agentStatuses.empty": "-", "xpack.securitySolution.hostIsolationExceptions.cardActionDeleteLabel": "删除例外", "xpack.securitySolution.hostIsolationExceptions.cardActionEditLabel": "编辑例外", "xpack.securitySolution.hostIsolationExceptions.deleteModtalTitle": "删除主机隔离例外", From 14f01672c7643c76ab7a4549cae9cc05aacde229 Mon Sep 17 00:00:00 2001 From: Jiawei Wu <74562234+JiaweiWu@users.noreply.github.com> Date: Wed, 19 Apr 2023 08:48:23 -0700 Subject: [PATCH 51/78] [RAM] Maintenance Window Task Runner Integration + New AAD/Event Log Fields (#154761) ## Summary Resolves: https://github.com/elastic/kibana/issues/153468 Maintenance window API PR: https://github.com/elastic/kibana/pull/153411 This PR does the following: - Skip alert notifications for rules in maintenance - Add `maintenance_window_ids` field to alert events in the event log - Add `maintenance_window_ids` attribute to AAD ### 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: kibanamachine <42973632+kibanamachine@users.noreply.github.com> --- .../src/field_maps/alert_field_map.ts | 6 + .../src/default_alerts_as_data.ts | 5 + .../src/technical_field_names.ts | 2 + .../field_maps/mapping_from_field_map.test.ts | 3 + .../legacy_alerts_client.test.ts | 2 + .../alerts_client/legacy_alerts_client.ts | 3 + .../alerting_event_logger.test.ts | 2 + .../alerting_event_logger.ts | 2 + ...eate_alert_event_log_record_object.test.ts | 8 + .../create_alert_event_log_record_object.ts | 3 + .../server/maintenance_window_client.mock.ts | 2 +- x-pack/plugins/alerting/server/plugin.ts | 5 + .../alerting/server/task_runner/fixtures.ts | 9 +- .../server/task_runner/log_alerts.test.ts | 83 +++++ .../alerting/server/task_runner/log_alerts.ts | 5 + .../server/task_runner/task_runner.test.ts | 104 ++++++ .../server/task_runner/task_runner.ts | 23 ++ .../task_runner/task_runner_cancel.test.ts | 7 + .../task_runner/task_runner_factory.test.ts | 4 + .../server/task_runner/task_runner_factory.ts | 2 + x-pack/plugins/alerting/server/types.ts | 1 + .../plugins/event_log/generated/mappings.json | 7 + x-pack/plugins/event_log/generated/schemas.ts | 1 + x-pack/plugins/event_log/scripts/mappings.js | 5 + .../technical_rule_field_map.test.ts | 5 + .../utils/create_lifecycle_executor.test.ts | 327 ++++++++++++++++++ .../server/utils/create_lifecycle_executor.ts | 5 + .../utils/rule_executor.test_helpers.ts | 3 + .../tests/alerting/group1/event_log.ts | 123 +++++++ .../tests/trial/get_summarized_alerts.ts | 137 ++++++++ 30 files changed, 892 insertions(+), 2 deletions(-) diff --git a/packages/kbn-alerts-as-data-utils/src/field_maps/alert_field_map.ts b/packages/kbn-alerts-as-data-utils/src/field_maps/alert_field_map.ts index 72fe68fa9cf59..91f51ea3acffc 100644 --- a/packages/kbn-alerts-as-data-utils/src/field_maps/alert_field_map.ts +++ b/packages/kbn-alerts-as-data-utils/src/field_maps/alert_field_map.ts @@ -13,6 +13,7 @@ import { ALERT_END, ALERT_FLAPPING, ALERT_FLAPPING_HISTORY, + ALERT_MAINTENANCE_WINDOW_IDS, ALERT_INSTANCE_ID, ALERT_LAST_DETECTED, ALERT_REASON, @@ -67,6 +68,11 @@ export const alertFieldMap = { array: true, required: false, }, + [ALERT_MAINTENANCE_WINDOW_IDS]: { + type: 'keyword', + array: true, + required: false, + }, [ALERT_INSTANCE_ID]: { type: 'keyword', array: false, diff --git a/packages/kbn-rule-data-utils/src/default_alerts_as_data.ts b/packages/kbn-rule-data-utils/src/default_alerts_as_data.ts index e996acbd9fb6c..98ea2d76730c2 100644 --- a/packages/kbn-rule-data-utils/src/default_alerts_as_data.ts +++ b/packages/kbn-rule-data-utils/src/default_alerts_as_data.ts @@ -40,6 +40,9 @@ const ALERT_FLAPPING = `${ALERT_NAMESPACE}.flapping` as const; // kibana.alert.flapping_history - whether the alert is currently in a flapping state const ALERT_FLAPPING_HISTORY = `${ALERT_NAMESPACE}.flapping_history` as const; +// kibana.alert.maintenance_window_ids - IDs of maintenance windows that are affecting this alert +const ALERT_MAINTENANCE_WINDOW_IDS = `${ALERT_NAMESPACE}.maintenance_window_ids` as const; + // kibana.alert.instance.id - alert ID, also known as alert instance ID const ALERT_INSTANCE_ID = `${ALERT_NAMESPACE}.instance.id` as const; @@ -107,6 +110,7 @@ const fields = { ALERT_END, ALERT_FLAPPING, ALERT_FLAPPING_HISTORY, + ALERT_MAINTENANCE_WINDOW_IDS, ALERT_INSTANCE_ID, ALERT_LAST_DETECTED, ALERT_REASON, @@ -143,6 +147,7 @@ export { ALERT_END, ALERT_FLAPPING, ALERT_FLAPPING_HISTORY, + ALERT_MAINTENANCE_WINDOW_IDS, ALERT_INSTANCE_ID, ALERT_LAST_DETECTED, ALERT_REASON, diff --git a/packages/kbn-rule-data-utils/src/technical_field_names.ts b/packages/kbn-rule-data-utils/src/technical_field_names.ts index cf45162b20853..a5a39d7ef33b1 100644 --- a/packages/kbn-rule-data-utils/src/technical_field_names.ts +++ b/packages/kbn-rule-data-utils/src/technical_field_names.ts @@ -16,6 +16,7 @@ import { ALERT_DURATION, ALERT_END, ALERT_FLAPPING, + ALERT_MAINTENANCE_WINDOW_IDS, ALERT_INSTANCE_ID, ALERT_REASON, ALERT_RULE_CATEGORY, @@ -125,6 +126,7 @@ const fields = { ALERT_EVALUATION_THRESHOLD, ALERT_EVALUATION_VALUE, ALERT_FLAPPING, + ALERT_MAINTENANCE_WINDOW_IDS, ALERT_INSTANCE_ID, ALERT_RULE_CONSUMER, ALERT_RULE_PRODUCER, diff --git a/x-pack/plugins/alerting/common/alert_schema/field_maps/mapping_from_field_map.test.ts b/x-pack/plugins/alerting/common/alert_schema/field_maps/mapping_from_field_map.test.ts index 4456bd363a016..7a86a45dcd045 100644 --- a/x-pack/plugins/alerting/common/alert_schema/field_maps/mapping_from_field_map.test.ts +++ b/x-pack/plugins/alerting/common/alert_schema/field_maps/mapping_from_field_map.test.ts @@ -216,6 +216,9 @@ describe('mappingFromFieldMap', () => { flapping_history: { type: 'boolean', }, + maintenance_window_ids: { + type: 'keyword', + }, instance: { properties: { id: { diff --git a/x-pack/plugins/alerting/server/alerts_client/legacy_alerts_client.test.ts b/x-pack/plugins/alerting/server/alerts_client/legacy_alerts_client.test.ts index 3fb1877756d15..59668072632b4 100644 --- a/x-pack/plugins/alerting/server/alerts_client/legacy_alerts_client.test.ts +++ b/x-pack/plugins/alerting/server/alerts_client/legacy_alerts_client.test.ts @@ -236,6 +236,7 @@ describe('Legacy Alerts Client', () => { shouldLogAndScheduleActionsForAlerts: true, flappingSettings: DEFAULT_FLAPPING_SETTINGS, notifyWhen: RuleNotifyWhen.CHANGE, + maintenanceWindowIds: ['window-id1', 'window-id2'], }); expect(processAlerts).toHaveBeenCalledWith({ @@ -284,6 +285,7 @@ describe('Legacy Alerts Client', () => { ruleRunMetricsStore, canSetRecoveryContext: false, shouldPersistAlerts: true, + maintenanceWindowIds: ['window-id1', 'window-id2'], }); expect(alertsClient.getProcessedAlerts('active')).toEqual({ 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 8fce6782cd2f1..b06ca4ffada79 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 @@ -117,6 +117,7 @@ export class LegacyAlertsClient< shouldLogAndScheduleActionsForAlerts, flappingSettings, notifyWhen, + maintenanceWindowIds, }: { eventLogger: AlertingEventLogger; ruleLabel: string; @@ -124,6 +125,7 @@ export class LegacyAlertsClient< ruleRunMetricsStore: RuleRunMetricsStore; flappingSettings: RulesSettingsFlappingProperties; notifyWhen: RuleNotifyWhenType | null; + maintenanceWindowIds?: string[]; }) { const { newAlerts: processedAlertsNew, @@ -176,6 +178,7 @@ export class LegacyAlertsClient< ruleRunMetricsStore, canSetRecoveryContext: this.options.ruleType.doesSetRecoveryContext ?? false, shouldPersistAlerts: shouldLogAndScheduleActionsForAlerts, + maintenanceWindowIds, }); } 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 90c3645e151e5..0eba6610754f3 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 @@ -68,6 +68,7 @@ const alert = { duration: '2343252346', }, flapping: false, + maintenanceWindowIds: ['window-id1', 'window-id2'], }; const action = { @@ -1068,6 +1069,7 @@ describe('createAlertRecord', () => { expect(record.kibana?.alert?.rule?.rule_type_id).toEqual(contextWithName.ruleType.id); expect(record.kibana?.alert?.rule?.consumer).toEqual(contextWithName.consumer); expect(record.kibana?.alert?.rule?.execution?.uuid).toEqual(contextWithName.executionId); + expect(record.kibana?.alert?.maintenance_window_ids).toEqual(alert.maintenanceWindowIds); expect(record.kibana?.alerting?.instance_id).toEqual(alert.id); expect(record.kibana?.alerting?.action_group_id).toEqual(alert.group); expect(record.kibana?.saved_objects).toEqual([ 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 f84936e7ea6e4..37029b4c96703 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 @@ -50,6 +50,7 @@ interface AlertOpts { group?: string; state?: AlertInstanceState; flapping: boolean; + maintenanceWindowIds?: string[]; } interface ActionOpts { @@ -256,6 +257,7 @@ export function createAlertRecord(context: RuleContextOpts, alert: AlertOpts) { ], ruleName: context.ruleName, flapping: alert.flapping, + maintenanceWindowIds: alert.maintenanceWindowIds, }); } diff --git a/x-pack/plugins/alerting/server/lib/create_alert_event_log_record_object.test.ts b/x-pack/plugins/alerting/server/lib/create_alert_event_log_record_object.test.ts index 185aa098a49d7..e7d77e55b9e4a 100644 --- a/x-pack/plugins/alerting/server/lib/create_alert_event_log_record_object.test.ts +++ b/x-pack/plugins/alerting/server/lib/create_alert_event_log_record_object.test.ts @@ -9,6 +9,8 @@ import { createAlertEventLogRecordObject } from './create_alert_event_log_record import { UntypedNormalizedRuleType } from '../rule_type_registry'; import { RecoveredActionGroup } from '../types'; +const MAINTENANCE_WINDOW_IDS = ['test-1', 'test-2']; + describe('createAlertEventLogRecordObject', () => { const ruleType: jest.Mocked = { id: 'test', @@ -44,6 +46,7 @@ describe('createAlertEventLogRecordObject', () => { }, ], spaceId: 'default', + maintenanceWindowIds: MAINTENANCE_WINDOW_IDS, }) ).toStrictEqual({ '@timestamp': '1970-01-01T00:00:00.000Z', @@ -61,6 +64,7 @@ describe('createAlertEventLogRecordObject', () => { }, rule_type_id: 'test', }, + maintenance_window_ids: MAINTENANCE_WINDOW_IDS, }, saved_objects: [ { @@ -113,6 +117,7 @@ describe('createAlertEventLogRecordObject', () => { }, ], spaceId: 'default', + maintenanceWindowIds: MAINTENANCE_WINDOW_IDS, }) ).toStrictEqual({ event: { @@ -132,6 +137,7 @@ describe('createAlertEventLogRecordObject', () => { }, rule_type_id: 'test', }, + maintenance_window_ids: MAINTENANCE_WINDOW_IDS, }, alerting: { action_group_id: 'group 1', @@ -196,6 +202,7 @@ describe('createAlertEventLogRecordObject', () => { ongoing: 3, recovered: 1, }, + maintenanceWindowIds: MAINTENANCE_WINDOW_IDS, }) ).toStrictEqual({ event: { @@ -215,6 +222,7 @@ describe('createAlertEventLogRecordObject', () => { }, rule_type_id: 'test', }, + maintenance_window_ids: MAINTENANCE_WINDOW_IDS, }, alerting: { action_group_id: 'group 1', diff --git a/x-pack/plugins/alerting/server/lib/create_alert_event_log_record_object.ts b/x-pack/plugins/alerting/server/lib/create_alert_event_log_record_object.ts index f987c297d0217..97284f0ce9f78 100644 --- a/x-pack/plugins/alerting/server/lib/create_alert_event_log_record_object.ts +++ b/x-pack/plugins/alerting/server/lib/create_alert_event_log_record_object.ts @@ -42,6 +42,7 @@ interface CreateAlertEventLogRecordParams { ongoing: number; recovered: number; }; + maintenanceWindowIds?: string[]; } export function createAlertEventLogRecordObject(params: CreateAlertEventLogRecordParams): Event { @@ -60,6 +61,7 @@ export function createAlertEventLogRecordObject(params: CreateAlertEventLogRecor flapping, alertUuid, alertSummary, + maintenanceWindowIds, } = params; const alerting = params.instanceId || group || alertSummary @@ -92,6 +94,7 @@ export function createAlertEventLogRecordObject(params: CreateAlertEventLogRecor kibana: { alert: { ...(flapping !== undefined ? { flapping } : {}), + ...(maintenanceWindowIds ? { maintenance_window_ids: maintenanceWindowIds } : {}), ...(alertUuid ? { uuid: alertUuid } : {}), rule: { rule_type_id: ruleType.id, diff --git a/x-pack/plugins/alerting/server/maintenance_window_client.mock.ts b/x-pack/plugins/alerting/server/maintenance_window_client.mock.ts index 9d0157d8e2139..cda36a816f41c 100644 --- a/x-pack/plugins/alerting/server/maintenance_window_client.mock.ts +++ b/x-pack/plugins/alerting/server/maintenance_window_client.mock.ts @@ -17,7 +17,7 @@ const createMaintenanceWindowClientMock = () => { find: jest.fn(), get: jest.fn(), archive: jest.fn(), - getActiveMaintenanceWindows: jest.fn(), + getActiveMaintenanceWindows: jest.fn().mockResolvedValue([]), finish: jest.fn(), delete: jest.fn(), }; diff --git a/x-pack/plugins/alerting/server/plugin.ts b/x-pack/plugins/alerting/server/plugin.ts index 2902848b6dea5..e5e7aaa8f8610 100644 --- a/x-pack/plugins/alerting/server/plugin.ts +++ b/x-pack/plugins/alerting/server/plugin.ts @@ -502,6 +502,10 @@ export class AlertingPlugin { return rulesSettingsClientFactory!.create(request); }; + const getMaintenanceWindowClientWithRequest = (request: KibanaRequest) => { + return maintenanceWindowClientFactory!.create(request); + }; + taskRunnerFactory.initialize({ logger, data: plugins.data, @@ -528,6 +532,7 @@ export class AlertingPlugin { actionsConfigMap: getActionsConfigMap(this.config.rules.run.actions), usageCounter: this.usageCounter, getRulesSettingsClientWithRequest, + getMaintenanceWindowClientWithRequest, }); this.eventLogService!.registerSavedObjectProvider('alert', (request) => { diff --git a/x-pack/plugins/alerting/server/task_runner/fixtures.ts b/x-pack/plugins/alerting/server/task_runner/fixtures.ts index dacddea634fa4..64db706d824f7 100644 --- a/x-pack/plugins/alerting/server/task_runner/fixtures.ts +++ b/x-pack/plugins/alerting/server/task_runner/fixtures.ts @@ -233,7 +233,13 @@ export const mockTaskInstance = () => ({ ownerId: null, }); -export const generateAlertOpts = ({ action, group, state, id }: GeneratorParams = {}) => { +export const generateAlertOpts = ({ + action, + group, + state, + id, + maintenanceWindowIds = [], +}: GeneratorParams = {}) => { id = id ?? '1'; let message: string = ''; switch (action) { @@ -255,6 +261,7 @@ export const generateAlertOpts = ({ action, group, state, id }: GeneratorParams state, ...(group ? { group } : {}), flapping: false, + maintenanceWindowIds, }; }; diff --git a/x-pack/plugins/alerting/server/task_runner/log_alerts.test.ts b/x-pack/plugins/alerting/server/task_runner/log_alerts.test.ts index ffeb5db16fed3..18379740e5feb 100644 --- a/x-pack/plugins/alerting/server/task_runner/log_alerts.test.ts +++ b/x-pack/plugins/alerting/server/task_runner/log_alerts.test.ts @@ -362,4 +362,87 @@ describe('logAlerts', () => { uuid: expect.any(String), }); }); + + test('should correctly set maintenance window in ruleRunMetricsStore and call alertingEventLogger.logAlert', () => { + jest.clearAllMocks(); + const MAINTENANCE_WINDOW_IDS = ['window-id-1', 'window-id-2']; + + logAlerts({ + logger, + alertingEventLogger, + newAlerts: { + '4': new Alert<{}, {}, DefaultActionGroupId>('4'), + }, + activeAlerts: { + '1': new Alert<{}, {}, DefaultActionGroupId>('1'), + '4': new Alert<{}, {}, DefaultActionGroupId>('4'), + }, + recoveredAlerts: { + '7': new Alert<{}, {}, DefaultActionGroupId>('7'), + '8': new Alert<{}, {}, DefaultActionGroupId>('8'), + }, + ruleLogPrefix: `test-rule-type-id:123: 'test rule'`, + ruleRunMetricsStore, + canSetRecoveryContext: false, + shouldPersistAlerts: true, + maintenanceWindowIds: MAINTENANCE_WINDOW_IDS, + }); + + expect(ruleRunMetricsStore.getNumberOfNewAlerts()).toEqual(1); + expect(ruleRunMetricsStore.getNumberOfActiveAlerts()).toEqual(2); + expect(ruleRunMetricsStore.getNumberOfRecoveredAlerts()).toEqual(2); + + expect(alertingEventLogger.logAlert).toHaveBeenCalledTimes(5); + + expect(alertingEventLogger.logAlert).toHaveBeenNthCalledWith(1, { + action: 'recovered-instance', + id: '7', + message: "test-rule-type-id:123: 'test rule' alert '7' has recovered", + state: {}, + flapping: false, + group: undefined, + uuid: expect.any(String), + maintenanceWindowIds: MAINTENANCE_WINDOW_IDS, + }); + expect(alertingEventLogger.logAlert).toHaveBeenNthCalledWith(2, { + action: 'recovered-instance', + id: '8', + message: "test-rule-type-id:123: 'test rule' alert '8' has recovered", + state: {}, + flapping: false, + group: undefined, + uuid: expect.any(String), + maintenanceWindowIds: MAINTENANCE_WINDOW_IDS, + }); + expect(alertingEventLogger.logAlert).toHaveBeenNthCalledWith(3, { + action: 'new-instance', + id: '4', + message: "test-rule-type-id:123: 'test rule' created new alert: '4'", + state: {}, + flapping: false, + group: undefined, + uuid: expect.any(String), + maintenanceWindowIds: MAINTENANCE_WINDOW_IDS, + }); + expect(alertingEventLogger.logAlert).toHaveBeenNthCalledWith(4, { + action: 'active-instance', + id: '1', + message: "test-rule-type-id:123: 'test rule' active alert: '1' in actionGroup: 'undefined'", + state: {}, + flapping: false, + group: undefined, + uuid: expect.any(String), + maintenanceWindowIds: MAINTENANCE_WINDOW_IDS, + }); + expect(alertingEventLogger.logAlert).toHaveBeenNthCalledWith(5, { + action: 'active-instance', + id: '4', + message: "test-rule-type-id:123: 'test rule' active alert: '4' in actionGroup: 'undefined'", + state: {}, + flapping: false, + group: undefined, + uuid: expect.any(String), + maintenanceWindowIds: MAINTENANCE_WINDOW_IDS, + }); + }); }); diff --git a/x-pack/plugins/alerting/server/task_runner/log_alerts.ts b/x-pack/plugins/alerting/server/task_runner/log_alerts.ts index a939ad511da34..8c1427b12b123 100644 --- a/x-pack/plugins/alerting/server/task_runner/log_alerts.ts +++ b/x-pack/plugins/alerting/server/task_runner/log_alerts.ts @@ -28,6 +28,7 @@ export interface LogAlertsParams< ruleRunMetricsStore: RuleRunMetricsStore; canSetRecoveryContext: boolean; shouldPersistAlerts: boolean; + maintenanceWindowIds?: string[]; } export function logAlerts< @@ -45,6 +46,7 @@ export function logAlerts< ruleRunMetricsStore, canSetRecoveryContext, shouldPersistAlerts, + maintenanceWindowIds, }: LogAlertsParams) { const newAlertIds = Object.keys(newAlerts); const activeAlertIds = Object.keys(activeAlerts); @@ -104,6 +106,7 @@ export function logAlerts< message, state, flapping: recoveredAlerts[id].getFlapping(), + maintenanceWindowIds, }); } @@ -121,6 +124,7 @@ export function logAlerts< message, state, flapping: activeAlerts[id].getFlapping(), + maintenanceWindowIds, }); } @@ -138,6 +142,7 @@ export function logAlerts< message, state, flapping: activeAlerts[id].getFlapping(), + maintenanceWindowIds, }); } } 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 d99bc21d5d3d4..ea37e5d8594d6 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 @@ -16,6 +16,7 @@ import { RuleExecutionStatusWarningReasons, Rule, RuleAction, + MaintenanceWindow, } from '../types'; import { ConcreteTaskInstance, isUnrecoverableError } from '@kbn/task-manager-plugin/server'; import { TaskRunnerContext } from './task_runner_factory'; @@ -77,7 +78,9 @@ import { SharePluginStart } from '@kbn/share-plugin/server'; import { dataViewPluginMocks } from '@kbn/data-views-plugin/public/mocks'; import { DataViewsServerPluginStart } from '@kbn/data-views-plugin/server'; import { rulesSettingsClientMock } from '../rules_settings_client.mock'; +import { maintenanceWindowClientMock } from '../maintenance_window_client.mock'; import { alertsServiceMock } from '../alerts_service/alerts_service.mock'; +import { getMockMaintenanceWindow } from '../maintenance_window_client/methods/test_helpers'; jest.mock('uuid', () => ({ v4: () => '5f6aa57d-3e22-484e-bae8-cbed868f4d28', @@ -130,6 +133,7 @@ describe('Task Runner', () => { dataViewsServiceFactory: jest.fn().mockResolvedValue(dataViewPluginMocks.createStartContract()), } as DataViewsServerPluginStart; const alertsService = alertsServiceMock.create(); + const maintenanceWindowClient = maintenanceWindowClientMock.create(); type TaskRunnerFactoryInitializerParamsType = jest.Mocked & { actionsPlugin: jest.Mocked; @@ -167,6 +171,7 @@ describe('Task Runner', () => { }, }, getRulesSettingsClientWithRequest: jest.fn().mockReturnValue(rulesSettingsClientMock.create()), + getMaintenanceWindowClientWithRequest: jest.fn().mockReturnValue(maintenanceWindowClient), }; const ephemeralTestParams: Array< @@ -203,6 +208,7 @@ describe('Task Runner', () => { }); savedObjectsService.getScopedClient.mockReturnValue(services.savedObjectsClient); elasticsearchService.client.asScoped.mockReturnValue(services.scopedClusterClient); + maintenanceWindowClient.getActiveMaintenanceWindows.mockResolvedValue([]); taskRunnerFactoryInitializerParams.getRulesClientWithRequest.mockReturnValue(rulesClient); taskRunnerFactoryInitializerParams.actionsPlugin.getActionsClientWithRequest.mockResolvedValue( actionsClient @@ -217,6 +223,9 @@ describe('Task Runner', () => { taskRunnerFactoryInitializerParams.getRulesSettingsClientWithRequest.mockReturnValue( rulesSettingsClientMock.create() ); + taskRunnerFactoryInitializerParams.getMaintenanceWindowClientWithRequest.mockReturnValue( + maintenanceWindowClient + ); mockedRuleTypeSavedObject.monitoring!.run.history = []; mockedRuleTypeSavedObject.monitoring!.run.calculated_metrics.success_ratio = 0; @@ -602,6 +611,101 @@ describe('Task Runner', () => { } ); + test('skips alert notification if there are active maintenance windows', async () => { + taskRunnerFactoryInitializerParams.actionsPlugin.isActionTypeEnabled.mockReturnValue(true); + taskRunnerFactoryInitializerParams.actionsPlugin.isActionExecutable.mockReturnValue(true); + ruleType.executor.mockImplementation( + async ({ + services: executorServices, + }: RuleExecutorOptions< + RuleTypeParams, + RuleTypeState, + AlertInstanceState, + AlertInstanceContext, + string + >) => { + executorServices.alertFactory.create('1').scheduleActions('default'); + return { state: {} }; + } + ); + const taskRunner = new TaskRunner( + ruleType, + mockedTaskInstance, + taskRunnerFactoryInitializerParams, + inMemoryMetrics + ); + expect(AlertingEventLogger).toHaveBeenCalledTimes(1); + rulesClient.getAlertFromRaw.mockReturnValue(mockedRuleTypeSavedObject as Rule); + maintenanceWindowClient.getActiveMaintenanceWindows.mockResolvedValueOnce([ + { + ...getMockMaintenanceWindow(), + id: 'test-id-1', + } as MaintenanceWindow, + { + ...getMockMaintenanceWindow(), + id: 'test-id-2', + } as MaintenanceWindow, + ]); + + encryptedSavedObjectsClient.getDecryptedAsInternalUser.mockResolvedValue(SAVED_OBJECT); + await taskRunner.run(); + expect(actionsClient.ephemeralEnqueuedExecution).toHaveBeenCalledTimes(0); + + expect(logger.debug).toHaveBeenCalledTimes(7); + expect(logger.debug).nthCalledWith(1, 'executing rule test:1 at 1970-01-01T00:00:00.000Z'); + expect(logger.debug).nthCalledWith( + 2, + `rule test:1: '${RULE_NAME}' has 1 active alerts: [{\"instanceId\":\"1\",\"actionGroup\":\"default\"}]` + ); + expect(logger.debug).nthCalledWith( + 3, + `no scheduling of actions for rule test:1: '${RULE_NAME}': has active maintenance windows test-id-1,test-id-2.` + ); + expect(logger.debug).nthCalledWith( + 4, + 'deprecated ruleRunStatus for test:1: {"lastExecutionDate":"1970-01-01T00:00:00.000Z","status":"active"}' + ); + expect(logger.debug).nthCalledWith( + 5, + 'ruleRunStatus for test:1: {"outcome":"succeeded","outcomeOrder":0,"outcomeMsg":null,"warning":null,"alertsCount":{"active":1,"new":1,"recovered":0,"ignored":0}}' + ); + 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,"triggeredActionsStatus":"complete"}' + ); + expect(logger.debug).nthCalledWith( + 7, + 'Updating rule task for test rule with id 1 - {"lastExecutionDate":"1970-01-01T00:00:00.000Z","status":"active"} - {"outcome":"succeeded","outcomeOrder":0,"outcomeMsg":null,"warning":null,"alertsCount":{"active":1,"new":1,"recovered":0,"ignored":0}}' + ); + + testAlertingEventLogCalls({ + activeAlerts: 1, + newAlerts: 1, + status: 'active', + logAlert: 2, + }); + expect(alertingEventLogger.logAlert).toHaveBeenNthCalledWith( + 1, + generateAlertOpts({ + action: EVENT_LOG_ACTIONS.newInstance, + group: 'default', + state: { start: DATE_1970, duration: '0' }, + maintenanceWindowIds: ['test-id-1', 'test-id-2'], + }) + ); + expect(alertingEventLogger.logAlert).toHaveBeenNthCalledWith( + 2, + generateAlertOpts({ + action: EVENT_LOG_ACTIONS.activeInstance, + group: 'default', + state: { start: DATE_1970, duration: '0' }, + maintenanceWindowIds: ['test-id-1', 'test-id-2'], + }) + ); + + expect(mockUsageCounter.incrementCounter).not.toHaveBeenCalled(); + }); + test.each(ephemeralTestParams)( 'skips firing actions for active alert if alert is muted %s', async (nameExtension, customTaskRunnerFactoryInitializerParams, enqueueFunction) => { 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 466a785de4003..febe4a2eeacb7 100644 --- a/x-pack/plugins/alerting/server/task_runner/task_runner.ts +++ b/x-pack/plugins/alerting/server/task_runner/task_runner.ts @@ -47,6 +47,7 @@ import { parseDuration, RawAlertInstance, RuleLastRunOutcomeOrderMap, + MaintenanceWindow, } from '../../common'; import { NormalizedRuleType, UntypedNormalizedRuleType } from '../rule_type_registry'; import { getEsErrorMessage } from '../lib/errors'; @@ -315,6 +316,22 @@ export class TaskRunner< }); const rulesSettingsClient = this.context.getRulesSettingsClientWithRequest(fakeRequest); const flappingSettings = await rulesSettingsClient.flapping().get(); + const maintenanceWindowClient = this.context.getMaintenanceWindowClientWithRequest(fakeRequest); + + let activeMaintenanceWindows: MaintenanceWindow[] = []; + try { + activeMaintenanceWindows = await maintenanceWindowClient.getActiveMaintenanceWindows({ + interval: rule.schedule.interval, + }); + } catch (err) { + this.logger.error( + `error getting active maintenance window for ${ruleTypeId}:${ruleId} ${err.message}` + ); + } + + const maintenanceWindowIds = activeMaintenanceWindows.map( + (maintenanceWindow) => maintenanceWindow.id + ); const { updatedRuleTypeState } = await this.timer.runWithTimer( TaskRunnerTimerSpan.RuleTypeRun, @@ -397,6 +414,7 @@ export class TaskRunner< }, logger: this.logger, flappingSettings, + ...(maintenanceWindowIds.length ? { maintenanceWindowIds } : {}), }) ); @@ -444,6 +462,7 @@ export class TaskRunner< shouldLogAndScheduleActionsForAlerts: this.shouldLogAndScheduleActionsForAlerts(), flappingSettings, notifyWhen, + maintenanceWindowIds, }); }); @@ -470,6 +489,10 @@ export class TaskRunner< if (isRuleSnoozed(rule)) { this.logger.debug(`no scheduling of actions for rule ${ruleLabel}: rule is snoozed.`); + } else if (maintenanceWindowIds.length) { + this.logger.debug( + `no scheduling of actions for rule ${ruleLabel}: has active maintenance windows ${maintenanceWindowIds}.` + ); } else if (!this.shouldLogAndScheduleActionsForAlerts()) { this.logger.debug( `no scheduling of actions for rule ${ruleLabel}: rule execution has been cancelled.` 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 c02ebe647e697..b43149fc05bcf 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 @@ -54,6 +54,7 @@ import { SharePluginStart } from '@kbn/share-plugin/server'; import { DataViewsServerPluginStart } from '@kbn/data-views-plugin/server'; import { dataViewPluginMocks } from '@kbn/data-views-plugin/public/mocks'; import { rulesSettingsClientMock } from '../rules_settings_client.mock'; +import { maintenanceWindowClientMock } from '../maintenance_window_client.mock'; import { alertsServiceMock } from '../alerts_service/alerts_service.mock'; jest.mock('uuid', () => ({ @@ -143,6 +144,9 @@ describe('Task Runner Cancel', () => { }, }, getRulesSettingsClientWithRequest: jest.fn().mockReturnValue(rulesSettingsClientMock.create()), + getMaintenanceWindowClientWithRequest: jest + .fn() + .mockReturnValue(maintenanceWindowClientMock.create()), }; beforeEach(() => { @@ -173,6 +177,9 @@ describe('Task Runner Cancel', () => { taskRunnerFactoryInitializerParams.getRulesSettingsClientWithRequest.mockReturnValue( rulesSettingsClientMock.create() ); + taskRunnerFactoryInitializerParams.getMaintenanceWindowClientWithRequest.mockReturnValue( + maintenanceWindowClientMock.create() + ); rulesClient.getAlertFromRaw.mockReturnValue(mockedRuleTypeSavedObject as Rule); encryptedSavedObjectsClient.getDecryptedAsInternalUser.mockResolvedValue({ diff --git a/x-pack/plugins/alerting/server/task_runner/task_runner_factory.test.ts b/x-pack/plugins/alerting/server/task_runner/task_runner_factory.test.ts index 61ec6f18a1138..e9c9f244118ab 100644 --- a/x-pack/plugins/alerting/server/task_runner/task_runner_factory.test.ts +++ b/x-pack/plugins/alerting/server/task_runner/task_runner_factory.test.ts @@ -30,6 +30,7 @@ import { SharePluginStart } from '@kbn/share-plugin/server'; import { DataViewsServerPluginStart } from '@kbn/data-views-plugin/server'; import { dataViewPluginMocks } from '@kbn/data-views-plugin/public/mocks'; import { rulesSettingsClientMock } from '../rules_settings_client.mock'; +import { maintenanceWindowClientMock } from '../maintenance_window_client.mock'; import { alertsServiceMock } from '../alerts_service/alerts_service.mock'; const inMemoryMetrics = inMemoryMetricsMock.create(); @@ -120,6 +121,9 @@ describe('Task Runner Factory', () => { }, }, getRulesSettingsClientWithRequest: jest.fn().mockReturnValue(rulesSettingsClientMock.create()), + getMaintenanceWindowClientWithRequest: jest + .fn() + .mockReturnValue(maintenanceWindowClientMock.create()), }; beforeEach(() => { diff --git a/x-pack/plugins/alerting/server/task_runner/task_runner_factory.ts b/x-pack/plugins/alerting/server/task_runner/task_runner_factory.ts index ced3da1ef346d..0299f07ab8de4 100644 --- a/x-pack/plugins/alerting/server/task_runner/task_runner_factory.ts +++ b/x-pack/plugins/alerting/server/task_runner/task_runner_factory.ts @@ -32,6 +32,7 @@ import { AlertInstanceContext, RulesClientApi, RulesSettingsClientApi, + MaintenanceWindowClientApi, } from '../types'; import { TaskRunner } from './task_runner'; import { NormalizedRuleType } from '../rule_type_registry'; @@ -65,6 +66,7 @@ export interface TaskRunnerContext { cancelAlertsOnRuleTimeout: boolean; usageCounter?: UsageCounter; getRulesSettingsClientWithRequest(request: KibanaRequest): RulesSettingsClientApi; + getMaintenanceWindowClientWithRequest(request: KibanaRequest): MaintenanceWindowClientApi; } export class TaskRunnerFactory { diff --git a/x-pack/plugins/alerting/server/types.ts b/x-pack/plugins/alerting/server/types.ts index c578a05c468a7..ee9bf42e5716b 100644 --- a/x-pack/plugins/alerting/server/types.ts +++ b/x-pack/plugins/alerting/server/types.ts @@ -118,6 +118,7 @@ export interface RuleExecutorOptions< state: State; namespace?: string; flappingSettings: RulesSettingsFlappingProperties; + maintenanceWindowIds?: string[]; } export interface RuleParamsAndRefs { diff --git a/x-pack/plugins/event_log/generated/mappings.json b/x-pack/plugins/event_log/generated/mappings.json index c9758df202a3d..f50a1c0ef3321 100644 --- a/x-pack/plugins/event_log/generated/mappings.json +++ b/x-pack/plugins/event_log/generated/mappings.json @@ -304,6 +304,13 @@ "flapping": { "type": "boolean" }, + "maintenance_window_ids": { + "type": "keyword", + "ignore_above": 1024, + "meta": { + "isArray": "true" + } + }, "uuid": { "type": "keyword", "ignore_above": 1024 diff --git a/x-pack/plugins/event_log/generated/schemas.ts b/x-pack/plugins/event_log/generated/schemas.ts index 7471390a7bc88..6706db9635397 100644 --- a/x-pack/plugins/event_log/generated/schemas.ts +++ b/x-pack/plugins/event_log/generated/schemas.ts @@ -140,6 +140,7 @@ export const EventSchema = schema.maybe( alert: schema.maybe( schema.object({ flapping: ecsBoolean(), + maintenance_window_ids: ecsStringMulti(), uuid: ecsString(), rule: schema.maybe( schema.object({ diff --git a/x-pack/plugins/event_log/scripts/mappings.js b/x-pack/plugins/event_log/scripts/mappings.js index 2e236d7b4eff8..7081c321cc659 100644 --- a/x-pack/plugins/event_log/scripts/mappings.js +++ b/x-pack/plugins/event_log/scripts/mappings.js @@ -86,6 +86,10 @@ exports.EcsCustomPropertyMappings = { flapping: { type: 'boolean', }, + maintenance_window_ids: { + type: 'keyword', + ignore_above: 1024, + }, uuid: { type: 'keyword', ignore_above: 1024, @@ -274,4 +278,5 @@ exports.EcsEventLogMultiValuedProperties = [ 'event.type', 'rule.author', 'kibana.space_ids', + 'kibana.alert.maintenance_window_ids', ]; diff --git a/x-pack/plugins/rule_registry/common/assets/field_maps/technical_rule_field_map.test.ts b/x-pack/plugins/rule_registry/common/assets/field_maps/technical_rule_field_map.test.ts index 581c77670a153..b98c94b163445 100644 --- a/x-pack/plugins/rule_registry/common/assets/field_maps/technical_rule_field_map.test.ts +++ b/x-pack/plugins/rule_registry/common/assets/field_maps/technical_rule_field_map.test.ts @@ -72,6 +72,11 @@ it('matches snapshot', () => { "required": false, "type": "date", }, + "kibana.alert.maintenance_window_ids": Object { + "array": true, + "required": false, + "type": "keyword", + }, "kibana.alert.reason": Object { "array": false, "required": false, diff --git a/x-pack/plugins/rule_registry/server/utils/create_lifecycle_executor.test.ts b/x-pack/plugins/rule_registry/server/utils/create_lifecycle_executor.test.ts index 375deb87aa7fd..daaac95f952f1 100644 --- a/x-pack/plugins/rule_registry/server/utils/create_lifecycle_executor.test.ts +++ b/x-pack/plugins/rule_registry/server/utils/create_lifecycle_executor.test.ts @@ -9,6 +9,7 @@ import { loggerMock } from '@kbn/logging-mocks'; import { pick } from 'lodash'; import { ALERT_INSTANCE_ID, + ALERT_MAINTENANCE_WINDOW_IDS, ALERT_RULE_CATEGORY, ALERT_RULE_CONSUMER, ALERT_RULE_NAME, @@ -921,6 +922,332 @@ describe('createLifecycleExecutor', () => { }); }); + describe('set maintenance window ids on the document', () => { + const maintenanceWindowIds = ['test-id-1', 'test-id-2']; + + it('updates documents with maintenance window ids for newly firing alerts', async () => { + const logger = loggerMock.create(); + const ruleDataClientMock = createRuleDataClientMock(); + const executor = createLifecycleExecutor( + logger, + ruleDataClientMock + )<{}, TestRuleState, never, never, never>(async ({ services, state }) => { + services.alertWithLifecycle({ + id: 'TEST_ALERT_0', + fields: { [TAGS]: ['source-tag1', 'source-tag2'] }, + }); + services.alertWithLifecycle({ + id: 'TEST_ALERT_1', + fields: { [TAGS]: ['source-tag3', 'source-tag4'] }, + }); + + return { state }; + }); + + await executor( + createDefaultAlertExecutorOptions({ + params: {}, + state: { wrapped: initialRuleState, trackedAlerts: {}, trackedAlertsRecovered: {} }, + logger, + maintenanceWindowIds, + }) + ); + + expect((await ruleDataClientMock.getWriter()).bulk).toHaveBeenCalledWith( + expect.objectContaining({ + body: [ + // alert documents + { index: { _id: expect.any(String) } }, + expect.objectContaining({ + [ALERT_INSTANCE_ID]: 'TEST_ALERT_0', + [ALERT_STATUS]: ALERT_STATUS_ACTIVE, + [EVENT_ACTION]: 'open', + [EVENT_KIND]: 'signal', + [TAGS]: ['source-tag1', 'source-tag2', 'rule-tag1', 'rule-tag2'], + [ALERT_MAINTENANCE_WINDOW_IDS]: maintenanceWindowIds, + }), + { index: { _id: expect.any(String) } }, + expect.objectContaining({ + [ALERT_INSTANCE_ID]: 'TEST_ALERT_1', + [ALERT_STATUS]: ALERT_STATUS_ACTIVE, + [EVENT_ACTION]: 'open', + [EVENT_KIND]: 'signal', + [TAGS]: ['source-tag3', 'source-tag4', 'rule-tag1', 'rule-tag2'], + [ALERT_MAINTENANCE_WINDOW_IDS]: maintenanceWindowIds, + }), + ], + }) + ); + expect((await ruleDataClientMock.getWriter()).bulk).not.toHaveBeenCalledWith( + expect.objectContaining({ + body: expect.arrayContaining([ + // evaluation documents + { index: {} }, + expect.objectContaining({ + [EVENT_KIND]: 'event', + }), + ]), + }) + ); + }); + + it('updates documents with maintenance window ids for repeatedly firing alerts', async () => { + const logger = loggerMock.create(); + const ruleDataClientMock = createRuleDataClientMock(); + ruleDataClientMock.getReader().search.mockResolvedValue({ + hits: { + hits: [ + { + _source: { + '@timestamp': '', + [ALERT_INSTANCE_ID]: 'TEST_ALERT_0', + [ALERT_UUID]: 'ALERT_0_UUID', + [ALERT_RULE_CATEGORY]: 'RULE_TYPE_NAME', + [ALERT_RULE_CONSUMER]: 'CONSUMER', + [ALERT_RULE_NAME]: 'NAME', + [ALERT_RULE_PRODUCER]: 'PRODUCER', + [ALERT_RULE_TYPE_ID]: 'RULE_TYPE_ID', + [ALERT_RULE_UUID]: 'RULE_UUID', + [ALERT_STATUS]: ALERT_STATUS_ACTIVE, + [ALERT_WORKFLOW_STATUS]: 'closed', + [SPACE_IDS]: ['fake-space-id'], + labels: { LABEL_0_KEY: 'LABEL_0_VALUE' }, // this must show up in the written doc + }, + }, + { + _source: { + '@timestamp': '', + [ALERT_INSTANCE_ID]: 'TEST_ALERT_1', + [ALERT_UUID]: 'ALERT_1_UUID', + [ALERT_RULE_CATEGORY]: 'RULE_TYPE_NAME', + [ALERT_RULE_CONSUMER]: 'CONSUMER', + [ALERT_RULE_NAME]: 'NAME', + [ALERT_RULE_PRODUCER]: 'PRODUCER', + [ALERT_RULE_TYPE_ID]: 'RULE_TYPE_ID', + [ALERT_RULE_UUID]: 'RULE_UUID', + [ALERT_STATUS]: ALERT_STATUS_ACTIVE, + [ALERT_WORKFLOW_STATUS]: 'open', + [SPACE_IDS]: ['fake-space-id'], + labels: { LABEL_0_KEY: 'LABEL_0_VALUE' }, // this must not show up in the written doc + }, + }, + ], + }, + } as any); + + const executor = createLifecycleExecutor( + logger, + ruleDataClientMock + )<{}, TestRuleState, never, never, never>(async ({ services, state }) => { + services.alertWithLifecycle({ + id: 'TEST_ALERT_0', + fields: {}, + }); + services.alertWithLifecycle({ + id: 'TEST_ALERT_1', + fields: {}, + }); + + return { state }; + }); + + await executor( + createDefaultAlertExecutorOptions({ + alertId: 'TEST_ALERT_0', + params: {}, + state: { + wrapped: initialRuleState, + trackedAlerts: { + TEST_ALERT_0: { + alertId: 'TEST_ALERT_0', + alertUuid: 'TEST_ALERT_0_UUID', + started: '2020-01-01T12:00:00.000Z', + flappingHistory: [], + flapping: false, + pendingRecoveredCount: 0, + }, + TEST_ALERT_1: { + alertId: 'TEST_ALERT_1', + alertUuid: 'TEST_ALERT_1_UUID', + started: '2020-01-02T12:00:00.000Z', + flappingHistory: [], + flapping: false, + pendingRecoveredCount: 0, + }, + }, + trackedAlertsRecovered: {}, + }, + logger, + maintenanceWindowIds, + }) + ); + + expect((await ruleDataClientMock.getWriter()).bulk).toHaveBeenCalledWith( + expect.objectContaining({ + body: [ + // alert document + { index: { _id: 'TEST_ALERT_0_UUID' } }, + expect.objectContaining({ + [ALERT_INSTANCE_ID]: 'TEST_ALERT_0', + [ALERT_WORKFLOW_STATUS]: 'closed', + [ALERT_STATUS]: ALERT_STATUS_ACTIVE, + labels: { LABEL_0_KEY: 'LABEL_0_VALUE' }, + [EVENT_ACTION]: 'active', + [EVENT_KIND]: 'signal', + [ALERT_MAINTENANCE_WINDOW_IDS]: maintenanceWindowIds, + }), + { index: { _id: 'TEST_ALERT_1_UUID' } }, + expect.objectContaining({ + [ALERT_INSTANCE_ID]: 'TEST_ALERT_1', + [ALERT_WORKFLOW_STATUS]: 'open', + [ALERT_STATUS]: ALERT_STATUS_ACTIVE, + [EVENT_ACTION]: 'active', + [EVENT_KIND]: 'signal', + [ALERT_MAINTENANCE_WINDOW_IDS]: maintenanceWindowIds, + }), + ], + }) + ); + expect((await ruleDataClientMock.getWriter()).bulk).not.toHaveBeenCalledWith( + expect.objectContaining({ + body: expect.arrayContaining([ + // evaluation documents + { index: {} }, + expect.objectContaining({ + [EVENT_KIND]: 'event', + }), + ]), + }) + ); + }); + + it('updates document with maintenance window ids for recovered alerts', async () => { + const logger = loggerMock.create(); + const ruleDataClientMock = createRuleDataClientMock(); + ruleDataClientMock.getReader().search.mockResolvedValue({ + hits: { + hits: [ + { + _source: { + '@timestamp': '', + [ALERT_INSTANCE_ID]: 'TEST_ALERT_0', + [ALERT_UUID]: 'ALERT_0_UUID', + [ALERT_RULE_CATEGORY]: 'RULE_TYPE_NAME', + [ALERT_RULE_CONSUMER]: 'CONSUMER', + [ALERT_RULE_NAME]: 'NAME', + [ALERT_RULE_PRODUCER]: 'PRODUCER', + [ALERT_RULE_TYPE_ID]: 'RULE_TYPE_ID', + [ALERT_RULE_UUID]: 'RULE_UUID', + [ALERT_STATUS]: ALERT_STATUS_ACTIVE, + [SPACE_IDS]: ['fake-space-id'], + labels: { LABEL_0_KEY: 'LABEL_0_VALUE' }, // this must show up in the written doc + [TAGS]: ['source-tag1', 'source-tag2'], + }, + }, + { + _source: { + '@timestamp': '', + [ALERT_INSTANCE_ID]: 'TEST_ALERT_1', + [ALERT_UUID]: 'ALERT_1_UUID', + [ALERT_RULE_CATEGORY]: 'RULE_TYPE_NAME', + [ALERT_RULE_CONSUMER]: 'CONSUMER', + [ALERT_RULE_NAME]: 'NAME', + [ALERT_RULE_PRODUCER]: 'PRODUCER', + [ALERT_RULE_TYPE_ID]: 'RULE_TYPE_ID', + [ALERT_RULE_UUID]: 'RULE_UUID', + [ALERT_STATUS]: ALERT_STATUS_ACTIVE, + [SPACE_IDS]: ['fake-space-id'], + labels: { LABEL_0_KEY: 'LABEL_0_VALUE' }, // this must not show up in the written doc + [TAGS]: ['source-tag3', 'source-tag4'], + }, + }, + ], + }, + } as any); + const executor = createLifecycleExecutor( + logger, + ruleDataClientMock + )<{}, TestRuleState, never, never, never>(async ({ services, state }) => { + // TEST_ALERT_0 has recovered + services.alertWithLifecycle({ + id: 'TEST_ALERT_1', + fields: {}, + }); + + return { state }; + }); + + await executor( + createDefaultAlertExecutorOptions({ + alertId: 'TEST_ALERT_0', + params: {}, + state: { + wrapped: initialRuleState, + trackedAlerts: { + TEST_ALERT_0: { + alertId: 'TEST_ALERT_0', + alertUuid: 'TEST_ALERT_0_UUID', + started: '2020-01-01T12:00:00.000Z', + flappingHistory: [], + flapping: false, + pendingRecoveredCount: 0, + }, + TEST_ALERT_1: { + alertId: 'TEST_ALERT_1', + alertUuid: 'TEST_ALERT_1_UUID', + started: '2020-01-02T12:00:00.000Z', + flappingHistory: [], + flapping: false, + pendingRecoveredCount: 0, + }, + }, + trackedAlertsRecovered: {}, + }, + logger, + maintenanceWindowIds, + }) + ); + + expect((await ruleDataClientMock.getWriter()).bulk).toHaveBeenCalledWith( + expect.objectContaining({ + body: expect.arrayContaining([ + // alert document + { index: { _id: 'TEST_ALERT_0_UUID' } }, + expect.objectContaining({ + [ALERT_INSTANCE_ID]: 'TEST_ALERT_0', + [ALERT_STATUS]: ALERT_STATUS_RECOVERED, + labels: { LABEL_0_KEY: 'LABEL_0_VALUE' }, + [TAGS]: ['source-tag1', 'source-tag2', 'rule-tag1', 'rule-tag2'], + [EVENT_ACTION]: 'close', + [EVENT_KIND]: 'signal', + [ALERT_MAINTENANCE_WINDOW_IDS]: maintenanceWindowIds, + }), + { index: { _id: 'TEST_ALERT_1_UUID' } }, + expect.objectContaining({ + [ALERT_INSTANCE_ID]: 'TEST_ALERT_1', + [ALERT_STATUS]: ALERT_STATUS_ACTIVE, + [EVENT_ACTION]: 'active', + [EVENT_KIND]: 'signal', + [TAGS]: ['source-tag3', 'source-tag4', 'rule-tag1', 'rule-tag2'], + [ALERT_MAINTENANCE_WINDOW_IDS]: maintenanceWindowIds, + }), + ]), + }) + ); + expect((await ruleDataClientMock.getWriter()).bulk).not.toHaveBeenCalledWith( + expect.objectContaining({ + body: expect.arrayContaining([ + // evaluation documents + { index: {} }, + expect.objectContaining({ + [EVENT_KIND]: 'event', + }), + ]), + }) + ); + }); + }); + describe('set flapping on the document', () => { const flapping = new Array(16).fill(false).concat([true, true, true, true]); const notFlapping = new Array(20).fill(false); diff --git a/x-pack/plugins/rule_registry/server/utils/create_lifecycle_executor.ts b/x-pack/plugins/rule_registry/server/utils/create_lifecycle_executor.ts index 1b9c03745cda0..a213f25cc0fcd 100644 --- a/x-pack/plugins/rule_registry/server/utils/create_lifecycle_executor.ts +++ b/x-pack/plugins/rule_registry/server/utils/create_lifecycle_executor.ts @@ -43,6 +43,7 @@ import { TIMESTAMP, VERSION, ALERT_FLAPPING, + ALERT_MAINTENANCE_WINDOW_IDS, } from '../../common/technical_rule_data_field_names'; import { CommonAlertFieldNameLatest, CommonAlertIdFieldNameLatest } from '../../common/schemas'; import { IRuleDataClient } from '../rule_data_client'; @@ -131,6 +132,7 @@ export const createLifecycleExecutor = services: { alertFactory, shouldWriteAlerts }, state: previousState, flappingSettings, + maintenanceWindowIds, rule, } = options; @@ -299,6 +301,9 @@ export const createLifecycleExecutor = [VERSION]: ruleDataClient.kibanaVersion, [ALERT_FLAPPING]: flapping, ...(isRecovered ? { [ALERT_END]: commonRuleFields[TIMESTAMP] } : {}), + ...(maintenanceWindowIds?.length + ? { [ALERT_MAINTENANCE_WINDOW_IDS]: maintenanceWindowIds } + : {}), }; return { diff --git a/x-pack/plugins/rule_registry/server/utils/rule_executor.test_helpers.ts b/x-pack/plugins/rule_registry/server/utils/rule_executor.test_helpers.ts index 58b370847de5f..588e2efa3eeb8 100644 --- a/x-pack/plugins/rule_registry/server/utils/rule_executor.test_helpers.ts +++ b/x-pack/plugins/rule_registry/server/utils/rule_executor.test_helpers.ts @@ -39,6 +39,7 @@ export const createDefaultAlertExecutorOptions = < startedAt = new Date(), updatedAt = new Date(), shouldWriteAlerts = true, + maintenanceWindowIds, }: { alertId?: string; ruleName?: string; @@ -49,6 +50,7 @@ export const createDefaultAlertExecutorOptions = < startedAt?: Date; updatedAt?: Date; shouldWriteAlerts?: boolean; + maintenanceWindowIds?: string[]; }): RuleExecutorOptions => ({ startedAt, rule: { @@ -92,4 +94,5 @@ export const createDefaultAlertExecutorOptions = < executionId: 'b33f65d7-6e8b-4aae-8d20-c93613deb33f', logger, flappingSettings: DEFAULT_FLAPPING_SETTINGS, + ...(maintenanceWindowIds ? { maintenanceWindowIds } : {}), }); 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 7d832ccb450b4..fac54e789bd30 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 @@ -5,6 +5,7 @@ * 2.0. */ +import moment from 'moment'; import expect from '@kbn/expect'; import { IValidatedEvent, nanosToMillis } from '@kbn/event-log-plugin/server'; import { RuleNotifyWhen } from '@kbn/alerting-plugin/common'; @@ -1180,6 +1181,128 @@ export default function eventLogTests({ getService }: FtrProviderContext) { } } }); + + it('should generate expected events affected by active maintenance windows', async () => { + // Create 2 active maintenance windows + const { body: window1 } = await supertest + .post(`${getUrlPrefix(space.id)}/internal/alerting/rules/maintenance_window`) + .set('kbn-xsrf', 'foo') + .send({ + title: 'test-maintenance-window-1', + duration: 60 * 60 * 1000, // 1 hr + r_rule: { + dtstart: moment.utc().toISOString(), + tzid: 'UTC', + freq: 0, // yearly + count: 1, + }, + }) + .expect(200); + objectRemover.add(space.id, window1.id, 'rules/maintenance_window', 'alerting', true); + + const { body: window2 } = await supertest + .post(`${getUrlPrefix(space.id)}/internal/alerting/rules/maintenance_window`) + .set('kbn-xsrf', 'foo') + .send({ + title: 'test-maintenance-window-2', + duration: 60 * 60 * 1000, // 1 hr + r_rule: { + dtstart: moment.utc().toISOString(), + tzid: 'UTC', + freq: 0, // yearly + count: 1, + }, + }) + .expect(200); + objectRemover.add(space.id, window2.id, 'rules/maintenance_window', 'alerting', true); + + // Create 1 inactive maintenance window + const { body: window3 } = await supertest + .post(`${getUrlPrefix(space.id)}/internal/alerting/rules/maintenance_window`) + .set('kbn-xsrf', 'foo') + .send({ + title: 'test-maintenance-window-3', + duration: 60 * 60 * 1000, // 1 hr + r_rule: { + dtstart: moment.utc().add(1, 'day').toISOString(), + tzid: 'UTC', + freq: 0, // yearly + count: 1, + }, + }) + .expect(200); + objectRemover.add(space.id, window3.id, 'rules/maintenance_window', 'alerting', true); + + const { body: createdAction } = await supertest + .post(`${getUrlPrefix(space.id)}/api/actions/connector`) + .set('kbn-xsrf', 'foo') + .send({ + name: 'MY action', + connector_type_id: 'test.noop', + config: {}, + secrets: {}, + }) + .expect(200); + + // pattern of when the alert should fire + const pattern = { + instance: [false, true, true], + }; + + const response = await supertest + .post(`${getUrlPrefix(space.id)}/api/alerting/rule`) + .set('kbn-xsrf', 'foo') + .send( + getTestRuleData({ + rule_type_id: 'test.patternFiring', + schedule: { interval: '1s' }, + throttle: null, + params: { + pattern, + }, + actions: [ + { + id: createdAction.id, + group: 'default', + params: {}, + }, + ], + }) + ); + + expect(response.status).to.eql(200); + const alertId = response.body.id; + objectRemover.add(space.id, alertId, 'rule', 'alerting'); + + // get the events we're expecting + const events = await retry.try(async () => { + return await getEventLog({ + getService, + spaceId: space.id, + type: 'alert', + id: alertId, + provider: 'alerting', + actions: new Map([ + // make sure the counts of the # of events per type are as expected + ['execute-start', { gte: 4 }], + ['execute', { gte: 4 }], + ['new-instance', { equal: 1 }], + ['active-instance', { gte: 1 }], + ['recovered-instance', { equal: 1 }], + ]), + }); + }); + + const actionsToCheck = ['new-instance', 'active-instance', 'recovered-instance']; + + events.forEach((event) => { + if (actionsToCheck.includes(event?.event?.action || '')) { + const alertMaintenanceWindowIds = + event?.kibana?.alert?.maintenance_window_ids?.sort(); + expect(alertMaintenanceWindowIds).eql([window1.id, window2.id].sort()); + } + }); + }); }); } }); diff --git a/x-pack/test/rule_registry/spaces_only/tests/trial/get_summarized_alerts.ts b/x-pack/test/rule_registry/spaces_only/tests/trial/get_summarized_alerts.ts index 32f47b708e545..680b61cceaf20 100644 --- a/x-pack/test/rule_registry/spaces_only/tests/trial/get_summarized_alerts.ts +++ b/x-pack/test/rule_registry/spaces_only/tests/trial/get_summarized_alerts.ts @@ -15,6 +15,7 @@ import { AlertConsumers, ALERT_REASON, ALERT_INSTANCE_ID, + ALERT_MAINTENANCE_WINDOW_IDS, } from '@kbn/rule-registry-plugin/common/technical_rule_data_field_names'; import { createLifecycleExecutor, @@ -380,5 +381,141 @@ export default function createGetSummarizedAlertsTest({ getService }: FtrProvide expect(summarizedAlertsExcludingId2.recovered.count).to.eql(0); expect(get(summarizedAlertsExcludingId2.new.data[0], ALERT_INSTANCE_ID)).to.eql(id1); }); + + it('should return new, ongoing, and recovered alerts if there are active maintenance windows', async () => { + const id = 'host-01'; + const maintenanceWindowIds = ['test-id-1', 'test-id-2']; + + // This creates the function that will wrap the solution's rule executor with the RuleRegistry lifecycle + const createLifecycleRuleExecutor = createLifecycleExecutor(logger, ruleDataClient); + const createGetSummarizedAlerts = createGetSummarizedAlertsFn({ + ruleDataClient, + useNamespace: false, + isLifecycleAlert: true, + }); + + // This creates the executor that is passed to the Alerting framework. + const executor = createLifecycleRuleExecutor< + MockRuleParams, + { shouldTriggerAlert: boolean }, + MockAlertState, + MockAlertContext, + MockAllowedActionGroups + >(async function (options) { + const { services, state: previousState } = options; + const { alertWithLifecycle } = services; + + const triggerAlert = previousState.shouldTriggerAlert; + + if (triggerAlert) { + alertWithLifecycle({ + id, + fields: { + [ALERT_REASON]: 'Test alert is firing', + }, + }); + } + + return Promise.resolve({ state: { shouldTriggerAlert: triggerAlert } }); + }); + + const getSummarizedAlerts = createGetSummarizedAlerts(); + + // Create the options with the minimal amount of values to test the lifecycle executor + const options = { + spaceId: 'default', + rule: { + id, + name: 'test rule', + ruleTypeId: 'observability.test.fake', + ruleTypeName: 'test', + consumer: 'observability', + producer: 'observability.test', + }, + services: { + alertFactory: getMockAlertFactory(), + shouldWriteAlerts: sinon.stub().returns(true), + }, + flappingSettings: DEFAULT_FLAPPING_SETTINGS, + maintenanceWindowIds, + } as unknown as RuleExecutorOptions< + MockRuleParams, + WrappedLifecycleRuleState<{ shouldTriggerAlert: boolean }>, + { [x: string]: unknown }, + { [x: string]: unknown }, + string + >; + + const getState = ( + shouldTriggerAlert: boolean, + alerts: Record + ) => ({ wrapped: { shouldTriggerAlert }, trackedAlerts: alerts, trackedAlertsRecovered: {} }); + + // Execute the rule the first time - this creates a new alert + const execution1Uuid = uuidv4(); + const execution1Result = await executor({ + ...options, + startedAt: new Date(), + state: getState(true, {}), + executionId: execution1Uuid, + }); + + const execution1SummarizedAlerts = await getSummarizedAlerts({ + ruleId: id, + executionUuid: execution1Uuid, + spaceId: 'default', + excludedAlertInstanceIds: [], + }); + expect(execution1SummarizedAlerts.new.count).to.eql(1); + expect(execution1SummarizedAlerts.ongoing.count).to.eql(0); + expect(execution1SummarizedAlerts.recovered.count).to.eql(0); + expect(get(execution1SummarizedAlerts.new.data[0], ALERT_MAINTENANCE_WINDOW_IDS)).to.eql( + maintenanceWindowIds + ); + + // Execute again to update the existing alert + const execution2Uuid = uuidv4(); + const execution2Result = await executor({ + ...options, + startedAt: new Date(), + state: getState(true, execution1Result.state.trackedAlerts), + executionId: execution2Uuid, + }); + + const execution2SummarizedAlerts = await getSummarizedAlerts({ + ruleId: id, + executionUuid: execution2Uuid, + spaceId: 'default', + excludedAlertInstanceIds: [], + }); + expect(execution2SummarizedAlerts.new.count).to.eql(0); + expect(execution2SummarizedAlerts.ongoing.count).to.eql(1); + expect(execution2SummarizedAlerts.recovered.count).to.eql(0); + expect(get(execution2SummarizedAlerts.ongoing.data[0], ALERT_MAINTENANCE_WINDOW_IDS)).to.eql( + maintenanceWindowIds + ); + + // Execute again to recover the alert + const execution3Uuid = uuidv4(); + await executor({ + ...options, + startedAt: new Date(), + state: getState(false, execution2Result.state.trackedAlerts), + executionId: execution3Uuid, + }); + + const execution3SummarizedAlerts = await getSummarizedAlerts({ + ruleId: id, + executionUuid: execution3Uuid, + spaceId: 'default', + excludedAlertInstanceIds: [], + }); + expect(execution3SummarizedAlerts.new.count).to.eql(0); + expect(execution3SummarizedAlerts.ongoing.count).to.eql(0); + expect(execution3SummarizedAlerts.recovered.count).to.eql(1); + expect( + get(execution3SummarizedAlerts.recovered.data[0], ALERT_MAINTENANCE_WINDOW_IDS) + ).to.eql(maintenanceWindowIds); + }); }); } From 54f9f11339678f7949ff7b39b5fb6e734debd9bf Mon Sep 17 00:00:00 2001 From: Shahzad Date: Wed, 19 Apr 2023 18:10:31 +0200 Subject: [PATCH 52/78] [Synthetics] Monitor form validate inline script (#155185) ## Summary Fixes https://github.com/elastic/kibana/issues/149299 Added inline script basic validation !! Also adjusted Test Now button to be more user friendly !! image --------- Co-authored-by: Justin Kambic --- .../monitor_add_edit/form/field_config.tsx | 22 ++++++++++++++++++- .../monitor_add_edit/form/run_test_btn.tsx | 10 ++++----- 2 files changed, 25 insertions(+), 7 deletions(-) diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/form/field_config.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/form/field_config.tsx index 7e1ea329fe3f8..b0de4f812da1a 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/form/field_config.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/form/field_config.tsx @@ -895,7 +895,27 @@ export const FIELD = (readOnly?: boolean): FieldMap => ({ isEditFlow: isEdit, }), validation: () => ({ - validate: (value) => Boolean(value.script), + validate: (value) => { + // return false if script contains import or require statement + if ( + value.script?.includes('import ') || + value.script?.includes('require(') || + value.script?.includes('journey(') + ) { + return i18n.translate('xpack.synthetics.monitorConfig.monitorScript.invalid', { + defaultMessage: + 'Monitor script is invalid. Inline scripts cannot be full journey scripts, they may only contain step definitions.', + }); + } + // should contain at least one step + if (value.script && !value.script?.includes('step(')) { + return i18n.translate('xpack.synthetics.monitorConfig.monitorScript.invalid.oneStep', { + defaultMessage: + 'Monitor script is invalid. Inline scripts must contain at least one step definition.', + }); + } + return Boolean(value.script); + }, }), error: i18n.translate('xpack.synthetics.monitorConfig.monitorScript.error', { defaultMessage: 'Monitor script is required', diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/form/run_test_btn.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/form/run_test_btn.tsx index 5bfc724e39f49..07497c0e6cd50 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/form/run_test_btn.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/form/run_test_btn.tsx @@ -20,14 +20,14 @@ import { import { runOnceMonitor } from '../../../state/manual_test_runs/api'; export const RunTestButton = () => { - const { watch, formState, getValues } = useFormContext(); + const { watch, formState, getValues, handleSubmit } = useFormContext(); const [inProgress, setInProgress] = useState(false); const [testRun, setTestRun] = useState(); const handleTestNow = () => { const config = getValues() as MonitorFieldsType; - if (config) { + if (config && !Object.keys(formState.errors).length) { setInProgress(true); setTestRun({ id: uuidv4(), @@ -68,9 +68,7 @@ export const RunTestButton = () => { disabled={isDisabled} aria-label={TEST_NOW_ARIA_LABEL} iconType="play" - onClick={() => { - handleTestNow(); - }} + onClick={handleSubmit(handleTestNow)} > {RUN_TEST} @@ -111,7 +109,7 @@ const useTooltipContent = ( tooltipContent = isTestRunInProgress ? TEST_SCHEDULED_LABEL : tooltipContent; - const isDisabled = !isValid || isTestRunInProgress || !isAnyPublicLocationSelected; + const isDisabled = isTestRunInProgress || !isAnyPublicLocationSelected; return { tooltipContent, isDisabled }; }; From 2fbfb33bed4aea5c2201c4719291220ce0175d72 Mon Sep 17 00:00:00 2001 From: Shahzad Date: Wed, 19 Apr 2023 18:14:11 +0200 Subject: [PATCH 53/78] [Synthetics] Enable throttling in synthetics app (#154378) ## Summary Enable throttling config in synthetics app !! image --------- Co-authored-by: Dominique Clarke --- .../group2/check_registered_types.test.ts | 2 +- .../common/constants/monitor_defaults.ts | 88 ++++++++- .../common/constants/monitor_management.ts | 12 +- .../common/formatters/browser/formatters.ts | 21 +- .../format_synthetics_policy.test.ts | 7 +- .../formatters/format_synthetics_policy.ts | 8 + .../monitor_management/monitor_configs.ts | 9 - .../monitor_management/monitor_types.ts | 31 +-- .../common/types/monitor_validation.ts | 8 +- .../fields/throttling/connection_profile.tsx | 81 ++++++++ .../throttling_config_field.test.tsx | 81 ++++++++ .../throttling/throttling_config_field.tsx | 79 ++++++++ .../throttling_disabled_callout.tsx | 33 ++++ .../throttling/throttling_download_field.tsx | 91 +++++++++ .../throttling_exceeded_callout.tsx | 49 +++++ .../throttling/throttling_fields.test.tsx | 86 +++++++++ .../fields/throttling/throttling_fields.tsx | 97 ++++++++++ .../throttling/throttling_latency_field.tsx | 72 +++++++ .../throttling/throttling_upload_field.tsx | 87 +++++++++ .../throttling/use_connection_profiles.tsx | 30 +++ .../monitor_add_edit/form/defaults.test.tsx | 22 +-- .../monitor_add_edit/form/field_config.tsx | 53 ++---- .../monitor_add_edit/form/field_wrappers.tsx | 8 + .../monitor_add_edit/form/form_config.tsx | 3 +- .../monitor_add_edit/form/formatter.test.tsx | 20 +- .../monitor_add_edit/form/validation.tsx | 14 +- .../monitor_edit_page.test.tsx | 5 + .../__mocks__/synthetics_store.mock.ts | 14 +- .../migrations/monitors/8.8.0.test.ts | 106 ++++++++++- .../migrations/monitors/8.8.0.ts | 120 +++++++++++- .../lib/saved_objects/synthetics_monitor.ts | 7 + .../monitor_cruds/add_monitor_project.ts | 2 +- .../monitor_cruds/monitor_validation.test.ts | 14 +- .../synthetics_service/formatters/browser.ts | 26 +-- .../formatters/format_configs.test.ts | 28 +-- .../formatters/format_configs.ts | 19 +- .../synthetics_private_location.test.ts | 6 +- .../normalizers/browser_monitor.test.ts | 180 ++++++++++++++++-- .../normalizers/browser_monitor.ts | 71 ++++--- .../project_monitor_formatter.test.ts | 10 - .../project_monitor_formatter_legacy.test.ts | 10 - .../translations/translations/fr-FR.json | 1 - .../translations/translations/ja-JP.json | 1 - .../translations/translations/zh-CN.json | 1 - .../apis/synthetics/add_monitor_project.ts | 16 +- .../synthetics/add_monitor_project_legacy.ts | 10 +- .../uptime/rest/fixtures/browser_monitor.json | 14 +- 47 files changed, 1480 insertions(+), 273 deletions(-) create mode 100644 x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/fields/throttling/connection_profile.tsx create mode 100644 x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/fields/throttling/throttling_config_field.test.tsx create mode 100644 x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/fields/throttling/throttling_config_field.tsx create mode 100644 x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/fields/throttling/throttling_disabled_callout.tsx create mode 100644 x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/fields/throttling/throttling_download_field.tsx create mode 100644 x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/fields/throttling/throttling_exceeded_callout.tsx create mode 100644 x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/fields/throttling/throttling_fields.test.tsx create mode 100644 x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/fields/throttling/throttling_fields.tsx create mode 100644 x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/fields/throttling/throttling_latency_field.tsx create mode 100644 x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/fields/throttling/throttling_upload_field.tsx create mode 100644 x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/fields/throttling/use_connection_profiles.tsx diff --git a/src/core/server/integration_tests/saved_objects/migrations/group2/check_registered_types.test.ts b/src/core/server/integration_tests/saved_objects/migrations/group2/check_registered_types.test.ts index a6428387f65f3..1da0a3c83c9bb 100644 --- a/src/core/server/integration_tests/saved_objects/migrations/group2/check_registered_types.test.ts +++ b/src/core/server/integration_tests/saved_objects/migrations/group2/check_registered_types.test.ts @@ -138,7 +138,7 @@ describe('checking migration metadata changes on all registered SO types', () => "slo": "aefffabdb35d15a6c388634af2cee1fa59ede83c", "space": "7fc578a1f9f7708cb07479f03953d664ad9f1dae", "spaces-usage-stats": "084bd0f080f94fb5735d7f3cf12f13ec92f36bad", - "synthetics-monitor": "7136a2669a65323c56da849f26c369cdeeb3b381", + "synthetics-monitor": "fa988678e5d6791c75515fa1acdbbb3b2d1f916d", "synthetics-param": "9776c9b571d35f0d0397e8915e035ea1dc026db7", "synthetics-privates-locations": "7d032fc788905e32152029ae7ab3d6038c48ae44", "tag": "87f21f07df9cc37001b15a26e413c18f50d1fbfe", diff --git a/x-pack/plugins/synthetics/common/constants/monitor_defaults.ts b/x-pack/plugins/synthetics/common/constants/monitor_defaults.ts index 956dfa2af23b5..6b9cc7913019a 100644 --- a/x-pack/plugins/synthetics/common/constants/monitor_defaults.ts +++ b/x-pack/plugins/synthetics/common/constants/monitor_defaults.ts @@ -4,6 +4,7 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ +import { i18n } from '@kbn/i18n'; import { BrowserAdvancedFields, BrowserSimpleFields, @@ -22,6 +23,7 @@ import { SourceType, TCPAdvancedFields, TCPSimpleFields, + ThrottlingConfig, TLSFields, TLSVersion, VerificationMode, @@ -30,6 +32,86 @@ import { ConfigKey } from './monitor_management'; export const DEFAULT_NAMESPACE_STRING = 'default'; +export enum PROFILE_VALUES_ENUM { + DEFAULT = 'default', + CABLE = 'cable', + DSL = 'dsl', + THREE_G = '3g', + FOUR_G = '4g', + LTE = 'lte', + FIBRE = 'fibre', + NO_THROTTLING = 'no-throttling', + CUSTOM = 'custom', +} + +export const CUSTOM_LABEL = i18n.translate('xpack.synthetics.connectionProfile.custom', { + defaultMessage: 'Custom', +}); + +export const PROFILE_VALUES: ThrottlingConfig[] = [ + { + value: { download: '5', upload: '3', latency: '20' }, + id: PROFILE_VALUES_ENUM.DEFAULT, + label: i18n.translate('xpack.synthetics.connectionProfile.default', { + defaultMessage: 'Default', + }), + }, + { + value: { download: '5', upload: '1', latency: '28' }, + id: PROFILE_VALUES_ENUM.CABLE, + label: i18n.translate('xpack.synthetics.connectionProfile.cable', { + defaultMessage: 'Cable', + }), + }, + { + value: { download: '1.5', upload: '0.384', latency: '50' }, + id: PROFILE_VALUES_ENUM.DSL, + label: i18n.translate('xpack.synthetics.connectionProfile.dsl', { + defaultMessage: 'DSL', + }), + }, + { + value: { download: '1.6', upload: '0.768', latency: '300' }, + id: PROFILE_VALUES_ENUM.THREE_G, + label: i18n.translate('xpack.synthetics.connectionProfile.threeG', { + defaultMessage: '3G', + }), + }, + { + value: { download: '9', upload: '0.75', latency: '170' }, + id: PROFILE_VALUES_ENUM.FOUR_G, + label: i18n.translate('xpack.synthetics.connectionProfile.fourG', { + defaultMessage: '4G', + }), + }, + { + value: { download: '12', upload: '0.75', latency: '70' }, + id: PROFILE_VALUES_ENUM.LTE, + label: i18n.translate('xpack.synthetics.connectionProfile.lte', { + defaultMessage: 'LTE', + }), + }, + { + value: { download: '20', upload: '5', latency: '4' }, + id: PROFILE_VALUES_ENUM.FIBRE, + label: i18n.translate('xpack.synthetics.connectionProfile.fibre', { + defaultMessage: 'Fibre', + }), + }, + { + value: null, + id: PROFILE_VALUES_ENUM.NO_THROTTLING, + label: i18n.translate('xpack.synthetics.connectionProfile.noThrottling', { + defaultMessage: 'No throttling', + }), + }, +]; + +export const PROFILES_MAP = PROFILE_VALUES.reduce((acc, profile) => { + acc[profile.id] = profile; + return acc; +}, {} as { [key: string]: ThrottlingConfig }); + export const ALLOWED_SCHEDULES_IN_MINUTES = [ '1', '3', @@ -71,11 +153,7 @@ export const DEFAULT_BROWSER_ADVANCED_FIELDS: BrowserAdvancedFields = { [ConfigKey.JOURNEY_FILTERS_MATCH]: '', [ConfigKey.JOURNEY_FILTERS_TAGS]: [], [ConfigKey.IGNORE_HTTPS_ERRORS]: false, - [ConfigKey.IS_THROTTLING_ENABLED]: true, - [ConfigKey.DOWNLOAD_SPEED]: '5', - [ConfigKey.UPLOAD_SPEED]: '3', - [ConfigKey.LATENCY]: '20', - [ConfigKey.THROTTLING_CONFIG]: '5d/3u/20l', + [ConfigKey.THROTTLING_CONFIG]: PROFILES_MAP[PROFILE_VALUES_ENUM.DEFAULT], }; export const DEFAULT_BROWSER_SIMPLE_FIELDS: BrowserSimpleFields = { diff --git a/x-pack/plugins/synthetics/common/constants/monitor_management.ts b/x-pack/plugins/synthetics/common/constants/monitor_management.ts index 231acff2c6230..01da8ebce5d43 100644 --- a/x-pack/plugins/synthetics/common/constants/monitor_management.ts +++ b/x-pack/plugins/synthetics/common/constants/monitor_management.ts @@ -65,11 +65,7 @@ export enum ConfigKey { TLS_VERSION = 'ssl.supported_protocols', TAGS = 'tags', TIMEOUT = 'timeout', - THROTTLING_CONFIG = 'throttling.config', - IS_THROTTLING_ENABLED = 'throttling.is_enabled', - DOWNLOAD_SPEED = 'throttling.download_speed', - UPLOAD_SPEED = 'throttling.upload_speed', - LATENCY = 'throttling.latency', + THROTTLING_CONFIG = 'throttling', URLS = 'urls', USERNAME = 'username', WAIT = 'wait', @@ -106,4 +102,10 @@ export enum LegacyConfigKey { ZIP_URL_TLS_KEY_PASSPHRASE = 'source.zip_url.ssl.key_passphrase', ZIP_URL_TLS_VERIFICATION_MODE = 'source.zip_url.ssl.verification_mode', ZIP_URL_TLS_VERSION = 'source.zip_url.ssl.supported_protocols', + + THROTTLING_CONFIG = 'throttling.config', + IS_THROTTLING_ENABLED = 'throttling.is_enabled', + DOWNLOAD_SPEED = 'throttling.download_speed', + UPLOAD_SPEED = 'throttling.upload_speed', + LATENCY = 'throttling.latency', } diff --git a/x-pack/plugins/synthetics/common/formatters/browser/formatters.ts b/x-pack/plugins/synthetics/common/formatters/browser/formatters.ts index 1ad7c055aecc3..508464c48a5f2 100644 --- a/x-pack/plugins/synthetics/common/formatters/browser/formatters.ts +++ b/x-pack/plugins/synthetics/common/formatters/browser/formatters.ts @@ -17,29 +17,20 @@ import { tlsFormatters } from '../tls/formatters'; export type BrowserFormatMap = Record; -const throttlingFormatter: Formatter = (fields) => { - if (!fields[ConfigKey.IS_THROTTLING_ENABLED]) return 'false'; +export const throttlingFormatter: Formatter = (fields) => { + const throttling = fields[ConfigKey.THROTTLING_CONFIG]; - const getThrottlingValue = (v: string | undefined, suffix: 'd' | 'u' | 'l') => - v !== '' && v !== undefined ? `${v}${suffix}` : null; + if (!throttling || throttling?.id === 'no-throttling' || !throttling?.value) { + return 'false'; + } - return [ - getThrottlingValue(fields[ConfigKey.DOWNLOAD_SPEED], 'd'), - getThrottlingValue(fields[ConfigKey.UPLOAD_SPEED], 'u'), - getThrottlingValue(fields[ConfigKey.LATENCY], 'l'), - ] - .filter((v) => v !== null) - .join('/'); + return `${throttling.value.download}d/${throttling.value.upload}u/${throttling.value.latency}l`; }; export const browserFormatters: BrowserFormatMap = { [ConfigKey.SOURCE_PROJECT_CONTENT]: null, [ConfigKey.PARAMS]: null, [ConfigKey.SCREENSHOTS]: null, - [ConfigKey.IS_THROTTLING_ENABLED]: null, - [ConfigKey.DOWNLOAD_SPEED]: null, - [ConfigKey.UPLOAD_SPEED]: null, - [ConfigKey.LATENCY]: null, [ConfigKey.IGNORE_HTTPS_ERRORS]: null, [ConfigKey.PLAYWRIGHT_OPTIONS]: null, [ConfigKey.TEXT_ASSERTION]: null, diff --git a/x-pack/plugins/synthetics/common/formatters/format_synthetics_policy.test.ts b/x-pack/plugins/synthetics/common/formatters/format_synthetics_policy.test.ts index a03c54fc4f34e..6be823ee0a208 100644 --- a/x-pack/plugins/synthetics/common/formatters/format_synthetics_policy.test.ts +++ b/x-pack/plugins/synthetics/common/formatters/format_synthetics_policy.test.ts @@ -6,6 +6,7 @@ */ import { ConfigKey, DataStream } from '../runtime_types'; import { formatSyntheticsPolicy } from './format_synthetics_policy'; +import { PROFILE_VALUES_ENUM, PROFILES_MAP } from '../constants/monitor_defaults'; const gParams = { proxyUrl: 'https://proxy.com' }; describe('formatSyntheticsPolicy', () => { @@ -1145,11 +1146,7 @@ const browserConfig: any = { 'filter_journeys.match': '', 'filter_journeys.tags': [], ignore_https_errors: false, - 'throttling.is_enabled': true, - 'throttling.download_speed': '5', - 'throttling.upload_speed': '3', - 'throttling.latency': '20', - 'throttling.config': '5d/3u/20l', + throttling: PROFILES_MAP[PROFILE_VALUES_ENUM.DEFAULT], 'ssl.certificate_authorities': '', 'ssl.certificate': '', 'ssl.key': '', diff --git a/x-pack/plugins/synthetics/common/formatters/format_synthetics_policy.ts b/x-pack/plugins/synthetics/common/formatters/format_synthetics_policy.ts index 392a50d59ed28..1a9eedd6acf2a 100644 --- a/x-pack/plugins/synthetics/common/formatters/format_synthetics_policy.ts +++ b/x-pack/plugins/synthetics/common/formatters/format_synthetics_policy.ts @@ -7,6 +7,8 @@ import { NewPackagePolicy } from '@kbn/fleet-plugin/common'; import { cloneDeep } from 'lodash'; +import { throttlingFormatter } from './browser/formatters'; +import { LegacyConfigKey } from '../constants/monitor_management'; import { replaceStringWithParams } from './formatting_utils'; import { syntheticsPolicyFormatters } from './formatters'; import { ConfigKey, DataStream, MonitorFields } from '../runtime_types'; @@ -69,5 +71,11 @@ export const formatSyntheticsPolicy = ( } }); + // TODO: remove this once we remove legacy support + const throttling = dataStream?.vars?.[LegacyConfigKey.THROTTLING_CONFIG]; + if (throttling) { + throttling.value = throttlingFormatter?.(config, ConfigKey.THROTTLING_CONFIG); + } + return { formattedPolicy, hasDataStream: Boolean(dataStream), hasInput: Boolean(currentInput) }; }; diff --git a/x-pack/plugins/synthetics/common/runtime_types/monitor_management/monitor_configs.ts b/x-pack/plugins/synthetics/common/runtime_types/monitor_management/monitor_configs.ts index 934c534795301..e616d887df4f7 100644 --- a/x-pack/plugins/synthetics/common/runtime_types/monitor_management/monitor_configs.ts +++ b/x-pack/plugins/synthetics/common/runtime_types/monitor_management/monitor_configs.ts @@ -111,15 +111,6 @@ export enum ScreenshotOption { export const ScreenshotOptionCodec = tEnum('ScreenshotOption', ScreenshotOption); export type ScreenshotOptionType = t.TypeOf; -export enum ThrottlingSuffix { - DOWNLOAD = 'd', - UPLOAD = 'u', - LATENCY = 'l', -} - -export const ThrottlingSuffixCodec = tEnum('ThrottlingSuffix', ThrottlingSuffix); -export type ThrottlingSuffixType = t.TypeOf; - export enum SourceType { UI = 'ui', PROJECT = 'project', diff --git a/x-pack/plugins/synthetics/common/runtime_types/monitor_management/monitor_types.ts b/x-pack/plugins/synthetics/common/runtime_types/monitor_management/monitor_types.ts index 2e639e360fd1a..97363987afcc4 100644 --- a/x-pack/plugins/synthetics/common/runtime_types/monitor_management/monitor_types.ts +++ b/x-pack/plugins/synthetics/common/runtime_types/monitor_management/monitor_types.ts @@ -192,15 +192,6 @@ export const HTTPFieldsCodec = t.intersection([ export type HTTPFields = t.TypeOf; -// Browser Fields -export const ThrottlingConfigKeyCodec = t.union([ - t.literal(ConfigKey.DOWNLOAD_SPEED), - t.literal(ConfigKey.UPLOAD_SPEED), - t.literal(ConfigKey.LATENCY), -]); - -export type ThrottlingConfigKey = t.TypeOf; - export const EncryptedBrowserSimpleFieldsCodec = t.intersection([ t.intersection([ t.interface({ @@ -225,16 +216,28 @@ export const BrowserSensitiveSimpleFieldsCodec = t.intersection([ CommonFieldsCodec, ]); +export const ThrottlingConfigValueCodec = t.interface({ + download: t.string, + upload: t.string, + latency: t.string, +}); + +export type ThrottlingConfigValue = t.TypeOf; + +export const ThrottlingConfigCodec = t.interface({ + value: t.union([ThrottlingConfigValueCodec, t.null]), + label: t.string, + id: t.string, +}); + +export type ThrottlingConfig = t.TypeOf; + export const EncryptedBrowserAdvancedFieldsCodec = t.interface({ [ConfigKey.SCREENSHOTS]: t.string, [ConfigKey.JOURNEY_FILTERS_MATCH]: t.string, [ConfigKey.JOURNEY_FILTERS_TAGS]: t.array(t.string), [ConfigKey.IGNORE_HTTPS_ERRORS]: t.boolean, - [ConfigKey.IS_THROTTLING_ENABLED]: t.boolean, - [ConfigKey.DOWNLOAD_SPEED]: t.string, - [ConfigKey.UPLOAD_SPEED]: t.string, - [ConfigKey.LATENCY]: t.string, - [ConfigKey.THROTTLING_CONFIG]: t.string, + [ConfigKey.THROTTLING_CONFIG]: ThrottlingConfigCodec, }); export const BrowserSimpleFieldsCodec = t.intersection([ diff --git a/x-pack/plugins/synthetics/common/types/monitor_validation.ts b/x-pack/plugins/synthetics/common/types/monitor_validation.ts index 9d37a2d9b6104..09cf19cff4e48 100644 --- a/x-pack/plugins/synthetics/common/types/monitor_validation.ts +++ b/x-pack/plugins/synthetics/common/types/monitor_validation.ts @@ -5,10 +5,12 @@ * 2.0. */ -import { ConfigKey, MonitorFields } from '../runtime_types'; +import { ConfigKey, MonitorFields, ThrottlingConfig } from '../runtime_types'; -export type Validator = (config: Partial) => boolean; -export type NamespaceValidator = (config: Partial) => false | string; +export type Validator = (config: Partial) => boolean; +export type NamespaceValidator = ( + config: Partial +) => false | string; export type ConfigValidation = Omit, ConfigKey.NAMESPACE> & Record; diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/fields/throttling/connection_profile.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/fields/throttling/connection_profile.tsx new file mode 100644 index 0000000000000..83beb1e63939f --- /dev/null +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/fields/throttling/connection_profile.tsx @@ -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 { EuiFlexGroup, EuiFlexItem, EuiText } from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n-react'; +import React from 'react'; +import { PROFILE_VALUES_ENUM } from '../../../../../../../common/constants/monitor_defaults'; +import { ThrottlingConfig } from '../../../../../../../common/runtime_types'; + +export const ConnectionProfile = ({ + throttling, + id, +}: { + throttling?: ThrottlingConfig; + id: string; +}) => { + if (id === PROFILE_VALUES_ENUM.NO_THROTTLING) { + return ( + + + + + + + + ); + } + + if (throttling && throttling.value) { + const { label, value } = throttling; + + return ( + + + {label} + + + + + + + + ); + } else { + return ( + + + + + + + + + + + + + ); + } +}; diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/fields/throttling/throttling_config_field.test.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/fields/throttling/throttling_config_field.test.tsx new file mode 100644 index 0000000000000..f0dcb655749db --- /dev/null +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/fields/throttling/throttling_config_field.test.tsx @@ -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 React from 'react'; +import { fireEvent, screen } from '@testing-library/react'; +import { PROFILE_OPTIONS, ThrottlingConfigField } from './throttling_config_field'; +import { render } from '../../../../utils/testing'; +import { PROFILES_MAP } from '../../../../../../../common/constants/monitor_defaults'; + +describe('ThrottlingConfigField', () => { + it('renders', async () => { + render( + {}} + /> + ); + expect(await screen.findByText('(5 Mbps, 3 Mbps, 20 ms)')).toBeInTheDocument(); + }); + + it('selects custom values', async () => { + const onChange = jest.fn(); + render( + + ); + expect(await screen.findByText('(5 Mbps, 3 Mbps, 20 ms)')).toBeInTheDocument(); + fireEvent.click(screen.getByTestId('syntheticsThrottlingSelect')); + fireEvent.click(await screen.findByTestId('syntheticsThrottlingSelectCustom')); + + const customValue = { + id: 'custom', + label: 'Custom', + value: { download: '5', latency: '20', upload: '3' }, + }; + + expect(onChange).toHaveBeenCalledWith(customValue); + }); + + it('changes custom values', async () => { + const onChange = jest.fn(); + const customValue = { + id: 'custom', + label: 'Custom', + value: { download: '5', latency: '20', upload: '3' }, + }; + + render( + + ); + + fireEvent.input(screen.getByTestId('syntheticsBrowserUploadSpeed'), { + target: { value: '10' }, + }); + + expect(onChange).toHaveBeenCalledWith({ + ...customValue, + value: { ...customValue.value, upload: '10' }, + }); + }); +}); diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/fields/throttling/throttling_config_field.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/fields/throttling/throttling_config_field.tsx new file mode 100644 index 0000000000000..9635584e13673 --- /dev/null +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/fields/throttling/throttling_config_field.tsx @@ -0,0 +1,79 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import React from 'react'; +import { EuiSuperSelect } from '@elastic/eui'; +import { useConnectionProfiles } from './use_connection_profiles'; +import { ThrottlingDisabledCallout } from './throttling_disabled_callout'; +import { ThrottlingConfig } from '../../../../../../../common/runtime_types'; +import { ThrottlingFields } from './throttling_fields'; +import { PROFILE_VALUES_ENUM, PROFILE_VALUES, PROFILES_MAP, CUSTOM_LABEL } from '../../constants'; +import { ConnectionProfile } from './connection_profile'; + +export interface ThrottlingConfigFieldProps { + ariaLabel: string; + id: string; + onChange: (value: ThrottlingConfig) => void; + value: ThrottlingConfig; + placeholder?: string; + height?: string; + readOnly?: boolean; + disabled?: boolean; + fullWidth?: boolean; + options: typeof PROFILE_OPTIONS; + initialValue?: ThrottlingConfig; +} + +export const ThrottlingConfigField = (props: ThrottlingConfigFieldProps) => { + const { value, initialValue } = props; + + const isCustom = PROFILES_MAP[value?.id] === undefined; + + const isThrottlingDisabled = value?.id === PROFILE_VALUES_ENUM.NO_THROTTLING; + + const options = useConnectionProfiles(initialValue); + + return ( + <> + { + if (newValue === PROFILE_VALUES_ENUM.CUSTOM) { + props.onChange({ + ...PROFILES_MAP[PROFILE_VALUES_ENUM.DEFAULT], + id: PROFILE_VALUES_ENUM.CUSTOM, + label: CUSTOM_LABEL, + }); + } else { + props.onChange({ + ...PROFILES_MAP[PROFILE_VALUES_ENUM.DEFAULT], + ...PROFILES_MAP[newValue], + }); + } + }} + defaultValue={PROFILE_VALUES_ENUM.DEFAULT} + valueOfSelected={value?.id} + fullWidth={props.fullWidth} + readOnly={props.readOnly} + /> + {isThrottlingDisabled && } + {isCustom && ( + + )} + + ); +}; + +export const PROFILE_OPTIONS = PROFILE_VALUES.map(({ id }) => ({ + value: id, + inputDisplay: , + 'data-test-subj': `syntheticsThrottlingSelect-${id}`, +})); diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/fields/throttling/throttling_disabled_callout.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/fields/throttling/throttling_disabled_callout.tsx new file mode 100644 index 0000000000000..05d61b5b80d4d --- /dev/null +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/fields/throttling/throttling_disabled_callout.tsx @@ -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 { EuiCallOut, EuiSpacer } from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n-react'; +import React from 'react'; + +export const ThrottlingDisabledCallout = () => { + return ( + <> + + + } + color="warning" + iconType="warning" + > + + + + ); +}; diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/fields/throttling/throttling_download_field.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/fields/throttling/throttling_download_field.tsx new file mode 100644 index 0000000000000..918e5828cb70f --- /dev/null +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/fields/throttling/throttling_download_field.tsx @@ -0,0 +1,91 @@ +/* + * Copyright 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 { EuiFieldNumber, EuiFormRow, EuiText } from '@elastic/eui'; +import React from 'react'; +import { i18n } from '@kbn/i18n'; +import { Validation } from '../../../../../../../common/types'; +import { + BandwidthLimitKey, + ConfigKey, + DEFAULT_BANDWIDTH_LIMIT, + ThrottlingConfig, + ThrottlingConfigValue, +} from '../../../../../../../common/runtime_types'; +import { ThrottlingExceededMessage } from './throttling_exceeded_callout'; +import { OptionalLabel } from '../optional_label'; + +export const ThrottlingDownloadField = ({ + handleInputChange, + readOnly, + onFieldBlur, + validate, + throttling, + throttlingValue, +}: { + readOnly?: boolean; + handleInputChange: (value: string) => void; + onFieldBlur?: (field: keyof ThrottlingConfigValue) => void; + validate?: Validation; + throttling: ThrottlingConfig; + throttlingValue: ThrottlingConfigValue; +}) => { + const maxDownload = Number(DEFAULT_BANDWIDTH_LIMIT[BandwidthLimitKey.DOWNLOAD]); + + const exceedsDownloadLimits = Number(throttlingValue.download) > maxDownload; + + return ( + } + isInvalid={ + (validate ? !!validate?.[ConfigKey.THROTTLING_CONFIG]?.(throttling) : false) || + exceedsDownloadLimits + } + error={ + exceedsDownloadLimits ? ( + + ) : ( + DOWNLOAD_SPEED_ERROR + ) + } + > + { + handleInputChange(event.target.value); + }} + onBlur={() => onFieldBlur?.('download')} + data-test-subj="syntheticsBrowserDownloadSpeed" + append={ + + Mbps + + } + readOnly={readOnly} + /> + + ); +}; + +export const DOWNLOAD_LABEL = i18n.translate( + 'xpack.synthetics.createPackagePolicy.stepConfigure.browserAdvancedSettings.throttling.download.label', + { + defaultMessage: 'Download Speed', + } +); + +export const DOWNLOAD_SPEED_ERROR = i18n.translate( + 'xpack.synthetics.createPackagePolicy.stepConfigure.browserAdvancedSettings.throttling.download.error', + { + defaultMessage: 'Download speed must be greater than zero.', + } +); diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/fields/throttling/throttling_exceeded_callout.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/fields/throttling/throttling_exceeded_callout.tsx new file mode 100644 index 0000000000000..fe65ddb38a606 --- /dev/null +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/fields/throttling/throttling_exceeded_callout.tsx @@ -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 { EuiCallOut, EuiSpacer } from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n-react'; +import React from 'react'; + +export const ThrottlingExceededCallout = () => { + return ( + <> + + + } + color="warning" + iconType="warning" + > + + + + ); +}; + +export const ThrottlingExceededMessage = ({ + throttlingField, + limit, +}: { + throttlingField: string; + limit: number; +}) => { + return ( + + ); +}; diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/fields/throttling/throttling_fields.test.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/fields/throttling/throttling_fields.test.tsx new file mode 100644 index 0000000000000..9beffa5537cd1 --- /dev/null +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/fields/throttling/throttling_fields.test.tsx @@ -0,0 +1,86 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import React from 'react'; +import { fireEvent, screen } from '@testing-library/react'; +import { render } from '../../../../utils/testing'; +import { PROFILES_MAP } from '../../../../../../../common/constants/monitor_defaults'; +import { ThrottlingFields } from './throttling_fields'; + +describe('ThrottlingFields', () => { + it('renders', async () => { + render( {}} />); + + expect(await screen.findByText('Download Speed')).toBeInTheDocument(); + expect(await screen.findByText('Upload Speed')).toBeInTheDocument(); + expect(await screen.findByText('Latency')).toBeInTheDocument(); + }); + + it('calls setValue on change', async () => { + const setValue = jest.fn(); + render(); + + const throttling = PROFILES_MAP.default; + + const download = await screen.findByTestId('syntheticsBrowserDownloadSpeed'); + fireEvent.change(download, { target: { value: '10' } }); + expect(setValue).toHaveBeenCalledWith({ + ...throttling, + label: 'Custom', + id: 'custom', + value: { download: '10', latency: '20', upload: '3' }, + }); + + const upload = await screen.findByTestId('syntheticsBrowserUploadSpeed'); + fireEvent.change(upload, { target: { value: '10' } }); + expect(setValue).toHaveBeenLastCalledWith({ + ...throttling, + label: 'Custom', + id: 'custom', + value: { download: '5', latency: '20', upload: '10' }, + }); + + const latency = await screen.findByTestId('syntheticsBrowserLatency'); + fireEvent.change(latency, { target: { value: '10' } }); + expect(setValue).toHaveBeenLastCalledWith({ + ...throttling, + label: 'Custom', + id: 'custom', + value: { download: '5', latency: '10', upload: '3' }, + }); + }); + + it('shows maximum bandwidth callout on download and upload change', async () => { + const setValue = jest.fn(); + const throttling = PROFILES_MAP.default; + + render( + + ); + + expect( + await screen.findByText( + 'When using throttling values larger than a Synthetics Node bandwidth limit, your monitor will still have its bandwidth capped.' + ) + ).toBeInTheDocument(); + + expect( + await screen.findByText( + "You have exceeded the download limit for Synthetic Nodes. The download value can't be larger than 100Mbps." + ) + ).toBeInTheDocument(); + }); +}); diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/fields/throttling/throttling_fields.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/fields/throttling/throttling_fields.tsx new file mode 100644 index 0000000000000..886f482a86ed8 --- /dev/null +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/fields/throttling/throttling_fields.tsx @@ -0,0 +1,97 @@ +/* + * Copyright 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, { memo, useCallback } from 'react'; +import { EuiSpacer } from '@elastic/eui'; +import { ThrottlingUploadField } from './throttling_upload_field'; +import { ThrottlingExceededCallout } from './throttling_exceeded_callout'; +import { + BandwidthLimitKey, + DEFAULT_BANDWIDTH_LIMIT, + ThrottlingConfig, + ThrottlingConfigValue, +} from '../../../../../../../common/runtime_types'; +import { Validation } from '../../types'; +import { ThrottlingDisabledCallout } from './throttling_disabled_callout'; +import { ThrottlingDownloadField } from './throttling_download_field'; +import { ThrottlingLatencyField } from './throttling_latency_field'; +import { CUSTOM_LABEL, PROFILE_VALUES_ENUM } from '../../constants'; + +interface Props { + validate?: Validation; + minColumnWidth?: string; + onFieldBlur?: (field: keyof ThrottlingConfigValue) => void; + readOnly?: boolean; + throttling: ThrottlingConfig; + setValue: (value: ThrottlingConfig) => void; +} + +export const ThrottlingFields = memo( + ({ validate, onFieldBlur, setValue, readOnly = false, throttling }) => { + const maxDownload = DEFAULT_BANDWIDTH_LIMIT[BandwidthLimitKey.DOWNLOAD]; + const maxUpload = DEFAULT_BANDWIDTH_LIMIT[BandwidthLimitKey.UPLOAD]; + + const handleInputChange = useCallback( + ({ value, configKey }: { value: string; configKey: string }) => { + setValue({ + ...throttling, + value: { ...throttling.value!, [configKey]: value }, + label: CUSTOM_LABEL, + id: PROFILE_VALUES_ENUM.CUSTOM, + }); + }, + [setValue, throttling] + ); + + const exceedsDownloadLimits = Number(throttling.value?.download) > maxDownload; + const exceedsUploadLimits = Number(throttling.value?.upload) > maxUpload; + const isThrottlingEnabled = throttling.id !== PROFILE_VALUES_ENUM.NO_THROTTLING; + + const hasExceededLimits = isThrottlingEnabled && (exceedsDownloadLimits || exceedsUploadLimits); + + if (!isThrottlingEnabled || !throttling.value) { + return ; + } + + return ( +
+ {hasExceededLimits && } + + { + handleInputChange({ value: val, configKey: 'download' }); + }} + readOnly={readOnly} + /> + { + handleInputChange({ value: val, configKey: 'upload' }); + }} + readOnly={readOnly} + /> + { + handleInputChange({ value: val, configKey: 'latency' }); + }} + readOnly={readOnly} + /> +
+ ); + } +); diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/fields/throttling/throttling_latency_field.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/fields/throttling/throttling_latency_field.tsx new file mode 100644 index 0000000000000..59ebe3aa509cf --- /dev/null +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/fields/throttling/throttling_latency_field.tsx @@ -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 { EuiFieldNumber, EuiFormRow, EuiText } from '@elastic/eui'; +import React from 'react'; +import { i18n } from '@kbn/i18n'; +import { Validation } from '../../../../../../../common/types'; +import { + ConfigKey, + ThrottlingConfig, + ThrottlingConfigValue, +} from '../../../../../../../common/runtime_types'; +import { OptionalLabel } from '../optional_label'; + +export const ThrottlingLatencyField = ({ + throttling, + readOnly, + onFieldBlur, + validate, + handleInputChange, + throttlingValue, +}: { + readOnly?: boolean; + handleInputChange: (value: string) => void; + onFieldBlur?: (field: keyof ThrottlingConfigValue) => void; + validate?: Validation; + throttling: ThrottlingConfig; + throttlingValue: ThrottlingConfigValue; +}) => { + return ( + } + isInvalid={validate ? !!validate?.[ConfigKey.THROTTLING_CONFIG]?.(throttling) : false} + error={LATENCY_NEGATIVE_ERROR} + > + handleInputChange(event.target.value)} + onBlur={() => onFieldBlur?.('latency')} + data-test-subj="syntheticsBrowserLatency" + append={ + + ms + + } + readOnly={readOnly} + /> + + ); +}; + +export const LATENCY_LABEL = i18n.translate( + 'xpack.synthetics.createPackagePolicy.stepConfigure.browserAdvancedSettings.throttling.latency.label', + { + defaultMessage: 'Latency', + } +); + +export const LATENCY_NEGATIVE_ERROR = i18n.translate( + 'xpack.synthetics.createPackagePolicy.stepConfigure.browserAdvancedSettings.throttling.latency.error', + { + defaultMessage: 'Latency must not be negative.', + } +); diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/fields/throttling/throttling_upload_field.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/fields/throttling/throttling_upload_field.tsx new file mode 100644 index 0000000000000..8e245ba44c2a6 --- /dev/null +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/fields/throttling/throttling_upload_field.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 { EuiFieldNumber, EuiFormRow, EuiText } from '@elastic/eui'; +import React from 'react'; +import { i18n } from '@kbn/i18n'; +import { Validation } from '../../../../../../../common/types'; +import { + BandwidthLimitKey, + ConfigKey, + DEFAULT_BANDWIDTH_LIMIT, + ThrottlingConfig, + ThrottlingConfigValue, +} from '../../../../../../../common/runtime_types'; +import { ThrottlingExceededMessage } from './throttling_exceeded_callout'; +import { OptionalLabel } from '../optional_label'; + +export const ThrottlingUploadField = ({ + readOnly, + onFieldBlur, + throttling, + validate, + handleInputChange, + throttlingValue, +}: { + readOnly?: boolean; + handleInputChange: (value: string) => void; + onFieldBlur?: (field: keyof ThrottlingConfigValue) => void; + validate?: Validation; + throttling: ThrottlingConfig; + throttlingValue: ThrottlingConfigValue; +}) => { + const maxUpload = Number(DEFAULT_BANDWIDTH_LIMIT[BandwidthLimitKey.UPLOAD]); + + const exceedsUploadLimits = Number(throttlingValue.upload) > maxUpload; + + return ( + } + isInvalid={ + (validate ? !!validate?.[ConfigKey.THROTTLING_CONFIG]?.(throttling) : false) || + exceedsUploadLimits + } + error={ + exceedsUploadLimits ? ( + + ) : ( + UPLOAD_SPEED_ERROR + ) + } + > + handleInputChange(event.target.value)} + onBlur={() => onFieldBlur?.('upload')} + data-test-subj="syntheticsBrowserUploadSpeed" + append={ + + Mbps + + } + readOnly={readOnly} + /> + + ); +}; + +export const UPLOAD_LABEL = i18n.translate( + 'xpack.synthetics.createPackagePolicy.stepConfigure.browserAdvancedSettings.throttling.upload.label', + { defaultMessage: 'Upload Speed' } +); + +export const UPLOAD_SPEED_ERROR = i18n.translate( + 'xpack.synthetics.createPackagePolicy.stepConfigure.browserAdvancedSettings.throttling.upload.error', + { + defaultMessage: 'Upload speed must be greater than zero.', + } +); diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/fields/throttling/use_connection_profiles.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/fields/throttling/use_connection_profiles.tsx new file mode 100644 index 0000000000000..c65fb046bbe59 --- /dev/null +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/fields/throttling/use_connection_profiles.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, { useMemo } from 'react'; +import { PROFILE_VALUES_ENUM } from '../../../../../../../common/constants/monitor_defaults'; +import { ConnectionProfile } from './connection_profile'; +import { PROFILE_OPTIONS } from './throttling_config_field'; +import { ThrottlingConfig } from '../../../../../../../common/runtime_types'; + +export const useConnectionProfiles = (initialValue?: ThrottlingConfig) => { + return useMemo(() => { + return [ + ...PROFILE_OPTIONS, + { + value: PROFILE_VALUES_ENUM.CUSTOM, + inputDisplay: ( + + ), + 'data-test-subj': 'syntheticsThrottlingSelectCustom', + }, + ]; + }, [initialValue]); +}; diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/form/defaults.test.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/form/defaults.test.tsx index 70521c28983f7..c2090eb0f47b0 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/form/defaults.test.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/form/defaults.test.tsx @@ -5,7 +5,7 @@ * 2.0. */ import { ConfigKey, DataStream, FormMonitorType, SyntheticsMonitor } from '../types'; -import { DEFAULT_FIELDS } from '../constants'; +import { DEFAULT_FIELDS, PROFILE_VALUES_ENUM, PROFILES_MAP } from '../constants'; import { formatDefaultFormValues } from './defaults'; describe('defaults', () => { @@ -52,11 +52,15 @@ describe('defaults', () => { 'ssl.verification_mode': 'full', synthetics_args: [], tags: [], - 'throttling.config': '5d/3u/20l', - 'throttling.download_speed': '5', - 'throttling.is_enabled': true, - 'throttling.latency': '20', - 'throttling.upload_speed': '3', + throttling: { + value: { + download: '5', + latency: '20', + upload: '3', + }, + id: 'default', + label: 'Default', + }, timeout: '16', type: 'browser', 'url.port': null, @@ -114,11 +118,7 @@ describe('defaults', () => { 'ssl.verification_mode': 'full', synthetics_args: [], tags: [], - 'throttling.config': '5d/3u/20l', - 'throttling.download_speed': '5', - 'throttling.is_enabled': true, - 'throttling.latency': '20', - 'throttling.upload_speed': '3', + throttling: PROFILES_MAP[PROFILE_VALUES_ENUM.DEFAULT], timeout: '16', type: 'browser', 'url.port': null, diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/form/field_config.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/form/field_config.tsx index b0de4f812da1a..0980ef1aa961d 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/form/field_config.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/form/field_config.tsx @@ -14,8 +14,6 @@ import { EuiComboBoxOptionOption, EuiFlexGroup, EuiFlexItem, - EuiSuperSelect, - EuiText, EuiLink, EuiTextArea, EuiSelectProps, @@ -27,10 +25,13 @@ import { EuiCheckboxProps, EuiTextAreaProps, EuiButtonGroupProps, - EuiSuperSelectProps, EuiHighlight, EuiBadge, } from '@elastic/eui'; +import { + PROFILE_OPTIONS, + ThrottlingConfigFieldProps, +} from '../fields/throttling/throttling_config_field'; import { FieldText, FieldNumber, @@ -53,6 +54,7 @@ import { ResponseBodyIndexField, ResponseBodyIndexFieldProps, ControlledFieldProp, + ThrottlingWrapper, } from './field_wrappers'; import { getDocLinks } from '../../../../../kibana_services'; import { useMonitorName } from '../hooks/use_monitor_name'; @@ -67,12 +69,9 @@ import { VerificationMode, FieldMap, FormLocation, + ThrottlingConfig, } from '../types'; -import { - AlertConfigKey, - DEFAULT_BROWSER_ADVANCED_FIELDS, - ALLOWED_SCHEDULES_IN_MINUTES, -} from '../constants'; +import { AlertConfigKey, ALLOWED_SCHEDULES_IN_MINUTES } from '../constants'; import { getDefaultFormFields } from './defaults'; import { validate, validateHeaders, WHOLE_NUMBERS_ONLY, FLOATS_ONLY } from './validation'; @@ -1123,41 +1122,23 @@ export const FIELD = (readOnly?: boolean): FieldMap => ({ }, [ConfigKey.THROTTLING_CONFIG]: { fieldKey: ConfigKey.THROTTLING_CONFIG, - component: EuiSuperSelect, + component: ThrottlingWrapper, label: i18n.translate('xpack.synthetics.monitorConfig.throttling.label', { defaultMessage: 'Connection profile', }), required: true, controlled: true, helpText: i18n.translate('xpack.synthetics.monitorConfig.throttling.helpText', { - defaultMessage: - 'Simulate network throttling (download, upload, latency). More options will be added in a future version.', - }), - props: (): EuiSuperSelectProps => ({ - options: [ - { - value: DEFAULT_BROWSER_ADVANCED_FIELDS[ConfigKey.THROTTLING_CONFIG], - inputDisplay: ( - - - - {i18n.translate('xpack.synthetics.monitorConfig.throttling.options.default', { - defaultMessage: 'Default', - })} - - - - - {'(5 Mbps, 3 Mbps, 20 ms)'} - - - - ), - }, - ], - readOnly, - disabled: true, // currently disabled through 1.0 until we define connection profiles + defaultMessage: 'Simulate network throttling (download, upload, latency).', }), + props: ({ formState }): Partial => { + return { + options: PROFILE_OPTIONS, + readOnly, + disabled: false, + initialValue: formState.defaultValues?.[ConfigKey.THROTTLING_CONFIG] as ThrottlingConfig, + }; + }, validation: () => ({ required: true, }), diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/form/field_wrappers.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/form/field_wrappers.tsx index 80455d8dd4e4d..27a8a3ee5ab4d 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/form/field_wrappers.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/form/field_wrappers.tsx @@ -26,6 +26,10 @@ import { EuiComboBox, EuiComboBoxProps, } from '@elastic/eui'; +import { + ThrottlingConfigField, + ThrottlingConfigFieldProps, +} from '../fields/throttling/throttling_config_field'; import { SourceField, SourceFieldProps } from '../fields/source_field'; import { FormattedComboBox as DefaultFormattedComboBox, @@ -132,3 +136,7 @@ export const RequestBodyField = React.forwardRef( (props, _ref) => ); + +export const ThrottlingWrapper = React.forwardRef( + (props, _ref) => +); diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/form/form_config.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/form/form_config.tsx index a33f0e14b7777..46a482512413a 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/form/form_config.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/form/form_config.tsx @@ -121,6 +121,7 @@ export const BROWSER_ADVANCED = (readOnly: boolean) => [ } ), components: [ + FIELD(readOnly)[ConfigKey.THROTTLING_CONFIG], FIELD(readOnly)[ConfigKey.IGNORE_HTTPS_ERRORS], FIELD(readOnly)[ConfigKey.SYNTHETICS_ARGS], FIELD(readOnly)[ConfigKey.PLAYWRIGHT_OPTIONS], @@ -209,7 +210,6 @@ export const FORM_CONFIG = (readOnly: boolean): FieldConfig => ({ FIELD(readOnly)[ConfigKey.NAME], FIELD(readOnly)[ConfigKey.LOCATIONS], FIELD(readOnly)[`${ConfigKey.SCHEDULE}.number`], - FIELD(readOnly)[ConfigKey.THROTTLING_CONFIG], FIELD(readOnly)[ConfigKey.ENABLED], FIELD(readOnly)[AlertConfigKey.STATUS_ENABLED], ], @@ -236,7 +236,6 @@ export const FORM_CONFIG = (readOnly: boolean): FieldConfig => ({ FIELD(readOnly)[ConfigKey.TEXT_ASSERTION], FIELD(readOnly)[ConfigKey.LOCATIONS], FIELD(readOnly)[`${ConfigKey.SCHEDULE}.number`], - FIELD(readOnly)[ConfigKey.THROTTLING_CONFIG], FIELD(readOnly)[ConfigKey.ENABLED], FIELD(readOnly)[AlertConfigKey.STATUS_ENABLED], ], diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/form/formatter.test.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/form/formatter.test.tsx index 6000b02cb7f87..0464c36ba2429 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/form/formatter.test.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/form/formatter.test.tsx @@ -7,7 +7,11 @@ import { format, ALLOWED_FIELDS } from './formatter'; import { DataStream } from '../../../../../../common/runtime_types'; -import { DEFAULT_FIELDS } from '../../../../../../common/constants/monitor_defaults'; +import { + DEFAULT_FIELDS, + PROFILE_VALUES_ENUM, + PROFILES_MAP, +} from '../../../../../../common/constants/monitor_defaults'; describe('format', () => { let formValues: Record; @@ -199,11 +203,6 @@ describe('format', () => { 'filter_journeys.match': '', 'filter_journeys.tags': [], ignore_https_errors: false, - 'throttling.is_enabled': true, - 'throttling.download_speed': '5', - 'throttling.upload_speed': '3', - 'throttling.latency': '20', - 'throttling.config': '5d/3u/20l', 'ssl.certificate_authorities': '', 'ssl.certificate': '', 'ssl.key': '', @@ -215,9 +214,7 @@ describe('format', () => { script: '', fileName: '', }, - throttling: { - config: '5d/3u/20l', - }, + throttling: PROFILES_MAP[PROFILE_VALUES_ENUM.DEFAULT], source: { inline: { type: scriptType, @@ -278,11 +275,6 @@ describe('format', () => { 'ssl.verification_mode': 'full', synthetics_args: [], tags: [], - 'throttling.config': '5d/3u/20l', - 'throttling.download_speed': '5', - 'throttling.is_enabled': true, - 'throttling.latency': '20', - 'throttling.upload_speed': '3', timeout: '16', type: 'browser', 'url.port': null, diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/form/validation.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/form/validation.tsx index cc03228d755be..332ee8e6fbc55 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/form/validation.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/form/validation.tsx @@ -148,11 +148,15 @@ const validateThrottleValue = (speed: string | undefined, allowZero?: boolean) = const validateBrowser: ValidationLibrary = { ...validateCommon, [ConfigKey.SOURCE_INLINE]: ({ [ConfigKey.SOURCE_INLINE]: inlineScript }) => !inlineScript, - [ConfigKey.DOWNLOAD_SPEED]: ({ [ConfigKey.DOWNLOAD_SPEED]: downloadSpeed }) => - validateThrottleValue(downloadSpeed), - [ConfigKey.UPLOAD_SPEED]: ({ [ConfigKey.UPLOAD_SPEED]: uploadSpeed }) => - validateThrottleValue(uploadSpeed), - [ConfigKey.LATENCY]: ({ [ConfigKey.LATENCY]: latency }) => validateThrottleValue(latency, true), + [ConfigKey.THROTTLING_CONFIG]: ({ throttling }) => { + if (!throttling || throttling.value === null) return true; + const { download, upload, latency } = throttling.value; + return ( + validateThrottleValue(String(download)) || + validateThrottleValue(String(upload)) || + validateThrottleValue(String(latency), true) + ); + }, [ConfigKey.PLAYWRIGHT_OPTIONS]: ({ [ConfigKey.PLAYWRIGHT_OPTIONS]: playwrightOptions }) => playwrightOptions ? !validJSONFormat(playwrightOptions) : false, [ConfigKey.PARAMS]: ({ [ConfigKey.PARAMS]: params }) => diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/monitor_edit_page.test.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/monitor_edit_page.test.tsx index bd9bc9d57c4aa..6b45a7d3920c2 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/monitor_edit_page.test.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/monitor_edit_page.test.tsx @@ -12,6 +12,10 @@ import { MonitorEditPage } from './monitor_edit_page'; import { ConfigKey } from '../../../../../common/runtime_types'; import * as observabilityPublic from '@kbn/observability-plugin/public'; +import { + PROFILE_VALUES_ENUM, + PROFILES_MAP, +} from '../../../../../common/constants/monitor_defaults'; mockGlobals(); @@ -45,6 +49,7 @@ describe('MonitorEditPage', () => { [ConfigKey.MONITOR_SOURCE_TYPE]: 'ui', [ConfigKey.FORM_MONITOR_TYPE]: 'multistep', [ConfigKey.LOCATIONS]: [], + [ConfigKey.THROTTLING_CONFIG]: PROFILES_MAP[PROFILE_VALUES_ENUM.DEFAULT], }, }, refetch: () => null, diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/utils/testing/__mocks__/synthetics_store.mock.ts b/x-pack/plugins/synthetics/public/apps/synthetics/utils/testing/__mocks__/synthetics_store.mock.ts index d65c68f2843b9..06ed54738fbda 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/utils/testing/__mocks__/synthetics_store.mock.ts +++ b/x-pack/plugins/synthetics/public/apps/synthetics/utils/testing/__mocks__/synthetics_store.mock.ts @@ -430,11 +430,15 @@ function getMonitorDetailsMockSlice() { 'filter_journeys.match': '', 'filter_journeys.tags': [], ignore_https_errors: false, - 'throttling.is_enabled': true, - 'throttling.download_speed': '5', - 'throttling.upload_speed': '3', - 'throttling.latency': '20', - 'throttling.config': '5d/3u/20l', + throttling: { + value: { + download: '5', + upload: '3', + latency: '20', + }, + label: 'Regular 3G', + id: 'three_g', + }, 'ssl.certificate_authorities': '', 'ssl.certificate': '', 'ssl.key': '', diff --git a/x-pack/plugins/synthetics/server/legacy_uptime/lib/saved_objects/migrations/monitors/8.8.0.test.ts b/x-pack/plugins/synthetics/server/legacy_uptime/lib/saved_objects/migrations/monitors/8.8.0.test.ts index a682252be3c79..65768459dc96e 100644 --- a/x-pack/plugins/synthetics/server/legacy_uptime/lib/saved_objects/migrations/monitors/8.8.0.test.ts +++ b/x-pack/plugins/synthetics/server/legacy_uptime/lib/saved_objects/migrations/monitors/8.8.0.test.ts @@ -8,7 +8,11 @@ import { encryptedSavedObjectsMock } from '@kbn/encrypted-saved-objects-plugin/s import { migration880 } from './8.8.0'; import { migrationMocks } from '@kbn/core/server/mocks'; import { ConfigKey, ScheduleUnit } from '../../../../../../common/runtime_types'; -import { ALLOWED_SCHEDULES_IN_MINUTES } from '../../../../../../common/constants/monitor_defaults'; +import { + ALLOWED_SCHEDULES_IN_MINUTES, + PROFILE_VALUES_ENUM, + PROFILES_MAP, +} from '../../../../../../common/constants/monitor_defaults'; import { browserUI, browserProject, @@ -18,6 +22,8 @@ import { httpUptimeUI, } from './test_fixtures/8.7.0'; import { httpUI as httpUI850 } from './test_fixtures/8.5.0'; +import { LegacyConfigKey } from '../../../../../../common/constants/monitor_management'; +import { omit } from 'lodash'; const context = migrationMocks.createContext(); const encryptedSavedObjectsSetup = encryptedSavedObjectsMock.createSetup(); @@ -151,11 +157,7 @@ describe('Monitor migrations v8.7.0 -> v8.8.0', () => { 'ssl.supported_protocols': ['TLSv1.1', 'TLSv1.2', 'TLSv1.3'], 'ssl.verification_mode': 'full', tags: [], - 'throttling.config': '5d/3u/20l', - 'throttling.download_speed': '5', - 'throttling.is_enabled': true, - 'throttling.latency': '20', - 'throttling.upload_speed': '3', + throttling: PROFILES_MAP[PROFILE_VALUES_ENUM.DEFAULT], timeout: null, type: 'browser', 'url.port': null, @@ -195,9 +197,24 @@ describe('Monitor migrations v8.7.0 -> v8.8.0', () => { name: null, }, }; - // @ts-ignore specificially testing monitors with invalid values + // @ts-ignore specifically testing monitors with invalid values const actual = migration880(encryptedSavedObjectsSetup)(invalidTestMonitor, context); - expect(actual).toEqual(invalidTestMonitor); + expect(actual).toEqual({ + ...invalidTestMonitor, + attributes: omit( + { + ...invalidTestMonitor.attributes, + throttling: PROFILES_MAP[PROFILE_VALUES_ENUM.DEFAULT], + }, + [ + LegacyConfigKey.THROTTLING_CONFIG, + LegacyConfigKey.IS_THROTTLING_ENABLED, + LegacyConfigKey.DOWNLOAD_SPEED, + LegacyConfigKey.UPLOAD_SPEED, + LegacyConfigKey.LATENCY, + ] + ), + }); }); }); @@ -413,4 +430,77 @@ describe('Monitor migrations v8.7.0 -> v8.8.0', () => { expect(actual.attributes[ConfigKey.SCHEDULE].unit).toEqual(ScheduleUnit.MINUTES); }); }); + + describe('throttling migration', () => { + it('handles migrating with enabled throttling', () => { + const actual = migration880(encryptedSavedObjectsSetup)(browserUI, context); + // @ts-ignore + expect(actual.attributes[ConfigKey.THROTTLING_CONFIG]).toEqual( + PROFILES_MAP[PROFILE_VALUES_ENUM.DEFAULT] + ); + }); + + it('handles migrating with defined throttling value', () => { + const testMonitor = { + ...browserUI, + attributes: { + ...browserUI.attributes, + [LegacyConfigKey.UPLOAD_SPEED]: '0.75', + [LegacyConfigKey.DOWNLOAD_SPEED]: '9', + [LegacyConfigKey.LATENCY]: '170', + }, + }; + const actual = migration880(encryptedSavedObjectsSetup)(testMonitor, context); + // @ts-ignore + expect(actual.attributes[ConfigKey.THROTTLING_CONFIG]).toEqual({ + id: '4g', + label: '4G', + value: { + download: '9', + upload: '0.75', + latency: '170', + }, + }); + }); + + it('handles migrating with custom throttling value', () => { + const testMonitor = { + ...browserUI, + attributes: { + ...browserUI.attributes, + [LegacyConfigKey.UPLOAD_SPEED]: '5', + [LegacyConfigKey.DOWNLOAD_SPEED]: '10', + [LegacyConfigKey.LATENCY]: '30', + }, + }; + const actual = migration880(encryptedSavedObjectsSetup)(testMonitor, context); + // @ts-ignore + expect(actual.attributes[ConfigKey.THROTTLING_CONFIG]).toEqual({ + id: 'custom', + label: 'Custom', + value: { + download: '10', + upload: '5', + latency: '30', + }, + }); + }); + + it('handles migrating with disabled throttling', () => { + const testMonitor = { + ...browserUI, + attributes: { + ...browserUI.attributes, + [LegacyConfigKey.IS_THROTTLING_ENABLED]: false, + }, + }; + const actual = migration880(encryptedSavedObjectsSetup)(testMonitor, context); + // @ts-ignore + expect(actual.attributes[ConfigKey.THROTTLING_CONFIG]).toEqual({ + id: 'no-throttling', + label: 'No throttling', + value: null, + }); + }); + }); }); diff --git a/x-pack/plugins/synthetics/server/legacy_uptime/lib/saved_objects/migrations/monitors/8.8.0.ts b/x-pack/plugins/synthetics/server/legacy_uptime/lib/saved_objects/migrations/monitors/8.8.0.ts index 7df6eb0c143cf..426984969c191 100644 --- a/x-pack/plugins/synthetics/server/legacy_uptime/lib/saved_objects/migrations/monitors/8.8.0.ts +++ b/x-pack/plugins/synthetics/server/legacy_uptime/lib/saved_objects/migrations/monitors/8.8.0.ts @@ -6,23 +6,31 @@ */ import { omit } from 'lodash'; import { EncryptedSavedObjectsPluginSetup } from '@kbn/encrypted-saved-objects-plugin/server'; -import { SavedObjectUnsanitizedDoc } from '@kbn/core/server'; +import { SavedObjectMigrationContext, SavedObjectUnsanitizedDoc } from '@kbn/core/server'; +import { LegacyConfigKey } from '../../../../../../common/constants/monitor_management'; import { + BrowserFields, ConfigKey, - SyntheticsMonitorWithSecrets, MonitorFields, - BrowserFields, ScheduleUnit, + SyntheticsMonitorWithSecrets, + ThrottlingConfig, } from '../../../../../../common/runtime_types'; -import { ALLOWED_SCHEDULES_IN_MINUTES } from '../../../../../../common/constants/monitor_defaults'; +import { + ALLOWED_SCHEDULES_IN_MINUTES, + CUSTOM_LABEL, + PROFILE_VALUES, + PROFILE_VALUES_ENUM, + PROFILES_MAP, +} from '../../../../../../common/constants/monitor_defaults'; import { LEGACY_SYNTHETICS_MONITOR_ENCRYPTED_TYPE, SYNTHETICS_MONITOR_ENCRYPTED_TYPE, } from '../../synthetics_monitor'; import { validateMonitor } from '../../../../../routes/monitor_cruds/monitor_validation'; import { - normalizeMonitorSecretAttributes, formatSecrets, + normalizeMonitorSecretAttributes, } from '../../../../../synthetics_service/utils/secrets'; export const migration880 = (encryptedSavedObjects: EncryptedSavedObjectsPluginSetup) => { @@ -61,6 +69,8 @@ export const migration880 = (encryptedSavedObjects: EncryptedSavedObjectsPluginS }, }; if (migrated.attributes.type === 'browser') { + migrated = updateThrottlingFields(migrated, logger); + try { const normalizedMonitorAttributes = normalizeMonitorSecretAttributes(migrated.attributes); migrated = { @@ -118,3 +128,103 @@ const omitZipUrlFields = (fields: BrowserFields) => { return formatSecrets(validationResult.decodedMonitor); }; + +const updateThrottlingFields = ( + doc: SavedObjectUnsanitizedDoc< + SyntheticsMonitorWithSecrets & + Partial<{ + [LegacyConfigKey.THROTTLING_CONFIG]: string; + [LegacyConfigKey.IS_THROTTLING_ENABLED]: boolean; + [LegacyConfigKey.DOWNLOAD_SPEED]: string; + [LegacyConfigKey.UPLOAD_SPEED]: string; + [LegacyConfigKey.LATENCY]: string; + [ConfigKey.THROTTLING_CONFIG]: ThrottlingConfig; + }> + >, + logger: SavedObjectMigrationContext +) => { + try { + const { attributes } = doc; + + const migrated = { + ...doc, + attributes: { + ...doc.attributes, + [ConfigKey.CONFIG_HASH]: '', + }, + }; + + const isThrottlingEnabled = attributes[LegacyConfigKey.IS_THROTTLING_ENABLED]; + if (isThrottlingEnabled) { + const defaultProfileValue = PROFILES_MAP[PROFILE_VALUES_ENUM.DEFAULT].value!; + + const download = + String(attributes[LegacyConfigKey.DOWNLOAD_SPEED]) || defaultProfileValue.download; + const upload = String(attributes[LegacyConfigKey.UPLOAD_SPEED]) || defaultProfileValue.upload; + const latency = String(attributes[LegacyConfigKey.LATENCY]) || defaultProfileValue.latency; + + migrated.attributes[ConfigKey.THROTTLING_CONFIG] = getMatchingThrottlingConfig( + download, + upload, + latency + ); + } else { + migrated.attributes[ConfigKey.THROTTLING_CONFIG] = + PROFILES_MAP[PROFILE_VALUES_ENUM.NO_THROTTLING]; + } + + // filter out legacy throttling fields + return { + ...migrated, + attributes: omit(migrated.attributes, [ + LegacyConfigKey.THROTTLING_CONFIG, + LegacyConfigKey.IS_THROTTLING_ENABLED, + LegacyConfigKey.DOWNLOAD_SPEED, + LegacyConfigKey.UPLOAD_SPEED, + LegacyConfigKey.LATENCY, + ]), + }; + } catch (e) { + logger.log.warn( + `Failed to migrate throttling fields from legacy Synthetics monitor: ${e.message}` + ); + const { attributes } = doc; + + attributes[ConfigKey.THROTTLING_CONFIG] = PROFILES_MAP[PROFILE_VALUES_ENUM.DEFAULT]; + + return { + ...doc, + attributes: omit(attributes, [ + LegacyConfigKey.THROTTLING_CONFIG, + LegacyConfigKey.IS_THROTTLING_ENABLED, + LegacyConfigKey.DOWNLOAD_SPEED, + LegacyConfigKey.UPLOAD_SPEED, + LegacyConfigKey.LATENCY, + ]), + }; + } +}; + +const getMatchingThrottlingConfig = (download: string, upload: string, latency: string) => { + const matchedProfile = PROFILE_VALUES.find(({ value }) => { + return ( + Number(value?.download) === Number(download) && + Number(value?.upload) === Number(upload) && + Number(value?.latency) === Number(latency) + ); + }); + + if (matchedProfile) { + return matchedProfile; + } else { + return { + id: PROFILE_VALUES_ENUM.CUSTOM, + label: CUSTOM_LABEL, + value: { + download: String(download), + upload: String(upload), + latency: String(latency), + }, + }; + } +}; diff --git a/x-pack/plugins/synthetics/server/legacy_uptime/lib/saved_objects/synthetics_monitor.ts b/x-pack/plugins/synthetics/server/legacy_uptime/lib/saved_objects/synthetics_monitor.ts index 3d5ddecf188a5..c8aa68f478703 100644 --- a/x-pack/plugins/synthetics/server/legacy_uptime/lib/saved_objects/synthetics_monitor.ts +++ b/x-pack/plugins/synthetics/server/legacy_uptime/lib/saved_objects/synthetics_monitor.ts @@ -170,6 +170,13 @@ export const getSyntheticsMonitorSavedObjectType = ( }, }, }, + throttling: { + properties: { + label: { + type: 'keyword', + }, + }, + }, }, }, management: { diff --git a/x-pack/plugins/synthetics/server/routes/monitor_cruds/add_monitor_project.ts b/x-pack/plugins/synthetics/server/routes/monitor_cruds/add_monitor_project.ts index 59f798b5bc623..b2e4d050e4d7c 100644 --- a/x-pack/plugins/synthetics/server/routes/monitor_cruds/add_monitor_project.ts +++ b/x-pack/plugins/synthetics/server/routes/monitor_cruds/add_monitor_project.ts @@ -78,7 +78,7 @@ export const addSyntheticsProjectMonitorRoute: SyntheticsRestApiRouteFactory = ( }; } catch (error) { server.logger.error(`Error adding monitors to project ${decodedProjectName}`); - if (error.output.statusCode === 404) { + if (error.output?.statusCode === 404) { const spaceId = server.spaces?.spacesService.getSpaceId(request) ?? DEFAULT_SPACE_ID; return response.notFound({ body: { message: `Kibana space '${spaceId}' does not exist` } }); } diff --git a/x-pack/plugins/synthetics/server/routes/monitor_cruds/monitor_validation.test.ts b/x-pack/plugins/synthetics/server/routes/monitor_cruds/monitor_validation.test.ts index 7678cfc54615e..12fe8306d1731 100644 --- a/x-pack/plugins/synthetics/server/routes/monitor_cruds/monitor_validation.test.ts +++ b/x-pack/plugins/synthetics/server/routes/monitor_cruds/monitor_validation.test.ts @@ -175,11 +175,15 @@ describe('validateMonitor', () => { [ConfigKey.JOURNEY_FILTERS_MATCH]: 'false', [ConfigKey.JOURNEY_FILTERS_TAGS]: testTags, [ConfigKey.IGNORE_HTTPS_ERRORS]: false, - [ConfigKey.IS_THROTTLING_ENABLED]: true, - [ConfigKey.DOWNLOAD_SPEED]: '5', - [ConfigKey.UPLOAD_SPEED]: '3', - [ConfigKey.LATENCY]: '20', - [ConfigKey.THROTTLING_CONFIG]: '5d/3u/20l', + [ConfigKey.THROTTLING_CONFIG]: { + value: { + download: '5', + upload: '3', + latency: '20', + }, + id: 'test', + label: 'test', + }, }; testBrowserFields = { diff --git a/x-pack/plugins/synthetics/server/synthetics_service/formatters/browser.ts b/x-pack/plugins/synthetics/server/synthetics_service/formatters/browser.ts index 391556bb99649..75fa3da2dcd3d 100644 --- a/x-pack/plugins/synthetics/server/synthetics_service/formatters/browser.ts +++ b/x-pack/plugins/synthetics/server/synthetics_service/formatters/browser.ts @@ -15,21 +15,21 @@ import { arrayFormatter, objectFormatter, stringToObjectFormatter } from './form export type BrowserFormatMap = Record; const throttlingFormatter: Formatter = (fields) => { - if (!fields[ConfigKey.IS_THROTTLING_ENABLED]) return false; + const value = fields[ConfigKey.THROTTLING_CONFIG]; + const defaultThrottling = DEFAULT_BROWSER_ADVANCED_FIELDS[ConfigKey.THROTTLING_CONFIG].value; + + const thValue = value?.value; + + if (!thValue || !defaultThrottling) return false; + + if (thValue?.download === '0' && thValue?.upload === '0' && thValue?.latency === '0') + return false; + if (value?.label === 'no-throttling') return false; return { - download: parseInt( - fields[ConfigKey.DOWNLOAD_SPEED] || DEFAULT_BROWSER_ADVANCED_FIELDS[ConfigKey.DOWNLOAD_SPEED], - 10 - ), - upload: parseInt( - fields[ConfigKey.UPLOAD_SPEED] || DEFAULT_BROWSER_ADVANCED_FIELDS[ConfigKey.UPLOAD_SPEED], - 10 - ), - latency: parseInt( - fields[ConfigKey.LATENCY] || DEFAULT_BROWSER_ADVANCED_FIELDS[ConfigKey.LATENCY], - 10 - ), + download: Number(thValue?.download ?? defaultThrottling.download), + upload: Number(thValue?.upload ?? defaultThrottling.upload), + latency: Number(thValue?.latency ?? defaultThrottling.latency), }; }; diff --git a/x-pack/plugins/synthetics/server/synthetics_service/formatters/format_configs.test.ts b/x-pack/plugins/synthetics/server/synthetics_service/formatters/format_configs.test.ts index eb2b40e324249..e5ff19ddaf1c0 100644 --- a/x-pack/plugins/synthetics/server/synthetics_service/formatters/format_configs.test.ts +++ b/x-pack/plugins/synthetics/server/synthetics_service/formatters/format_configs.test.ts @@ -73,11 +73,15 @@ const testBrowserConfig: Partial = { 'filter_journeys.match': '', 'filter_journeys.tags': ['dev'], ignore_https_errors: false, - 'throttling.is_enabled': true, - 'throttling.download_speed': '5', - 'throttling.upload_speed': '3', - 'throttling.latency': '20', - 'throttling.config': '5d/3u/20l', + throttling: { + value: { + download: '5', + latency: '20', + upload: '3', + }, + id: 'default', + label: 'default', + }, project_id: 'test-project', }; @@ -203,12 +207,16 @@ describe('browser fields', () => { }); it('excludes UI fields', () => { - testBrowserConfig['throttling.is_enabled'] = false; - testBrowserConfig['throttling.upload_speed'] = '3'; - const formattedConfig = formatMonitorConfigFields( Object.keys(testBrowserConfig) as ConfigKey[], - testBrowserConfig, + { + ...testBrowserConfig, + throttling: { + value: null, + label: 'no-throttling', + id: 'no-throttling', + }, + }, logger, { proxyUrl: 'https://www.google.com' } ); @@ -216,8 +224,6 @@ describe('browser fields', () => { const expected = { ...formattedConfig, throttling: false, - 'throttling.is_enabled': undefined, - 'throttling.upload_speed': undefined, }; expect(formattedConfig).toEqual(expected); diff --git a/x-pack/plugins/synthetics/server/synthetics_service/formatters/format_configs.ts b/x-pack/plugins/synthetics/server/synthetics_service/formatters/format_configs.ts index d9b12c550532f..16056d6687a70 100644 --- a/x-pack/plugins/synthetics/server/synthetics_service/formatters/format_configs.ts +++ b/x-pack/plugins/synthetics/server/synthetics_service/formatters/format_configs.ts @@ -23,10 +23,6 @@ const UI_KEYS_TO_SKIP = [ ConfigKey.JOURNEY_ID, ConfigKey.PROJECT_ID, ConfigKey.METADATA, - ConfigKey.UPLOAD_SPEED, - ConfigKey.DOWNLOAD_SPEED, - ConfigKey.LATENCY, - ConfigKey.IS_THROTTLING_ENABLED, ConfigKey.REVISION, ConfigKey.CUSTOM_HEARTBEAT_ID, ConfigKey.FORM_MONITOR_TYPE, @@ -36,19 +32,13 @@ const UI_KEYS_TO_SKIP = [ 'secrets', ]; -const uiToHeartbeatKeyMap = { - throttling: ConfigKey.THROTTLING_CONFIG, -}; - -type YamlKeys = keyof typeof uiToHeartbeatKeyMap; - export const formatMonitorConfigFields = ( configKeys: ConfigKey[], config: Partial, logger: Logger, params: Record ) => { - const formattedMonitor = {} as Record; + const formattedMonitor = {} as Record; configKeys.forEach((key) => { if (!UI_KEYS_TO_SKIP.includes(key)) { @@ -85,13 +75,6 @@ export const formatMonitorConfigFields = ( sslKeys.forEach((key) => (formattedMonitor[key] = null)); } - Object.keys(uiToHeartbeatKeyMap).forEach((key) => { - const hbKey = key as YamlKeys; - const configKey = uiToHeartbeatKeyMap[hbKey]; - formattedMonitor[hbKey] = formattedMonitor[configKey]; - delete formattedMonitor[configKey]; - }); - return omitBy(formattedMonitor, isNil) as Partial; }; diff --git a/x-pack/plugins/synthetics/server/synthetics_service/private_location/synthetics_private_location.test.ts b/x-pack/plugins/synthetics/server/synthetics_service/private_location/synthetics_private_location.test.ts index 5f1c74d5dfb05..a334b6b406b3b 100644 --- a/x-pack/plugins/synthetics/server/synthetics_service/private_location/synthetics_private_location.test.ts +++ b/x-pack/plugins/synthetics/server/synthetics_service/private_location/synthetics_private_location.test.ts @@ -311,11 +311,7 @@ const dummyBrowserConfig: Partial & { 'filter_journeys.match': '', 'filter_journeys.tags': [], ignore_https_errors: false, - 'throttling.is_enabled': true, - 'throttling.download_speed': '5', - 'throttling.upload_speed': '3', - 'throttling.latency': '20', - 'throttling.config': '5d/3u/20l', + throttling: { value: { download: '5', upload: '3', latency: '20' }, label: 'test', id: 'test' }, id: '75cdd125-5b62-4459-870c-46f59bf37e89', config_id: '75cdd125-5b62-4459-870c-46f59bf37e89', fields: { config_id: '75cdd125-5b62-4459-870c-46f59bf37e89', run_once: true }, diff --git a/x-pack/plugins/synthetics/server/synthetics_service/project_monitor/normalizers/browser_monitor.test.ts b/x-pack/plugins/synthetics/server/synthetics_service/project_monitor/normalizers/browser_monitor.test.ts index 0d66c944a1544..c1c6fa238b6c6 100644 --- a/x-pack/plugins/synthetics/server/synthetics_service/project_monitor/normalizers/browser_monitor.test.ts +++ b/x-pack/plugins/synthetics/server/synthetics_service/project_monitor/normalizers/browser_monitor.test.ts @@ -13,7 +13,11 @@ import { ProjectMonitor, PrivateLocation, } from '../../../../common/runtime_types'; -import { DEFAULT_FIELDS } from '../../../../common/constants/monitor_defaults'; +import { + DEFAULT_FIELDS, + PROFILE_VALUES_ENUM, + PROFILES_MAP, +} from '../../../../common/constants/monitor_defaults'; import { normalizeProjectMonitors } from '.'; describe('browser normalizers', () => { @@ -143,11 +147,6 @@ describe('browser normalizers', () => { 'service.name': '', 'source.project.content': 'test content 1', tags: ['tag1', 'tag2'], - 'throttling.config': '5d/10u/20l', - 'throttling.download_speed': '5', - 'throttling.is_enabled': true, - 'throttling.latency': '20', - 'throttling.upload_speed': '10', params: '', type: 'browser', project_id: projectId, @@ -157,6 +156,15 @@ describe('browser normalizers', () => { timeout: null, id: '', hash: testHash, + throttling: { + id: 'custom', + label: 'Custom', + value: { + download: '5', + latency: '20', + upload: '10', + }, + }, }, unsupportedKeys: [], errors: [], @@ -198,11 +206,6 @@ describe('browser normalizers', () => { 'service.name': '', 'source.project.content': 'test content 2', tags: ['tag3', 'tag4'], - 'throttling.config': '10d/15u/18l', - 'throttling.download_speed': '10', - 'throttling.is_enabled': true, - 'throttling.latency': '18', - 'throttling.upload_speed': '15', type: 'browser', project_id: projectId, namespace: 'test_space', @@ -211,6 +214,15 @@ describe('browser normalizers', () => { timeout: null, id: '', hash: testHash, + throttling: { + id: 'custom', + label: 'Custom', + value: { + download: '10', + latency: '18', + upload: '15', + }, + }, }, unsupportedKeys: [], errors: [], @@ -257,11 +269,6 @@ describe('browser normalizers', () => { 'service.name': '', 'source.project.content': 'test content 3', tags: ['tag3', 'tag4'], - 'throttling.config': '5d/3u/20l', - 'throttling.download_speed': '5', - 'throttling.is_enabled': false, - 'throttling.latency': '20', - 'throttling.upload_speed': '3', type: 'browser', project_id: projectId, namespace: 'test_space', @@ -270,6 +277,147 @@ describe('browser normalizers', () => { timeout: null, id: '', hash: testHash, + throttling: PROFILES_MAP[PROFILE_VALUES_ENUM.NO_THROTTLING], + }, + unsupportedKeys: [], + errors: [], + }, + ]); + }); + + it('handles defined throttling values', () => { + const actual = normalizeProjectMonitors({ + locations, + privateLocations, + monitors: [ + { + ...monitors[0], + throttling: { + download: 9, + upload: 0.75, + latency: 170, + }, + }, + ], + projectId, + namespace: 'test-space', + version: '8.5.0', + }); + expect(actual).toEqual([ + { + normalizedFields: { + ...DEFAULT_FIELDS[DataStream.BROWSER], + journey_id: 'test-id-1', + ignore_https_errors: true, + origin: 'project', + locations: [ + { + geo: { + lat: 33.333, + lon: 73.333, + }, + id: 'us_central', + isServiceManaged: true, + label: 'Test Location', + }, + ], + name: 'test-name-1', + schedule: { + number: '3', + unit: 'm', + }, + screenshots: 'off', + 'service.name': '', + 'source.project.content': 'test content 1', + tags: ['tag1', 'tag2'], + params: '', + type: 'browser', + project_id: projectId, + namespace: 'test_space', + original_space: 'test-space', + custom_heartbeat_id: 'test-id-1-test-project-id-test-space', + timeout: null, + id: '', + hash: testHash, + throttling: { + id: '4g', + label: '4G', + value: { + download: '9', + latency: '170', + upload: '0.75', + }, + }, + }, + unsupportedKeys: [], + errors: [], + }, + ]); + }); + + it('handles custom throttling values', () => { + const actual = normalizeProjectMonitors({ + locations, + privateLocations, + monitors: [ + { + ...monitors[0], + throttling: { + download: 10, + upload: 5, + latency: 30, + }, + }, + ], + projectId, + namespace: 'test-space', + version: '8.5.0', + }); + expect(actual).toEqual([ + { + normalizedFields: { + ...DEFAULT_FIELDS[DataStream.BROWSER], + journey_id: 'test-id-1', + ignore_https_errors: true, + origin: 'project', + locations: [ + { + geo: { + lat: 33.333, + lon: 73.333, + }, + id: 'us_central', + isServiceManaged: true, + label: 'Test Location', + }, + ], + name: 'test-name-1', + schedule: { + number: '3', + unit: 'm', + }, + screenshots: 'off', + 'service.name': '', + 'source.project.content': 'test content 1', + tags: ['tag1', 'tag2'], + params: '', + type: 'browser', + project_id: projectId, + namespace: 'test_space', + original_space: 'test-space', + custom_heartbeat_id: 'test-id-1-test-project-id-test-space', + timeout: null, + id: '', + hash: testHash, + throttling: { + id: 'custom', + label: 'Custom', + value: { + download: '10', + latency: '30', + upload: '5', + }, + }, }, unsupportedKeys: [], errors: [], diff --git a/x-pack/plugins/synthetics/server/synthetics_service/project_monitor/normalizers/browser_monitor.ts b/x-pack/plugins/synthetics/server/synthetics_service/project_monitor/normalizers/browser_monitor.ts index b8c6448b9e6e0..7ddf0cb01af91 100644 --- a/x-pack/plugins/synthetics/server/synthetics_service/project_monitor/normalizers/browser_monitor.ts +++ b/x-pack/plugins/synthetics/server/synthetics_service/project_monitor/normalizers/browser_monitor.ts @@ -10,8 +10,16 @@ import { ConfigKey, DataStream, FormMonitorType, + ProjectMonitor, + ThrottlingConfig, } from '../../../../common/runtime_types'; -import { DEFAULT_FIELDS } from '../../../../common/constants/monitor_defaults'; +import { + PROFILE_VALUES_ENUM, + DEFAULT_FIELDS, + PROFILES_MAP, + PROFILE_VALUES, + CUSTOM_LABEL, +} from '../../../../common/constants/monitor_defaults'; import { NormalizedProjectProps, NormalizerResult, @@ -38,33 +46,15 @@ export const getNormalizeBrowserFields = ({ version, }); + const throttling = normalizeThrottling(monitor.throttling); + const normalizedFields = { ...commonFields, [ConfigKey.MONITOR_TYPE]: DataStream.BROWSER, [ConfigKey.FORM_MONITOR_TYPE]: FormMonitorType.MULTISTEP, [ConfigKey.SOURCE_PROJECT_CONTENT]: monitor.content || defaultFields[ConfigKey.SOURCE_PROJECT_CONTENT], - [ConfigKey.THROTTLING_CONFIG]: - typeof monitor.throttling !== 'boolean' - ? `${monitor.throttling?.download}d/${monitor.throttling?.upload}u/${monitor.throttling?.latency}l` - : defaultFields[ConfigKey.THROTTLING_CONFIG], - [ConfigKey.DOWNLOAD_SPEED]: `${ - typeof monitor.throttling !== 'boolean' - ? monitor.throttling?.download - : defaultFields[ConfigKey.DOWNLOAD_SPEED] - }`, - [ConfigKey.UPLOAD_SPEED]: `${ - typeof monitor.throttling !== 'boolean' - ? monitor.throttling?.upload - : defaultFields[ConfigKey.UPLOAD_SPEED] - }`, - [ConfigKey.IS_THROTTLING_ENABLED]: - Boolean(monitor.throttling) ?? defaultFields[ConfigKey.IS_THROTTLING_ENABLED], - [ConfigKey.LATENCY]: `${ - typeof monitor.throttling !== 'boolean' - ? monitor.throttling?.latency - : defaultFields[ConfigKey.LATENCY] - }`, + [ConfigKey.THROTTLING_CONFIG]: throttling, [ConfigKey.IGNORE_HTTPS_ERRORS]: monitor.ignoreHTTPSErrors || defaultFields[ConfigKey.IGNORE_HTTPS_ERRORS], [ConfigKey.SCREENSHOTS]: monitor.screenshot || defaultFields[ConfigKey.SCREENSHOTS], @@ -89,3 +79,40 @@ export const getNormalizeBrowserFields = ({ errors, }; }; + +export const normalizeThrottling = ( + monitorThrottling: ProjectMonitor['throttling'] +): ThrottlingConfig => { + const defaultFields = DEFAULT_FIELDS[DataStream.BROWSER]; + + let throttling = defaultFields[ConfigKey.THROTTLING_CONFIG]; + if (typeof monitorThrottling === 'boolean' && !monitorThrottling) { + throttling = PROFILES_MAP[PROFILE_VALUES_ENUM.NO_THROTTLING]; + } + if (typeof monitorThrottling === 'object') { + const { download, upload, latency } = monitorThrottling; + const matchedProfile = PROFILE_VALUES.find(({ value }) => { + return ( + Number(value?.download) === download && + Number(value?.upload) === upload && + Number(value?.latency) === latency + ); + }); + + if (matchedProfile) { + return matchedProfile; + } else { + return { + id: PROFILE_VALUES_ENUM.CUSTOM, + label: CUSTOM_LABEL, + value: { + download: String(download), + upload: String(upload), + latency: String(latency), + }, + }; + } + } + + return throttling; +}; diff --git a/x-pack/plugins/synthetics/server/synthetics_service/project_monitor/project_monitor_formatter.test.ts b/x-pack/plugins/synthetics/server/synthetics_service/project_monitor/project_monitor_formatter.test.ts index 800b8cfa0ac5e..385ec449d6c1a 100644 --- a/x-pack/plugins/synthetics/server/synthetics_service/project_monitor/project_monitor_formatter.test.ts +++ b/x-pack/plugins/synthetics/server/synthetics_service/project_monitor/project_monitor_formatter.test.ts @@ -485,11 +485,6 @@ const payloadData = [ 'ssl.verification_mode': 'full', synthetics_args: [], tags: [], - 'throttling.config': '5d/3u/20l', - 'throttling.download_speed': '5', - 'throttling.is_enabled': true, - 'throttling.latency': '20', - 'throttling.upload_speed': '3', timeout: null, type: 'browser', 'url.port': null, @@ -540,11 +535,6 @@ const payloadData = [ 'ssl.verification_mode': 'full', synthetics_args: [], tags: [], - 'throttling.config': '5d/3u/20l', - 'throttling.download_speed': '5', - 'throttling.is_enabled': true, - 'throttling.latency': '20', - 'throttling.upload_speed': '3', timeout: null, type: 'browser', 'url.port': null, diff --git a/x-pack/plugins/synthetics/server/synthetics_service/project_monitor/project_monitor_formatter_legacy.test.ts b/x-pack/plugins/synthetics/server/synthetics_service/project_monitor/project_monitor_formatter_legacy.test.ts index c284e18dac56d..a7ae725337b06 100644 --- a/x-pack/plugins/synthetics/server/synthetics_service/project_monitor/project_monitor_formatter_legacy.test.ts +++ b/x-pack/plugins/synthetics/server/synthetics_service/project_monitor/project_monitor_formatter_legacy.test.ts @@ -554,11 +554,6 @@ const payloadData = [ 'ssl.verification_mode': 'full', synthetics_args: [], tags: [], - 'throttling.config': '5d/3u/20l', - 'throttling.download_speed': '5', - 'throttling.is_enabled': true, - 'throttling.latency': '20', - 'throttling.upload_speed': '3', timeout: null, type: 'browser', 'url.port': null, @@ -609,11 +604,6 @@ const payloadData = [ 'ssl.verification_mode': 'full', synthetics_args: [], tags: [], - 'throttling.config': '5d/3u/20l', - 'throttling.download_speed': '5', - 'throttling.is_enabled': true, - 'throttling.latency': '20', - 'throttling.upload_speed': '3', timeout: null, type: 'browser', 'url.port': null, diff --git a/x-pack/plugins/translations/translations/fr-FR.json b/x-pack/plugins/translations/translations/fr-FR.json index 72cfaa355835f..1cc24cee63e3f 100644 --- a/x-pack/plugins/translations/translations/fr-FR.json +++ b/x-pack/plugins/translations/translations/fr-FR.json @@ -34909,7 +34909,6 @@ "xpack.synthetics.monitorConfig.textAssertion.label": "Assertion de texte", "xpack.synthetics.monitorConfig.throttling.helpText": "Simulez la régulation du réseau (téléchargement, chargement, latence). D'autres options seront ajoutées dans une prochaine version.", "xpack.synthetics.monitorConfig.throttling.label": "Profil de connexion", - "xpack.synthetics.monitorConfig.throttling.options.default": "Par défaut", "xpack.synthetics.monitorConfig.timeout.formatError": "Le délai d'expiration n'est pas valide.", "xpack.synthetics.monitorConfig.timeout.greaterThan0Error": "Le délai d'expiration doit être supérieur ou égal à 0.", "xpack.synthetics.monitorConfig.timeout.helpText": "Temps total autorisé pour tester la connexion et l'échange de données.", diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 5636aeac2d32d..53c0c855e1d14 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -34888,7 +34888,6 @@ "xpack.synthetics.monitorConfig.textAssertion.label": "テキストアサーション", "xpack.synthetics.monitorConfig.throttling.helpText": "ネットワークスロットリングをシミュレートします(ダウンロード、アップロード、レイテンシ)。今後のバージョンではその他のオプションが追加されます。", "xpack.synthetics.monitorConfig.throttling.label": "接続プロファイル", - "xpack.synthetics.monitorConfig.throttling.options.default": "デフォルト", "xpack.synthetics.monitorConfig.timeout.formatError": "タイムアウトが無効です。", "xpack.synthetics.monitorConfig.timeout.greaterThan0Error": "タイムアウトは0以上でなければなりません。", "xpack.synthetics.monitorConfig.timeout.helpText": "接続のテストとデータの交換に許可された合計時間。", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 28b56ee5b92ff..f92e4a333eb58 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -34904,7 +34904,6 @@ "xpack.synthetics.monitorConfig.textAssertion.label": "文本断言", "xpack.synthetics.monitorConfig.throttling.helpText": "模拟网络限制(下载、上传、延迟)。将在未来版本中添加更多选项。", "xpack.synthetics.monitorConfig.throttling.label": "连接配置文件", - "xpack.synthetics.monitorConfig.throttling.options.default": "默认", "xpack.synthetics.monitorConfig.timeout.formatError": "超时无效。", "xpack.synthetics.monitorConfig.timeout.greaterThan0Error": "超时必须大于或等于 0。", "xpack.synthetics.monitorConfig.timeout.helpText": "允许用于测试连接并交换数据的总时间。", diff --git a/x-pack/test/api_integration/apis/synthetics/add_monitor_project.ts b/x-pack/test/api_integration/apis/synthetics/add_monitor_project.ts index 6c0dad1d2ce14..b1bc58abe7113 100644 --- a/x-pack/test/api_integration/apis/synthetics/add_monitor_project.ts +++ b/x-pack/test/api_integration/apis/synthetics/add_monitor_project.ts @@ -12,6 +12,10 @@ import { formatKibanaNamespace } from '@kbn/synthetics-plugin/common/formatters' import { syntheticsMonitorType } from '@kbn/synthetics-plugin/server/legacy_uptime/lib/saved_objects/synthetics_monitor'; import { REQUEST_TOO_LARGE } from '@kbn/synthetics-plugin/server/routes/monitor_cruds/add_monitor_project'; import { PackagePolicy } from '@kbn/fleet-plugin/common'; +import { + PROFILE_VALUES_ENUM, + PROFILES_MAP, +} from '@kbn/synthetics-plugin/common/constants/monitor_defaults'; import { FtrProviderContext } from '../../ftr_provider_context'; import { getFixtureJson } from '../uptime/rest/helper/get_fixture_json'; import { PrivateLocationTestService } from './services/private_location_test_service'; @@ -190,11 +194,7 @@ export default function ({ getService }: FtrProviderContext) { 'service.name': '', synthetics_args: [], tags: [], - 'throttling.config': '5d/3u/20l', - 'throttling.download_speed': '5', - 'throttling.is_enabled': true, - 'throttling.latency': '20', - 'throttling.upload_speed': '3', + throttling: PROFILES_MAP[PROFILE_VALUES_ENUM.DEFAULT], 'ssl.certificate': '', 'ssl.certificate_authorities': '', 'ssl.supported_protocols': ['TLSv1.1', 'TLSv1.2', 'TLSv1.3'], @@ -253,7 +253,11 @@ export default function ({ getService }: FtrProviderContext) { .set('kbn-xsrf', 'true') .expect(200); - expect(decryptedCreatedMonitor.body.attributes['throttling.is_enabled']).to.eql(false); + expect(decryptedCreatedMonitor.body.attributes.throttling).to.eql({ + value: null, + id: 'no-throttling', + label: 'No throttling', + }); } } finally { await Promise.all([ diff --git a/x-pack/test/api_integration/apis/synthetics/add_monitor_project_legacy.ts b/x-pack/test/api_integration/apis/synthetics/add_monitor_project_legacy.ts index 03477c85c3a06..65768d80a53f3 100644 --- a/x-pack/test/api_integration/apis/synthetics/add_monitor_project_legacy.ts +++ b/x-pack/test/api_integration/apis/synthetics/add_monitor_project_legacy.ts @@ -16,6 +16,10 @@ import { API_URLS } from '@kbn/synthetics-plugin/common/constants'; import { formatKibanaNamespace } from '@kbn/synthetics-plugin/common/formatters'; import { syntheticsMonitorType } from '@kbn/synthetics-plugin/server/legacy_uptime/lib/saved_objects/synthetics_monitor'; import { PackagePolicy } from '@kbn/fleet-plugin/common'; +import { + PROFILE_VALUES_ENUM, + PROFILES_MAP, +} from '@kbn/synthetics-plugin/common/constants/monitor_defaults'; import { FtrProviderContext } from '../../ftr_provider_context'; import { getFixtureJson } from '../uptime/rest/helper/get_fixture_json'; import { PrivateLocationTestService } from './services/private_location_test_service'; @@ -175,11 +179,7 @@ export default function ({ getService }: FtrProviderContext) { 'service.name': '', synthetics_args: [], tags: [], - 'throttling.config': '5d/3u/20l', - 'throttling.download_speed': '5', - 'throttling.is_enabled': true, - 'throttling.latency': '20', - 'throttling.upload_speed': '3', + throttling: PROFILES_MAP[PROFILE_VALUES_ENUM.DEFAULT], 'ssl.certificate': '', 'ssl.certificate_authorities': '', 'ssl.supported_protocols': ['TLSv1.1', 'TLSv1.2', 'TLSv1.3'], diff --git a/x-pack/test/api_integration/apis/uptime/rest/fixtures/browser_monitor.json b/x-pack/test/api_integration/apis/uptime/rest/fixtures/browser_monitor.json index 1a12efba9dc1c..44b1677de8a3a 100644 --- a/x-pack/test/api_integration/apis/uptime/rest/fixtures/browser_monitor.json +++ b/x-pack/test/api_integration/apis/uptime/rest/fixtures/browser_monitor.json @@ -31,11 +31,15 @@ "filter_journeys.match": "", "filter_journeys.tags": [], "ignore_https_errors": false, - "throttling.is_enabled": true, - "throttling.download_speed": "5", - "throttling.upload_speed": "3", - "throttling.latency": "20", - "throttling.config": "5d/3u/20l", + "throttling": { + "value": { + "download": "5", + "latency": "20", + "upload": "3" + }, + "id": "default", + "label": "Default" + }, "locations": [], "name": "Test HTTP Monitor 03", "namespace": "testnamespace", From 9ec31d0397d0a600c988327150912510b15208d1 Mon Sep 17 00:00:00 2001 From: Kevin Delemme Date: Wed, 19 Apr 2023 12:15:20 -0400 Subject: [PATCH 54/78] chore(slo): Improve active alerts badge (#155290) --- .../slo_active_alerts_badge.tsx | 13 +++++++------ .../hooks/slo/use_fetch_active_alerts.ts | 7 +++++++ .../slo_details/components/header_title.tsx | 2 +- .../slos/components/badges/slo_badges.tsx | 2 +- .../helpers/alerts_page_query_filter.test.ts | 18 ------------------ .../slos/helpers/alerts_page_query_filter.ts | 17 ----------------- 6 files changed, 16 insertions(+), 43 deletions(-) delete mode 100644 x-pack/plugins/observability/public/pages/slos/helpers/alerts_page_query_filter.test.ts delete mode 100644 x-pack/plugins/observability/public/pages/slos/helpers/alerts_page_query_filter.ts diff --git a/x-pack/plugins/observability/public/components/slo/slo_status_badge/slo_active_alerts_badge.tsx b/x-pack/plugins/observability/public/components/slo/slo_status_badge/slo_active_alerts_badge.tsx index 218f1c8bd84c3..4d5e826ab4129 100644 --- a/x-pack/plugins/observability/public/components/slo/slo_status_badge/slo_active_alerts_badge.tsx +++ b/x-pack/plugins/observability/public/components/slo/slo_status_badge/slo_active_alerts_badge.tsx @@ -8,17 +8,18 @@ import { EuiBadge, EuiFlexItem } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import React from 'react'; +import { SLOWithSummaryResponse } from '@kbn/slo-schema'; + import { paths } from '../../../config/paths'; import { useKibana } from '../../../utils/kibana_react'; - import { ActiveAlerts } from '../../../hooks/slo/use_fetch_active_alerts'; -import { toAlertsPageQueryFilter } from '../../../pages/slos/helpers/alerts_page_query_filter'; export interface Props { activeAlerts?: ActiveAlerts; + slo: SLOWithSummaryResponse; } -export function SloActiveAlertsBadge({ activeAlerts }: Props) { +export function SloActiveAlertsBadge({ slo, activeAlerts }: Props) { const { application: { navigateToUrl }, http: { basePath }, @@ -27,9 +28,9 @@ export function SloActiveAlertsBadge({ activeAlerts }: Props) { const handleActiveAlertsClick = () => { if (activeAlerts) { navigateToUrl( - `${basePath.prepend(paths.observability.alerts)}?_a=${toAlertsPageQueryFilter( - activeAlerts - )}` + `${basePath.prepend(paths.observability.alerts)}?_a=(kuery:'slo.id:"${ + slo.id + }"',rangeFrom:now-15m,rangeTo:now,status:active)` ); } }; diff --git a/x-pack/plugins/observability/public/hooks/slo/use_fetch_active_alerts.ts b/x-pack/plugins/observability/public/hooks/slo/use_fetch_active_alerts.ts index 4e90e51060df0..b8dc07a00ab32 100644 --- a/x-pack/plugins/observability/public/hooks/slo/use_fetch_active_alerts.ts +++ b/x-pack/plugins/observability/public/hooks/slo/use_fetch_active_alerts.ts @@ -58,6 +58,13 @@ export function useFetchActiveAlerts({ sloIds = [] }: Params): UseFetchActiveAle query: { bool: { filter: [ + { + range: { + '@timestamp': { + gte: 'now-15m/m', + }, + }, + }, { term: { 'kibana.alert.rule.rule_type_id': 'slo.rules.burnRate', diff --git a/x-pack/plugins/observability/public/pages/slo_details/components/header_title.tsx b/x-pack/plugins/observability/public/pages/slo_details/components/header_title.tsx index 4ce439f290fe7..94e7bcc8acf2c 100644 --- a/x-pack/plugins/observability/public/pages/slo_details/components/header_title.tsx +++ b/x-pack/plugins/observability/public/pages/slo_details/components/header_title.tsx @@ -38,7 +38,7 @@ export function HeaderTitle(props: Props) { {slo.name} - + ); diff --git a/x-pack/plugins/observability/public/pages/slos/components/badges/slo_badges.tsx b/x-pack/plugins/observability/public/pages/slos/components/badges/slo_badges.tsx index 15d36f3525ed1..89d5eadf4d9ad 100644 --- a/x-pack/plugins/observability/public/pages/slos/components/badges/slo_badges.tsx +++ b/x-pack/plugins/observability/public/pages/slos/components/badges/slo_badges.tsx @@ -31,7 +31,7 @@ export function SloBadges({ activeAlerts, rules, slo, onClickRuleBadge }: Props) - + ); diff --git a/x-pack/plugins/observability/public/pages/slos/helpers/alerts_page_query_filter.test.ts b/x-pack/plugins/observability/public/pages/slos/helpers/alerts_page_query_filter.test.ts deleted file mode 100644 index 956f5e087b6cf..0000000000000 --- a/x-pack/plugins/observability/public/pages/slos/helpers/alerts_page_query_filter.test.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 { toAlertsPageQueryFilter } from './alerts_page_query_filter'; - -describe('AlertsPageQueryFilter', () => { - it('computes the query filter correctly', async () => { - expect( - toAlertsPageQueryFilter({ count: 2, ruleIds: ['rule-1', 'rule-2'] }) - ).toMatchInlineSnapshot( - `"(kuery:'kibana.alert.rule.uuid:\\"rule-1\\" or kibana.alert.rule.uuid:\\"rule-2\\"',rangeFrom:now-15m,rangeTo:now,status:all)"` - ); - }); -}); diff --git a/x-pack/plugins/observability/public/pages/slos/helpers/alerts_page_query_filter.ts b/x-pack/plugins/observability/public/pages/slos/helpers/alerts_page_query_filter.ts deleted file mode 100644 index 3fa407fab1bd8..0000000000000 --- a/x-pack/plugins/observability/public/pages/slos/helpers/alerts_page_query_filter.ts +++ /dev/null @@ -1,17 +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 { ActiveAlerts } from '../../../hooks/slo/use_fetch_active_alerts'; - -export function toAlertsPageQueryFilter(activeAlerts: ActiveAlerts): string { - const kuery = activeAlerts.ruleIds - .map((ruleId) => `kibana.alert.rule.uuid:"${ruleId}"`) - .join(' or '); - - const query = `(kuery:'${kuery}',rangeFrom:now-15m,rangeTo:now,status:all)`; - return query; -} From eb90e40da5f48fa8554b495ecd9b046f5f5978e3 Mon Sep 17 00:00:00 2001 From: Aleh Zasypkin Date: Wed, 19 Apr 2023 18:33:11 +0200 Subject: [PATCH 55/78] Setup Node.js environment before instrumenting Kibana with APM. (#155063) --- src/cli/dev.js | 3 +-- src/cli/dist.js | 2 +- src/cli/tsconfig.json | 1 - 3 files changed, 2 insertions(+), 4 deletions(-) diff --git a/src/cli/dev.js b/src/cli/dev.js index fb61b53b6f210..f7a50ea16a7a9 100644 --- a/src/cli/dev.js +++ b/src/cli/dev.js @@ -6,7 +6,6 @@ * Side Public License, v 1. */ -require('@kbn/babel-register').install(); -require('./apm')(process.env.ELASTIC_APM_SERVICE_NAME || 'kibana-proxy'); require('../setup_node_env'); +require('./apm')(process.env.ELASTIC_APM_SERVICE_NAME || 'kibana-proxy'); require('./cli'); diff --git a/src/cli/dist.js b/src/cli/dist.js index fccc93cd53631..9bd7696a44561 100644 --- a/src/cli/dist.js +++ b/src/cli/dist.js @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -require('./apm')(); require('../setup_node_env/dist'); +require('./apm')(); require('../setup_node_env/root'); require('./cli'); diff --git a/src/cli/tsconfig.json b/src/cli/tsconfig.json index 5ee6fa3616614..ebbbc19f75c79 100644 --- a/src/cli/tsconfig.json +++ b/src/cli/tsconfig.json @@ -17,7 +17,6 @@ "@kbn/config", "@kbn/dev-utils", "@kbn/apm-config-loader", - "@kbn/babel-register", ], "exclude": [ "target/**/*", From 7a36571ce6e7312df4300c53b53844f91e532f20 Mon Sep 17 00:00:00 2001 From: Vadim Kibana <82822460+vadimkibana@users.noreply.github.com> Date: Wed, 19 Apr 2023 18:59:49 +0200 Subject: [PATCH 56/78] Bump "ajv" dependency minor version (#155234) ## Summary Upgrades `ajv` from `8.11` to `8.12`. --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index f898970efe8e7..58b87303d4a02 100644 --- a/package.json +++ b/package.json @@ -728,7 +728,7 @@ "JSONStream": "1.3.5", "abort-controller": "^3.0.0", "adm-zip": "^0.5.9", - "ajv": "^8.11.0", + "ajv": "^8.12.0", "antlr4ts": "^0.5.0-alpha.3", "archiver": "^5.3.1", "async": "^3.2.3", diff --git a/yarn.lock b/yarn.lock index 772067b69cdca..5ab19eee75bd9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -10050,10 +10050,10 @@ ajv@^6.1.0, ajv@^6.10.0, ajv@^6.10.2, ajv@^6.11.0, ajv@^6.12.2, ajv@^6.12.4, ajv json-schema-traverse "^0.4.1" uri-js "^4.2.2" -ajv@^8.0.0, ajv@^8.0.1, ajv@^8.11.0, ajv@^8.8.0: - version "8.11.0" - resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.11.0.tgz#977e91dd96ca669f54a11e23e378e33b884a565f" - integrity sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg== +ajv@^8.0.0, ajv@^8.0.1, ajv@^8.12.0, ajv@^8.8.0: + version "8.12.0" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.12.0.tgz#d1a0527323e22f53562c567c00991577dfbe19d1" + integrity sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA== dependencies: fast-deep-equal "^3.1.1" json-schema-traverse "^1.0.0" From c3c55e7aa849c0cc0b8a547c325540dc6b7803fa Mon Sep 17 00:00:00 2001 From: Jan Monschke Date: Wed, 19 Apr 2023 19:03:12 +0200 Subject: [PATCH 57/78] [SecuritySolution] Use correct queries and filters for prevalence calls (#154544) ## Summary Bug ticket https://github.com/elastic/kibana/issues/131967 describes an issue where the alert prevalence count is not correct for fields that have array values (such as `process.args`). ## Solution Getting the correct count for those fields involved adding more `term` conditions to the prevalence query and the timeline filter. This ensures that only alerts with the *exact* same array values match instead of partial matches as before. https://user-images.githubusercontent.com/68591/231395154-b5a1c968-8308-49fb-a218-f3611f8331c3.mov ### 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] Get approval from the product team --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> --- .../investigate_in_timeline.cy.ts | 34 +++++++++++------- .../cypress/screens/alerts_details.ts | 2 -- .../event_details/table/prevalence_cell.tsx | 1 + .../table/use_action_cell_data_provider.ts | 21 ++++++++++- .../containers/alerts/use_alert_prevalence.ts | 36 ++++++------------- .../es_archives/auditbeat/data.json | 3 +- 6 files changed, 56 insertions(+), 41 deletions(-) diff --git a/x-pack/plugins/security_solution/cypress/e2e/detection_alerts/investigate_in_timeline.cy.ts b/x-pack/plugins/security_solution/cypress/e2e/detection_alerts/investigate_in_timeline.cy.ts index e2d1204be84b2..213efb75e9860 100644 --- a/x-pack/plugins/security_solution/cypress/e2e/detection_alerts/investigate_in_timeline.cy.ts +++ b/x-pack/plugins/security_solution/cypress/e2e/detection_alerts/investigate_in_timeline.cy.ts @@ -8,6 +8,7 @@ import { closeTimeline } from '../../tasks/timeline'; import { getNewRule } from '../../objects/rule'; import { PROVIDER_BADGE, QUERY_TAB_BUTTON, TIMELINE_TITLE } from '../../screens/timeline'; +import { FILTER_BADGE } from '../../screens/alerts'; import { expandFirstAlert, investigateFirstAlertInTimeline } from '../../tasks/alerts'; import { createRule } from '../../tasks/api_calls/rules'; @@ -23,7 +24,6 @@ import { INSIGHTS_RELATED_ALERTS_BY_ANCESTRY, INSIGHTS_RELATED_ALERTS_BY_SESSION, SUMMARY_VIEW_INVESTIGATE_IN_TIMELINE_BUTTON, - SUMMARY_VIEW_PREVALENCE_CELL, } from '../../screens/alerts_details'; import { verifyInsightCount } from '../../tasks/alerts_details'; @@ -63,19 +63,29 @@ describe('Investigate in timeline', { testIsolation: false }, () => { }); it('should open a new timeline from a prevalence field', () => { - cy.get(SUMMARY_VIEW_PREVALENCE_CELL) - .first() - .invoke('text') - .then((alertCount) => { - // Click on the first button that lets us investigate in timeline - cy.get(ALERT_FLYOUT).find(SUMMARY_VIEW_INVESTIGATE_IN_TIMELINE_BUTTON).first().click(); + // Only one alert matches the exact process args in this case + const alertCount = 1; - // Make sure a new timeline is created and opened - cy.get(TIMELINE_TITLE).should('contain.text', 'Untitled timeline'); + // Click on the last button that lets us investigate in timeline. + // We expect this to be the `process.args` row. + const investigateButton = cy + .get(ALERT_FLYOUT) + .find(SUMMARY_VIEW_INVESTIGATE_IN_TIMELINE_BUTTON) + .last(); + investigateButton.should('have.text', alertCount); + investigateButton.click(); - // The alert count in this timeline should match the count shown on the alert flyout - cy.get(QUERY_TAB_BUTTON).should('contain.text', alertCount); - }); + // Make sure a new timeline is created and opened + cy.get(TIMELINE_TITLE).should('have.text', 'Untitled timeline'); + + // The alert count in this timeline should match the count shown on the alert flyout + cy.get(QUERY_TAB_BUTTON).should('contain.text', alertCount); + + // The correct filter is applied to the timeline query + cy.get(FILTER_BADGE).should( + 'have.text', + ' {"bool":{"must":[{"term":{"process.args":"-zsh"}},{"term":{"process.args":"unique"}}]}}' + ); }); it('should open a new timeline from an insights module', () => { diff --git a/x-pack/plugins/security_solution/cypress/screens/alerts_details.ts b/x-pack/plugins/security_solution/cypress/screens/alerts_details.ts index 74df1f1999373..c3a092c63a949 100644 --- a/x-pack/plugins/security_solution/cypress/screens/alerts_details.ts +++ b/x-pack/plugins/security_solution/cypress/screens/alerts_details.ts @@ -68,8 +68,6 @@ export const UPDATE_ENRICHMENT_RANGE_BUTTON = '[data-test-subj="enrichment-butto export const OVERVIEW_TAB = '[data-test-subj="overviewTab"]'; -export const SUMMARY_VIEW_PREVALENCE_CELL = `${SUMMARY_VIEW} [data-test-subj='alert-prevalence']`; - export const SUMMARY_VIEW_INVESTIGATE_IN_TIMELINE_BUTTON = `${SUMMARY_VIEW} [aria-label='Investigate in timeline']`; export const INSIGHTS_RELATED_ALERTS_BY_SESSION = `[data-test-subj='related-alerts-by-session']`; diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/table/prevalence_cell.tsx b/x-pack/plugins/security_solution/public/common/components/event_details/table/prevalence_cell.tsx index dca16cb5e896b..91fb095a16443 100644 --- a/x-pack/plugins/security_solution/public/common/components/event_details/table/prevalence_cell.tsx +++ b/x-pack/plugins/security_solution/public/common/components/event_details/table/prevalence_cell.tsx @@ -56,6 +56,7 @@ const PrevalenceCell: React.FC = ({ {count} diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/table/use_action_cell_data_provider.ts b/x-pack/plugins/security_solution/public/common/components/event_details/table/use_action_cell_data_provider.ts index 8e35d3a7881b1..7ee53ae5d4bee 100644 --- a/x-pack/plugins/security_solution/public/common/components/event_details/table/use_action_cell_data_provider.ts +++ b/x-pack/plugins/security_solution/public/common/components/event_details/table/use_action_cell_data_provider.ts @@ -7,6 +7,7 @@ /* eslint-disable complexity */ +import type { Filter } from '@kbn/es-query'; import { escapeDataProviderId } from '@kbn/securitysolution-t-grid'; import { isArray, isEmpty, isString } from 'lodash/fp'; import { useMemo } from 'react'; @@ -47,6 +48,7 @@ export interface UseActionCellDataProvider { export interface ActionCellValuesAndDataProvider { values: string[]; dataProviders: DataProvider[]; + filters: Filter[]; } export const getDataProvider = ( @@ -93,6 +95,23 @@ export const useActionCellDataProvider = ({ const cellData = useMemo(() => { if (values === null || values === undefined) return null; const arrayValues = Array.isArray(values) ? values : [values]; + + // For fields with multiple values we need add an extra filter that makes sure + // that only fields that match ALL the values are queried later on. + let filters: Filter[] = []; + if (arrayValues.length > 1) { + filters = [ + { + meta: {}, + query: { + bool: { + must: arrayValues.map((value) => ({ term: { [field]: value } })), + }, + }, + }, + ]; + } + return arrayValues.reduce( (memo, value, index) => { let id: string = ''; @@ -157,7 +176,7 @@ export const useActionCellDataProvider = ({ memo.dataProviders.push(getDataProvider(field, id, value)); return memo; }, - { values: [], dataProviders: [] } + { values: [], dataProviders: [], filters } ); }, [ contextId, diff --git a/x-pack/plugins/security_solution/public/common/containers/alerts/use_alert_prevalence.ts b/x-pack/plugins/security_solution/public/common/containers/alerts/use_alert_prevalence.ts index cbb6d144b055d..03ac3d6169351 100644 --- a/x-pack/plugins/security_solution/public/common/containers/alerts/use_alert_prevalence.ts +++ b/x-pack/plugins/security_solution/public/common/containers/alerts/use_alert_prevalence.ts @@ -68,13 +68,7 @@ export const useAlertPrevalence = ({ if (data) { const buckets = data.aggregations?.[ALERT_PREVALENCE_AGG]?.buckets; if (buckets && buckets.length > 0) { - /** - * Currently for array fields like `process.args` or potentially any `ip` fields - * We show the combined count of all occurences of the value, even though those values - * could be shared across multiple documents. To make this clearer, we should separate - * these values into separate table rows - */ - count = buckets?.reduce((sum, bucket) => sum + (bucket?.doc_count ?? 0), 0); + count = buckets[0].doc_count; } } @@ -132,31 +126,23 @@ const generateAlertPrevalenceQuery = ( }; } + // If we search for the prevalence of a field that has multiple values (e.g. process.args), + // we want to find alerts with the exact same values. if (Array.isArray(value) && value.length > 1) { - const shouldValues = value.map((val) => ({ match: { [field]: val } })); query = { bool: { - minimum_should_match: 1, - should: shouldValues, + must: value.map((term) => ({ term: { [field]: term } })) as object[], }, }; if (from !== undefined && to !== undefined) { - query = { - ...query, - bool: { - ...query.bool, - must: [ - { - range: { - '@timestamp': { - gte: from, - lte: to, - }, - }, - }, - ], + query.bool.must.push({ + range: { + '@timestamp': { + gte: from, + lte: to, + }, }, - }; + }); } } diff --git a/x-pack/test/security_solution_cypress/es_archives/auditbeat/data.json b/x-pack/test/security_solution_cypress/es_archives/auditbeat/data.json index ec69dcaa9d0f7..a4e42ac0335a9 100644 --- a/x-pack/test/security_solution_cypress/es_archives/auditbeat/data.json +++ b/x-pack/test/security_solution_cypress/es_archives/auditbeat/data.json @@ -204,7 +204,8 @@ "executable" : "/bin/zsh", "name" : "zsh", "args" : [ - "-zsh" + "-zsh", + "unique" ], "entity_id" : "q6pltOhTWlQx3BCE", "entry_leader": { From 0225747610d05be311d018a067bf89c2ef45016e Mon Sep 17 00:00:00 2001 From: Yan Savitski Date: Wed, 19 Apr 2023 19:04:25 +0200 Subject: [PATCH 58/78] [Enterprise Search] [Behavioral analytics] Add Explorer page (#155077) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Implement Explorer page that consists of: - ✔️ Tabs [Search terms, Top clicked results, No results, Referrers] - ✔️ Search bar for searching the data - ✔️ Data representation for top results as a table with paginations, sorting, page size - ✔️ Callout "Need a deeper analysis" image --------- Co-authored-by: Klim Markelov Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> --- ...alytics_collection_data_view_logic.test.ts | 75 ++++ .../analytics_collection_data_view_logic.ts | 46 +++ ...ics_collection_explore_table_logic.test.ts | 149 ------- ...nalytics_collection_explore_table_logic.ts | 333 ---------------- ...ytics_collection_explore_table_formulas.ts | 71 ++++ ...ics_collection_explore_table_logic.test.ts | 255 ++++++++++++ ...nalytics_collection_explore_table_logic.ts | 377 ++++++++++++++++++ ...nalytics_collection_explore_table_types.ts | 0 .../analytics_collection_explorer.test.tsx | 63 +++ .../analytics_collection_explorer.tsx | 51 +++ .../analytics_collection_explorer_callout.tsx | 54 +++ ...alytics_collection_explorer_table.test.tsx | 70 ++++ .../analytics_collection_explorer_table.tsx | 327 +++++++++++++++ .../analytics_collection_chart.test.tsx | 3 - .../analytics_collection_chart.tsx | 4 +- .../analytics_collection_metric.tsx | 2 - .../analytics_collection_overview.test.tsx | 4 +- .../analytics_collection_overview.tsx | 9 +- ...lytics_collection_overview_table.test.tsx} | 16 +- .../analytics_collection_overview_table.tsx} | 20 +- .../analytics_collection_toolbar.test.tsx | 6 +- .../analytics_collection_toolbar.tsx | 161 ++++---- ...analytics_collection_toolbar_logic.test.ts | 14 - .../analytics_collection_toolbar_logic.ts | 15 - .../analytics_collection_view.tsx | 9 +- .../fetch_analytics_collection_logic.ts | 3 +- .../use_discover_link.ts | 31 ++ .../analytics_collection_card.tsx | 2 - .../analytics/hoc/with_lens_data.test.tsx | 60 ++- .../analytics/hoc/with_lens_data.tsx | 13 +- .../utils/find_or_create_data_view.test.ts | 58 +++ .../utils/find_or_create_data_view.ts | 29 ++ .../plugins/enterprise_search/tsconfig.json | 1 + 33 files changed, 1668 insertions(+), 663 deletions(-) create mode 100644 x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/analytics_collection_data_view_logic.test.ts create mode 100644 x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/analytics_collection_data_view_logic.ts delete mode 100644 x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/analytics_collection_explore_table/analytics_collection_explore_table_logic.test.ts delete mode 100644 x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/analytics_collection_explore_table/analytics_collection_explore_table_logic.ts create mode 100644 x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/analytics_collection_explore_table_formulas.ts create mode 100644 x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/analytics_collection_explore_table_logic.test.ts create mode 100644 x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/analytics_collection_explore_table_logic.ts rename x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/{analytics_collection_explore_table => }/analytics_collection_explore_table_types.ts (100%) create mode 100644 x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/analytics_collection_explorer/analytics_collection_explorer.test.tsx create mode 100644 x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/analytics_collection_explorer/analytics_collection_explorer.tsx create mode 100644 x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/analytics_collection_explorer/analytics_collection_explorer_callout.tsx create mode 100644 x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/analytics_collection_explorer/analytics_collection_explorer_table.test.tsx create mode 100644 x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/analytics_collection_explorer/analytics_collection_explorer_table.tsx rename x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/{analytics_collection_explore_table/analytics_collection_explore_table.test.tsx => analytics_collection_overview/analytics_collection_overview_table.test.tsx} (70%) rename x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/{analytics_collection_explore_table/analytics_collection_explore_table.tsx => analytics_collection_overview/analytics_collection_overview_table.tsx} (94%) create mode 100644 x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/use_discover_link.ts create mode 100644 x-pack/plugins/enterprise_search/public/applications/analytics/utils/find_or_create_data_view.test.ts create mode 100644 x-pack/plugins/enterprise_search/public/applications/analytics/utils/find_or_create_data_view.ts diff --git a/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/analytics_collection_data_view_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/analytics_collection_data_view_logic.test.ts new file mode 100644 index 0000000000000..4c57bc4325f3e --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/analytics_collection_data_view_logic.test.ts @@ -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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { LogicMounter } from '../../../__mocks__/kea_logic'; + +import { DataView } from '@kbn/data-views-plugin/common'; + +import { AnalyticsCollection } from '../../../../../common/types/analytics'; + +import { findOrCreateDataView } from '../../utils/find_or_create_data_view'; + +import { AnalyticsCollectionDataViewLogic } from './analytics_collection_data_view_logic'; +import { FetchAnalyticsCollectionLogic } from './fetch_analytics_collection_logic'; + +jest.mock('../../utils/find_or_create_data_view', () => { + return { + findOrCreateDataView: jest.fn(), + }; +}); + +describe('AnalyticsCollectionDataViewLogic', () => { + const { mount } = new LogicMounter(AnalyticsCollectionDataViewLogic); + + beforeEach(() => { + jest.clearAllMocks(); + + mount(); + }); + + const defaultProps = { + dataView: null, + }; + + it('initializes with default values', () => { + expect(AnalyticsCollectionDataViewLogic.values).toEqual(defaultProps); + }); + + describe('reducers', () => { + it('should handle set dataView', () => { + const dataView = { id: 'test' } as DataView; + AnalyticsCollectionDataViewLogic.actions.setDataView(dataView); + expect(AnalyticsCollectionDataViewLogic.values.dataView).toBe(dataView); + }); + }); + + describe('listeners', () => { + it('should find and set dataView when analytics collection fetched', async () => { + const dataView = { id: 'test' } as DataView; + (findOrCreateDataView as jest.Mock).mockResolvedValue(dataView); + + await FetchAnalyticsCollectionLogic.actions.apiSuccess({ + events_datastream: 'events1', + name: 'collection1', + } as AnalyticsCollection); + + expect(AnalyticsCollectionDataViewLogic.values.dataView).toEqual(dataView); + }); + + it('should create, save and set dataView when analytics collection fetched but dataView is not found', async () => { + const dataView = { id: 'test' } as DataView; + (findOrCreateDataView as jest.Mock).mockResolvedValue(dataView); + + await FetchAnalyticsCollectionLogic.actions.apiSuccess({ + events_datastream: 'events1', + name: 'collection1', + } as AnalyticsCollection); + + expect(AnalyticsCollectionDataViewLogic.values.dataView).toEqual(dataView); + }); + }); +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/analytics_collection_data_view_logic.ts b/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/analytics_collection_data_view_logic.ts new file mode 100644 index 0000000000000..8d53757db78b6 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/analytics_collection_data_view_logic.ts @@ -0,0 +1,46 @@ +/* + * Copyright 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 { kea, MakeLogicType } from 'kea'; + +import { DataView } from '@kbn/data-views-plugin/common'; + +import { findOrCreateDataView } from '../../utils/find_or_create_data_view'; + +import { + FetchAnalyticsCollectionActions, + FetchAnalyticsCollectionLogic, +} from './fetch_analytics_collection_logic'; + +interface AnalyticsCollectionDataViewLogicValues { + dataView: DataView | null; +} + +interface AnalyticsCollectionDataViewLogicActions { + fetchedAnalyticsCollection: FetchAnalyticsCollectionActions['apiSuccess']; + setDataView(dataView: DataView): { dataView: DataView }; +} + +export const AnalyticsCollectionDataViewLogic = kea< + MakeLogicType +>({ + actions: { + setDataView: (dataView) => ({ dataView }), + }, + connect: { + actions: [FetchAnalyticsCollectionLogic, ['apiSuccess as fetchedAnalyticsCollection']], + }, + listeners: ({ actions }) => ({ + fetchedAnalyticsCollection: async (collection) => { + actions.setDataView(await findOrCreateDataView(collection)); + }, + }), + path: ['enterprise_search', 'analytics', 'collections', 'dataView'], + reducers: () => ({ + dataView: [null, { setDataView: (_, { dataView }) => dataView }], + }), +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/analytics_collection_explore_table/analytics_collection_explore_table_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/analytics_collection_explore_table/analytics_collection_explore_table_logic.test.ts deleted file mode 100644 index 4220c30985a95..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/analytics_collection_explore_table/analytics_collection_explore_table_logic.test.ts +++ /dev/null @@ -1,149 +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 { LogicMounter } from '../../../../__mocks__/kea_logic'; - -import { DataView } from '@kbn/data-views-plugin/common'; - -import { AnalyticsCollection } from '../../../../../../common/types/analytics'; - -import { KibanaLogic } from '../../../../shared/kibana/kibana_logic'; - -import { AnalyticsCollectionToolbarLogic } from '../analytics_collection_toolbar/analytics_collection_toolbar_logic'; - -import { - AnalyticsCollectionExploreTableLogic, - Sorting, -} from './analytics_collection_explore_table_logic'; -import { ExploreTableColumns, ExploreTables } from './analytics_collection_explore_table_types'; - -jest.mock('../../../../shared/kibana/kibana_logic', () => ({ - KibanaLogic: { - values: { - data: { - dataViews: { - find: jest.fn(() => Promise.resolve([{ id: 'some-data-view-id' }])), - }, - search: { - search: jest.fn().mockReturnValue({ subscribe: jest.fn() }), - }, - }, - }, - }, -})); - -describe('AnalyticsCollectionExplorerTablesLogic', () => { - const { mount } = new LogicMounter(AnalyticsCollectionExploreTableLogic); - - beforeEach(() => { - jest.clearAllMocks(); - - mount(); - }); - - const defaultProps = { - dataView: null, - isLoading: false, - items: [], - selectedTable: null, - sorting: null, - }; - - it('initializes with default values', () => { - expect(AnalyticsCollectionExploreTableLogic.values).toEqual(defaultProps); - }); - - describe('reducers', () => { - it('should handle set dataView', () => { - const dataView = { id: 'test' } as DataView; - AnalyticsCollectionExploreTableLogic.actions.setDataView(dataView); - expect(AnalyticsCollectionExploreTableLogic.values.dataView).toBe(dataView); - }); - - it('should handle set items', () => { - const items = [ - { count: 1, query: 'test' }, - { count: 2, query: 'test2' }, - ]; - AnalyticsCollectionExploreTableLogic.actions.setItems(items); - expect(AnalyticsCollectionExploreTableLogic.values.items).toEqual(items); - }); - - it('should handle set selectedTable', () => { - const id = ExploreTables.WorsePerformers; - const sorting = { direction: 'desc', field: ExploreTableColumns.count } as Sorting; - AnalyticsCollectionExploreTableLogic.actions.setSelectedTable(id, sorting); - expect(AnalyticsCollectionExploreTableLogic.values.selectedTable).toEqual(id); - expect(AnalyticsCollectionExploreTableLogic.values.sorting).toEqual(sorting); - }); - - it('should handle set sorting', () => { - const sorting = { direction: 'asc', field: ExploreTableColumns.sessions } as Sorting; - AnalyticsCollectionExploreTableLogic.actions.setSorting(sorting); - expect(AnalyticsCollectionExploreTableLogic.values.sorting).toEqual(sorting); - }); - - it('should handle isLoading', () => { - expect(AnalyticsCollectionExploreTableLogic.values.isLoading).toEqual(false); - - AnalyticsCollectionExploreTableLogic.actions.setItems([]); - expect(AnalyticsCollectionExploreTableLogic.values.isLoading).toEqual(false); - - AnalyticsCollectionExploreTableLogic.actions.setSelectedTable(ExploreTables.WorsePerformers); - expect(AnalyticsCollectionExploreTableLogic.values.isLoading).toEqual(true); - - AnalyticsCollectionToolbarLogic.actions.setTimeRange({ from: 'now-7d', to: 'now' }); - expect(AnalyticsCollectionExploreTableLogic.values.isLoading).toEqual(true); - - AnalyticsCollectionToolbarLogic.actions.setSearchSessionId('12345'); - expect(AnalyticsCollectionExploreTableLogic.values.isLoading).toEqual(true); - }); - }); - - describe('listeners', () => { - it('should fetch items when selectedTable changes', () => { - AnalyticsCollectionExploreTableLogic.actions.setSelectedTable(ExploreTables.TopReferrers); - expect(KibanaLogic.values.data.search.search).toHaveBeenCalledWith(expect.any(Object), { - indexPattern: undefined, - sessionId: undefined, - }); - }); - - it('should fetch items when timeRange changes', () => { - AnalyticsCollectionExploreTableLogic.actions.setSelectedTable(ExploreTables.WorsePerformers); - (KibanaLogic.values.data.search.search as jest.Mock).mockClear(); - - AnalyticsCollectionToolbarLogic.actions.setTimeRange({ from: 'now-7d', to: 'now' }); - expect(KibanaLogic.values.data.search.search).toHaveBeenCalledWith(expect.any(Object), { - indexPattern: undefined, - sessionId: undefined, - }); - }); - - it('should fetch items when searchSessionId changes', () => { - AnalyticsCollectionExploreTableLogic.actions.setSelectedTable(ExploreTables.WorsePerformers); - (KibanaLogic.values.data.search.search as jest.Mock).mockClear(); - - AnalyticsCollectionToolbarLogic.actions.setSearchSessionId('1234'); - expect(KibanaLogic.values.data.search.search).toHaveBeenCalledWith(expect.any(Object), { - indexPattern: undefined, - sessionId: '1234', - }); - }); - - it('should find and set dataView when findDataView is called', async () => { - const dataView = { id: 'test' } as DataView; - jest.spyOn(KibanaLogic.values.data.dataViews, 'find').mockResolvedValue([dataView]); - await AnalyticsCollectionExploreTableLogic.actions.findDataView({ - events_datastream: 'events1', - name: 'collection1', - } as AnalyticsCollection); - - expect(AnalyticsCollectionExploreTableLogic.values.dataView).toEqual(dataView); - }); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/analytics_collection_explore_table/analytics_collection_explore_table_logic.ts b/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/analytics_collection_explore_table/analytics_collection_explore_table_logic.ts deleted file mode 100644 index 46df5692aa963..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/analytics_collection_explore_table/analytics_collection_explore_table_logic.ts +++ /dev/null @@ -1,333 +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 { kea, MakeLogicType } from 'kea'; - -import { - IKibanaSearchRequest, - IKibanaSearchResponse, - isCompleteResponse, - TimeRange, -} from '@kbn/data-plugin/common'; -import { DataView } from '@kbn/data-views-plugin/common'; - -import { AnalyticsCollection } from '../../../../../../common/types/analytics'; -import { KibanaLogic } from '../../../../shared/kibana/kibana_logic'; -import { AnalyticsCollectionToolbarLogic } from '../analytics_collection_toolbar/analytics_collection_toolbar_logic'; - -import { - ExploreTableColumns, - ExploreTableItem, - ExploreTables, - SearchTermsTable, - TopClickedTable, - TopReferrersTable, - WorsePerformersTable, -} from './analytics_collection_explore_table_types'; - -const BASE_PAGE_SIZE = 10; - -export interface Sorting { - direction: 'asc' | 'desc'; - field: keyof T; -} - -interface TableParams { - parseResponseToItems(response: IKibanaSearchResponse): T[]; - requestParams(timeRange: TimeRange, sorting: Sorting | null): IKibanaSearchRequest; -} - -const tablesParams: { - [ExploreTables.SearchTerms]: TableParams; - [ExploreTables.TopClicked]: TableParams; - [ExploreTables.TopReferrers]: TableParams; - [ExploreTables.WorsePerformers]: TableParams; -} = { - [ExploreTables.SearchTerms]: { - parseResponseToItems: ( - response: IKibanaSearchResponse<{ - aggregations: { searches: { buckets: Array<{ doc_count: number; key: string }> } }; - }> - ) => - response.rawResponse.aggregations.searches.buckets.map((bucket) => ({ - [ExploreTableColumns.count]: bucket.doc_count, - [ExploreTableColumns.searchTerms]: bucket.key, - })), - requestParams: (timeRange, sorting) => ({ - params: { - aggs: { - searches: { - terms: { - field: 'search.query', - order: sorting - ? { - [sorting.field === ExploreTableColumns.count ? '_count' : '_key']: - sorting.direction, - } - : undefined, - size: BASE_PAGE_SIZE, - }, - }, - }, - query: { - range: { - '@timestamp': { - gte: timeRange.from, - lt: timeRange.to, - }, - }, - }, - size: 0, - track_total_hits: false, - }, - }), - }, - [ExploreTables.WorsePerformers]: { - parseResponseToItems: ( - response: IKibanaSearchResponse<{ - aggregations: { - formula: { searches: { buckets: Array<{ doc_count: number; key: string }> } }; - }; - }> - ) => - response.rawResponse.aggregations.formula.searches.buckets.map((bucket) => ({ - [ExploreTableColumns.count]: bucket.doc_count, - [ExploreTableColumns.query]: bucket.key, - })), - requestParams: (timeRange, sorting) => ({ - params: { - aggs: { - formula: { - aggs: { - searches: { - terms: { - field: 'search.query', - order: sorting - ? { - [sorting?.field === ExploreTableColumns.count ? '_count' : '_key']: - sorting?.direction, - } - : undefined, - size: BASE_PAGE_SIZE, - }, - }, - }, - filter: { term: { 'search.results.total_results': '0' } }, - }, - }, - query: { - range: { - '@timestamp': { - gte: timeRange.from, - lt: timeRange.to, - }, - }, - }, - size: 0, - track_total_hits: false, - }, - }), - }, - [ExploreTables.TopClicked]: { - parseResponseToItems: ( - response: IKibanaSearchResponse<{ - aggregations: { - formula: { searches: { buckets: Array<{ doc_count: number; key: string }> } }; - }; - }> - ) => - response.rawResponse.aggregations.formula.searches.buckets.map((bucket) => ({ - [ExploreTableColumns.count]: bucket.doc_count, - [ExploreTableColumns.page]: bucket.key, - })), - requestParams: (timeRange, sorting) => ({ - params: { - aggs: { - formula: { - aggs: { - searches: { - terms: { - field: 'search.results.items.page.url', - order: sorting - ? { - [sorting.field === ExploreTableColumns.count ? '_count' : '_key']: - sorting.direction, - } - : undefined, - size: BASE_PAGE_SIZE, - }, - }, - }, - filter: { term: { 'event.action': 'search_click' } }, - }, - }, - query: { - range: { - '@timestamp': { - gte: timeRange.from, - lt: timeRange.to, - }, - }, - }, - size: 0, - track_total_hits: false, - }, - }), - }, - [ExploreTables.TopReferrers]: { - parseResponseToItems: ( - response: IKibanaSearchResponse<{ - aggregations: { - formula: { searches: { buckets: Array<{ doc_count: number; key: string }> } }; - }; - }> - ) => - response.rawResponse.aggregations.formula.searches.buckets.map((bucket) => ({ - [ExploreTableColumns.sessions]: bucket.doc_count, - [ExploreTableColumns.page]: bucket.key, - })), - requestParams: (timeRange, sorting) => ({ - params: { - aggs: { - formula: { - aggs: { - searches: { - terms: { - field: 'page.referrer', - order: sorting - ? { - [sorting?.field === ExploreTableColumns.sessions ? '_count' : '_key']: - sorting?.direction, - } - : undefined, - size: BASE_PAGE_SIZE, - }, - }, - }, - filter: { term: { 'event.action': 'page_view' } }, - }, - }, - query: { - range: { - '@timestamp': { - gte: timeRange.from, - lt: timeRange.to, - }, - }, - }, - size: 0, - track_total_hits: false, - }, - }), - }, -}; - -export interface AnalyticsCollectionExploreTableLogicValues { - dataView: DataView | null; - isLoading: boolean; - items: ExploreTableItem[]; - selectedTable: ExploreTables | null; - sorting: Sorting | null; -} - -export interface AnalyticsCollectionExploreTableLogicActions { - findDataView(collection: AnalyticsCollection): { collection: AnalyticsCollection }; - setDataView(dataView: DataView): { dataView: DataView }; - setItems(items: ExploreTableItem[]): { items: ExploreTableItem[] }; - setSelectedTable( - id: ExploreTables | null, - sorting?: Sorting - ): { id: ExploreTables | null; sorting?: Sorting }; - setSorting(sorting?: Sorting): { sorting?: Sorting }; -} - -export const AnalyticsCollectionExploreTableLogic = kea< - MakeLogicType< - AnalyticsCollectionExploreTableLogicValues, - AnalyticsCollectionExploreTableLogicActions - > ->({ - actions: { - findDataView: (collection) => ({ collection }), - setDataView: (dataView) => ({ dataView }), - setItems: (items) => ({ items }), - setSelectedTable: (id, sorting) => ({ id, sorting }), - setSorting: (sorting) => ({ sorting }), - }, - listeners: ({ actions, values }) => { - const fetchItems = () => { - if (values.selectedTable === null || !(values.selectedTable in tablesParams)) { - return; - } - - const { requestParams, parseResponseToItems } = tablesParams[ - values.selectedTable - ] as TableParams; - const timeRange = AnalyticsCollectionToolbarLogic.values.timeRange; - - const search$ = KibanaLogic.values.data.search - .search(requestParams(timeRange, values.sorting), { - indexPattern: values.dataView || undefined, - sessionId: AnalyticsCollectionToolbarLogic.values.searchSessionId, - }) - .subscribe({ - error: (e) => { - KibanaLogic.values.data.search.showError(e); - }, - next: (response) => { - if (isCompleteResponse(response)) { - actions.setItems(parseResponseToItems(response)); - search$.unsubscribe(); - } - }, - }); - }; - - return { - findDataView: async ({ collection }) => { - const dataView = ( - await KibanaLogic.values.data.dataViews.find(collection.events_datastream, 1) - )?.[0]; - - if (dataView) { - actions.setDataView(dataView); - } - }, - setSelectedTable: () => { - fetchItems(); - }, - [AnalyticsCollectionToolbarLogic.actionTypes.setTimeRange]: () => { - fetchItems(); - }, - [AnalyticsCollectionToolbarLogic.actionTypes.setSearchSessionId]: () => { - fetchItems(); - }, - }; - }, - path: ['enterprise_search', 'analytics', 'collections', 'explore', 'table'], - reducers: () => ({ - dataView: [null, { setDataView: (_, { dataView }) => dataView }], - isLoading: [ - false, - { - setItems: () => false, - setSelectedTable: () => true, - [AnalyticsCollectionToolbarLogic.actionTypes.setTimeRange]: () => true, - [AnalyticsCollectionToolbarLogic.actionTypes.setSearchSessionId]: () => true, - }, - ], - items: [[], { setItems: (_, { items }) => items }], - selectedTable: [null, { setSelectedTable: (_, { id }) => id }], - sorting: [ - null, - { - setSelectedTable: (_, { sorting = null }) => sorting, - setSorting: (_, { sorting = null }) => sorting, - }, - ], - }), -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/analytics_collection_explore_table_formulas.ts b/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/analytics_collection_explore_table_formulas.ts new file mode 100644 index 0000000000000..b628d1d8e633f --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/analytics_collection_explore_table_formulas.ts @@ -0,0 +1,71 @@ +/* + * Copyright 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 { IKibanaSearchRequest, TimeRange } from '@kbn/data-plugin/common'; + +const getSearchQueryRequestParams = (field: string, search: string): { regexp: {} } => { + const createRegexQuery = (queryString: string) => { + const query = queryString.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, '\\$&'); + + return `.*${query}.*`; + }; + + return { + regexp: { + [field]: { + value: createRegexQuery(search), + }, + }, + }; +}; +export const getTotalCountRequestParams = (field: string) => ({ + totalCount: { + cardinality: { + field, + }, + }, +}); +export const getPaginationRequestSizeParams = (pageIndex: number, pageSize: number) => ({ + size: (pageIndex + 1) * pageSize, +}); +export const getPaginationRequestParams = (pageIndex: number, pageSize: number) => ({ + aggs: { + sort: { + bucket_sort: { + from: pageIndex * pageSize, + size: pageSize, + }, + }, + }, +}); + +export const getBaseSearchTemplate = ( + aggregationFieldName: string, + { search, timeRange }: { search: string; timeRange: TimeRange }, + aggs: IKibanaSearchRequest['params']['aggs'] +): IKibanaSearchRequest => ({ + params: { + aggs, + query: { + bool: { + must: [ + { + range: { + '@timestamp': { + gte: timeRange.from, + lt: timeRange.to, + }, + }, + }, + ...(search ? [getSearchQueryRequestParams(aggregationFieldName, search)] : []), + ], + }, + }, + size: 0, + track_total_hits: false, + }, +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/analytics_collection_explore_table_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/analytics_collection_explore_table_logic.test.ts new file mode 100644 index 0000000000000..6f02ab06fedfb --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/analytics_collection_explore_table_logic.test.ts @@ -0,0 +1,255 @@ +/* + * Copyright 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 { LogicMounter } from '../../../__mocks__/kea_logic'; + +import { KibanaLogic } from '../../../shared/kibana/kibana_logic'; + +import { + AnalyticsCollectionExploreTableLogic, + Sorting, +} from './analytics_collection_explore_table_logic'; +import { ExploreTableColumns, ExploreTables } from './analytics_collection_explore_table_types'; +import { AnalyticsCollectionToolbarLogic } from './analytics_collection_toolbar/analytics_collection_toolbar_logic'; + +jest.mock('../../../shared/kibana/kibana_logic', () => ({ + KibanaLogic: { + values: { + data: { + search: { + search: jest.fn().mockReturnValue({ subscribe: jest.fn() }), + }, + }, + }, + }, +})); + +describe('AnalyticsCollectionExplorerTablesLogic', () => { + const { mount } = new LogicMounter(AnalyticsCollectionExploreTableLogic); + + beforeEach(() => { + jest.clearAllMocks(); + + mount(); + }); + + const defaultProps = { + isLoading: false, + items: [], + pageIndex: 0, + pageSize: 10, + search: '', + selectedTable: null, + sorting: null, + totalItemsCount: 0, + }; + + it('initializes with default values', () => { + expect(AnalyticsCollectionExploreTableLogic.values).toEqual(defaultProps); + }); + + describe('reducers', () => { + it('should handle set items', () => { + const items = [ + { count: 1, query: 'test' }, + { count: 2, query: 'test2' }, + ]; + AnalyticsCollectionExploreTableLogic.actions.setItems(items); + expect(AnalyticsCollectionExploreTableLogic.values.items).toEqual(items); + }); + + it('should handle set selectedTable', () => { + const id = ExploreTables.WorsePerformers; + const sorting = { direction: 'desc', field: ExploreTableColumns.count } as Sorting; + AnalyticsCollectionExploreTableLogic.actions.setSelectedTable(id, sorting); + expect(AnalyticsCollectionExploreTableLogic.values.selectedTable).toEqual(id); + expect(AnalyticsCollectionExploreTableLogic.values.sorting).toEqual(sorting); + }); + + it('should handle set sorting', () => { + const sorting = { direction: 'asc', field: ExploreTableColumns.sessions } as Sorting; + AnalyticsCollectionExploreTableLogic.actions.onTableChange({ + sort: sorting, + }); + expect(AnalyticsCollectionExploreTableLogic.values.sorting).toEqual(sorting); + }); + + describe('isLoading', () => { + it('should handle onTableChange', () => { + AnalyticsCollectionExploreTableLogic.actions.onTableChange({ + page: { index: 2, size: 10 }, + sort: { + direction: 'asc', + field: ExploreTableColumns.sessions, + } as Sorting, + }); + expect(AnalyticsCollectionExploreTableLogic.values.isLoading).toEqual(true); + }); + + it('should handle setSearch', () => { + AnalyticsCollectionExploreTableLogic.actions.setSearch('test'); + expect(AnalyticsCollectionExploreTableLogic.values.isLoading).toEqual(true); + }); + + it('should handle setItems', () => { + AnalyticsCollectionExploreTableLogic.actions.setItems([]); + expect(AnalyticsCollectionExploreTableLogic.values.isLoading).toEqual(false); + }); + + it('should handle setSelectedTable', () => { + AnalyticsCollectionExploreTableLogic.actions.setSelectedTable(ExploreTables.TopReferrers); + expect(AnalyticsCollectionExploreTableLogic.values.isLoading).toEqual(true); + }); + + it('should handle setTimeRange', () => { + AnalyticsCollectionToolbarLogic.actions.setTimeRange({ from: 'now-7d', to: 'now' }); + expect(AnalyticsCollectionExploreTableLogic.values.isLoading).toEqual(true); + }); + + it('should handle setSearchSessionId', () => { + AnalyticsCollectionToolbarLogic.actions.setSearchSessionId('12345'); + expect(AnalyticsCollectionExploreTableLogic.values.isLoading).toEqual(true); + }); + }); + + describe('pageIndex', () => { + it('should handle setPageIndex', () => { + AnalyticsCollectionExploreTableLogic.actions.onTableChange({ + page: { index: 2, size: 10 }, + }); + expect(AnalyticsCollectionExploreTableLogic.values.pageIndex).toEqual(2); + }); + + it('should handle setSelectedTable', () => { + AnalyticsCollectionExploreTableLogic.actions.onTableChange({ + page: { index: 2, size: 10 }, + }); + AnalyticsCollectionExploreTableLogic.actions.setSelectedTable(ExploreTables.TopReferrers); + expect(AnalyticsCollectionExploreTableLogic.values.pageIndex).toEqual(0); + }); + + it('should handle reset', () => { + AnalyticsCollectionExploreTableLogic.actions.onTableChange({ + page: { index: 2, size: 10 }, + }); + AnalyticsCollectionExploreTableLogic.actions.reset(); + expect(AnalyticsCollectionExploreTableLogic.values.pageIndex).toEqual(0); + }); + + it('should handle setSearch', () => { + AnalyticsCollectionExploreTableLogic.actions.onTableChange({ + page: { index: 2, size: 10 }, + }); + AnalyticsCollectionExploreTableLogic.actions.setSearch(''); + expect(AnalyticsCollectionExploreTableLogic.values.pageIndex).toEqual(0); + }); + }); + + describe('pageSize', () => { + it('should handle setPageSize', () => { + AnalyticsCollectionExploreTableLogic.actions.onTableChange({ + page: { index: 2, size: 10 }, + }); + expect(AnalyticsCollectionExploreTableLogic.values.pageSize).toEqual(10); + }); + + it('should handle setSelectedTable', () => { + AnalyticsCollectionExploreTableLogic.actions.onTableChange({ + page: { index: 2, size: 10 }, + }); + AnalyticsCollectionExploreTableLogic.actions.setSelectedTable(ExploreTables.TopReferrers); + expect(AnalyticsCollectionExploreTableLogic.values.pageSize).toEqual(10); + }); + + it('should handle reset', () => { + AnalyticsCollectionExploreTableLogic.actions.onTableChange({ + page: { index: 2, size: 10 }, + }); + AnalyticsCollectionExploreTableLogic.actions.reset(); + expect(AnalyticsCollectionExploreTableLogic.values.pageSize).toEqual(10); + }); + }); + + describe('search', () => { + it('should handle setSearch', () => { + AnalyticsCollectionExploreTableLogic.actions.setSearch('test'); + expect(AnalyticsCollectionExploreTableLogic.values.search).toEqual('test'); + }); + + it('should handle setSelectedTable', () => { + AnalyticsCollectionExploreTableLogic.actions.setSearch('test'); + AnalyticsCollectionExploreTableLogic.actions.setSelectedTable(ExploreTables.TopReferrers); + expect(AnalyticsCollectionExploreTableLogic.values.search).toEqual(''); + }); + + it('should handle reset', () => { + AnalyticsCollectionExploreTableLogic.actions.setSearch('test'); + AnalyticsCollectionExploreTableLogic.actions.reset(); + expect(AnalyticsCollectionExploreTableLogic.values.search).toEqual(''); + }); + }); + + it('should handle totalItemsCount', () => { + AnalyticsCollectionExploreTableLogic.actions.setTotalItemsCount(100); + expect(AnalyticsCollectionExploreTableLogic.values.totalItemsCount).toEqual(100); + }); + }); + + describe('listeners', () => { + it('should fetch items when selectedTable changes', () => { + AnalyticsCollectionExploreTableLogic.actions.setSelectedTable(ExploreTables.TopReferrers); + expect(KibanaLogic.values.data.search.search).toHaveBeenCalledWith(expect.any(Object), { + indexPattern: undefined, + sessionId: undefined, + }); + }); + + it('should fetch items when timeRange changes', () => { + AnalyticsCollectionExploreTableLogic.actions.setSelectedTable(ExploreTables.WorsePerformers); + (KibanaLogic.values.data.search.search as jest.Mock).mockClear(); + + AnalyticsCollectionToolbarLogic.actions.setTimeRange({ from: 'now-7d', to: 'now' }); + expect(KibanaLogic.values.data.search.search).toHaveBeenCalledWith(expect.any(Object), { + indexPattern: undefined, + sessionId: undefined, + }); + }); + + it('should fetch items when searchSessionId changes', () => { + AnalyticsCollectionExploreTableLogic.actions.setSelectedTable(ExploreTables.WorsePerformers); + (KibanaLogic.values.data.search.search as jest.Mock).mockClear(); + + AnalyticsCollectionToolbarLogic.actions.setSearchSessionId('1234'); + expect(KibanaLogic.values.data.search.search).toHaveBeenCalledWith(expect.any(Object), { + indexPattern: undefined, + sessionId: '1234', + }); + }); + + it('should fetch items when onTableChange called', () => { + AnalyticsCollectionExploreTableLogic.actions.setSelectedTable(ExploreTables.WorsePerformers); + (KibanaLogic.values.data.search.search as jest.Mock).mockClear(); + + AnalyticsCollectionExploreTableLogic.actions.onTableChange({}); + expect(KibanaLogic.values.data.search.search).toHaveBeenCalledWith(expect.any(Object), { + indexPattern: undefined, + sessionId: undefined, + }); + }); + + it('should fetch items when search changes', () => { + AnalyticsCollectionExploreTableLogic.actions.setSelectedTable(ExploreTables.WorsePerformers); + (KibanaLogic.values.data.search.search as jest.Mock).mockClear(); + + AnalyticsCollectionExploreTableLogic.actions.setSearch('test'); + expect(KibanaLogic.values.data.search.search).toHaveBeenCalledWith(expect.any(Object), { + indexPattern: undefined, + sessionId: undefined, + }); + }); + }); +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/analytics_collection_explore_table_logic.ts b/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/analytics_collection_explore_table_logic.ts new file mode 100644 index 0000000000000..cce07541a9a2e --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/analytics_collection_explore_table_logic.ts @@ -0,0 +1,377 @@ +/* + * Copyright 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 { kea, MakeLogicType } from 'kea'; + +import { + IKibanaSearchRequest, + IKibanaSearchResponse, + isCompleteResponse, + TimeRange, +} from '@kbn/data-plugin/common'; + +import { KibanaLogic } from '../../../shared/kibana/kibana_logic'; + +import { AnalyticsCollectionDataViewLogic } from './analytics_collection_data_view_logic'; + +import { + getBaseSearchTemplate, + getPaginationRequestParams, + getPaginationRequestSizeParams, + getTotalCountRequestParams, +} from './analytics_collection_explore_table_formulas'; +import { + ExploreTableColumns, + ExploreTableItem, + ExploreTables, + SearchTermsTable, + TopClickedTable, + TopReferrersTable, + WorsePerformersTable, +} from './analytics_collection_explore_table_types'; +import { AnalyticsCollectionToolbarLogic } from './analytics_collection_toolbar/analytics_collection_toolbar_logic'; + +const BASE_PAGE_SIZE = 10; + +export interface Sorting { + direction: 'asc' | 'desc'; + field: keyof T; +} + +interface TableParams { + parseResponse(response: IKibanaSearchResponse): { items: T[]; totalCount: number }; + requestParams(props: { + pageIndex: number; + pageSize: number; + search: string; + sorting: Sorting | null; + timeRange: TimeRange; + }): IKibanaSearchRequest; +} + +const tablesParams: { + [ExploreTables.SearchTerms]: TableParams; + [ExploreTables.TopClicked]: TableParams; + [ExploreTables.TopReferrers]: TableParams; + [ExploreTables.WorsePerformers]: TableParams; +} = { + [ExploreTables.SearchTerms]: { + parseResponse: ( + response: IKibanaSearchResponse<{ + aggregations: { + searches: { buckets: Array<{ doc_count: number; key: string }> }; + totalCount: { value: number }; + }; + }> + ) => ({ + items: response.rawResponse.aggregations.searches.buckets.map((bucket) => ({ + [ExploreTableColumns.count]: bucket.doc_count, + [ExploreTableColumns.searchTerms]: bucket.key, + })), + totalCount: response.rawResponse.aggregations.totalCount.value, + }), + requestParams: ( + { timeRange, sorting, pageIndex, pageSize, search }, + aggregationFieldName = 'search.query' + ) => + getBaseSearchTemplate( + aggregationFieldName, + { search, timeRange }, + { + searches: { + terms: { + ...getPaginationRequestSizeParams(pageIndex, pageSize), + field: aggregationFieldName, + order: sorting + ? { + [sorting.field === ExploreTableColumns.count ? '_count' : '_key']: + sorting.direction, + } + : undefined, + }, + ...getPaginationRequestParams(pageIndex, pageSize), + }, + ...getTotalCountRequestParams(aggregationFieldName), + } + ), + }, + [ExploreTables.WorsePerformers]: { + parseResponse: ( + response: IKibanaSearchResponse<{ + aggregations: { + formula: { + searches: { buckets: Array<{ doc_count: number; key: string }> }; + totalCount: { value: number }; + }; + }; + }> + ) => ({ + items: response.rawResponse.aggregations.formula.searches.buckets.map((bucket) => ({ + [ExploreTableColumns.count]: bucket.doc_count, + [ExploreTableColumns.query]: bucket.key, + })), + totalCount: response.rawResponse.aggregations.formula.totalCount.value, + }), + requestParams: ( + { timeRange, sorting, pageIndex, pageSize, search }, + aggregationFieldName = 'search.query' + ) => + getBaseSearchTemplate( + aggregationFieldName, + { search, timeRange }, + { + formula: { + aggs: { + ...getTotalCountRequestParams(aggregationFieldName), + searches: { + terms: { + ...getPaginationRequestSizeParams(pageIndex, pageSize), + field: aggregationFieldName, + order: sorting + ? { + [sorting?.field === ExploreTableColumns.count ? '_count' : '_key']: + sorting?.direction, + } + : undefined, + }, + ...getPaginationRequestParams(pageIndex, pageSize), + }, + }, + filter: { term: { 'search.results.total_results': '0' } }, + }, + } + ), + }, + [ExploreTables.TopClicked]: { + parseResponse: ( + response: IKibanaSearchResponse<{ + aggregations: { + formula: { + searches: { buckets: Array<{ doc_count: number; key: string }> }; + totalCount: { value: number }; + }; + }; + }> + ) => ({ + items: response.rawResponse.aggregations.formula.searches.buckets.map((bucket) => ({ + [ExploreTableColumns.count]: bucket.doc_count, + [ExploreTableColumns.page]: bucket.key, + })), + totalCount: response.rawResponse.aggregations.formula.totalCount.value, + }), + requestParams: ( + { timeRange, sorting, pageIndex, pageSize, search }, + aggregationFieldName = 'search.results.items.page.url' + ) => + getBaseSearchTemplate( + aggregationFieldName, + { search, timeRange }, + { + formula: { + aggs: { + ...getTotalCountRequestParams(aggregationFieldName), + searches: { + terms: { + ...getPaginationRequestSizeParams(pageIndex, pageSize), + field: aggregationFieldName, + order: sorting + ? { + [sorting.field === ExploreTableColumns.count ? '_count' : '_key']: + sorting.direction, + } + : undefined, + }, + ...getPaginationRequestParams(pageIndex, pageSize), + }, + }, + filter: { term: { 'event.action': 'search_click' } }, + }, + } + ), + }, + [ExploreTables.TopReferrers]: { + parseResponse: ( + response: IKibanaSearchResponse<{ + aggregations: { + formula: { + searches: { buckets: Array<{ doc_count: number; key: string }> }; + totalCount: { value: number }; + }; + }; + }> + ) => ({ + items: response.rawResponse.aggregations.formula.searches.buckets.map((bucket) => ({ + [ExploreTableColumns.sessions]: bucket.doc_count, + [ExploreTableColumns.page]: bucket.key, + })), + totalCount: response.rawResponse.aggregations.formula.totalCount.value, + }), + requestParams: ( + { timeRange, sorting, pageIndex, pageSize, search }, + aggregationFieldName = 'page.referrer' + ) => + getBaseSearchTemplate( + aggregationFieldName, + { search, timeRange }, + { + formula: { + aggs: { + ...getTotalCountRequestParams(aggregationFieldName), + searches: { + terms: { + ...getPaginationRequestSizeParams(pageIndex, pageSize), + field: aggregationFieldName, + order: sorting + ? { + [sorting?.field === ExploreTableColumns.sessions ? '_count' : '_key']: + sorting?.direction, + } + : undefined, + }, + ...getPaginationRequestParams(pageIndex, pageSize), + }, + }, + filter: { term: { 'event.action': 'page_view' } }, + }, + } + ), + }, +}; + +export interface AnalyticsCollectionExploreTableLogicValues { + isLoading: boolean; + items: ExploreTableItem[]; + pageIndex: number; + pageSize: number; + search: string; + selectedTable: ExploreTables | null; + sorting: Sorting | null; + totalItemsCount: number; +} + +export interface AnalyticsCollectionExploreTableLogicActions { + onTableChange(state: { page?: { index: number; size: number }; sort?: Sorting }): { + page?: { index: number; size: number }; + sort?: Sorting; + }; + reset(): void; + setItems(items: ExploreTableItem[]): { items: ExploreTableItem[] }; + setSearch(search: string): { search: string }; + setSelectedTable( + id: ExploreTables | null, + sorting?: Sorting + ): { id: ExploreTables | null; sorting?: Sorting }; + setTotalItemsCount(count: number): { count: number }; +} + +export const AnalyticsCollectionExploreTableLogic = kea< + MakeLogicType< + AnalyticsCollectionExploreTableLogicValues, + AnalyticsCollectionExploreTableLogicActions + > +>({ + actions: { + onTableChange: ({ page, sort }) => ({ page, sort }), + reset: true, + setItems: (items) => ({ items }), + setSearch: (search) => ({ search }), + setSelectedTable: (id, sorting) => ({ id, sorting }), + setTotalItemsCount: (count) => ({ count }), + }, + listeners: ({ actions, values }) => { + const fetchItems = () => { + if (values.selectedTable === null || !(values.selectedTable in tablesParams)) { + return; + } + + const { requestParams, parseResponse } = tablesParams[values.selectedTable] as TableParams; + const timeRange = AnalyticsCollectionToolbarLogic.values.timeRange; + + const search$ = KibanaLogic.values.data.search + .search( + requestParams({ + pageIndex: values.pageIndex, + pageSize: values.pageSize, + search: values.search, + sorting: values.sorting, + timeRange, + }), + { + indexPattern: AnalyticsCollectionDataViewLogic.values.dataView || undefined, + sessionId: AnalyticsCollectionToolbarLogic.values.searchSessionId, + } + ) + .subscribe({ + error: (e) => { + KibanaLogic.values.data.search.showError(e); + }, + next: (response) => { + if (isCompleteResponse(response)) { + const { items, totalCount } = parseResponse(response); + + actions.setItems(items); + actions.setTotalItemsCount(totalCount); + search$.unsubscribe(); + } + }, + }); + }; + + return { + onTableChange: fetchItems, + setSearch: fetchItems, + setSelectedTable: fetchItems, + [AnalyticsCollectionToolbarLogic.actionTypes.setTimeRange]: fetchItems, + [AnalyticsCollectionToolbarLogic.actionTypes.setSearchSessionId]: fetchItems, + }; + }, + path: ['enterprise_search', 'analytics', 'collections', 'explore', 'table'], + reducers: () => ({ + isLoading: [ + false, + { + onTableChange: () => true, + setItems: () => false, + setSearch: () => true, + setSelectedTable: () => true, + setTableState: () => true, + [AnalyticsCollectionToolbarLogic.actionTypes.setTimeRange]: () => true, + [AnalyticsCollectionToolbarLogic.actionTypes.setSearchSessionId]: () => true, + }, + ], + items: [[], { setItems: (_, { items }) => items }], + pageIndex: [ + 0, + { + onTableChange: (_, { page }) => page?.index || 0, + reset: () => 0, + setSearch: () => 0, + setSelectedTable: () => 0, + }, + ], + pageSize: [ + BASE_PAGE_SIZE, + { + onTableChange: (_, { page }) => page?.size || BASE_PAGE_SIZE, + reset: () => BASE_PAGE_SIZE, + }, + ], + search: [ + '', + { reset: () => '', setSearch: (_, { search }) => search, setSelectedTable: () => '' }, + ], + selectedTable: [null, { setSelectedTable: (_, { id }) => id }], + sorting: [ + null, + { + onTableChange: (_, { sort = null }) => sort, + setSelectedTable: (_, { sorting = null }) => sorting, + }, + ], + totalItemsCount: [0, { setTotalItemsCount: (_, { count }) => count }], + }), +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/analytics_collection_explore_table/analytics_collection_explore_table_types.ts b/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/analytics_collection_explore_table_types.ts similarity index 100% rename from x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/analytics_collection_explore_table/analytics_collection_explore_table_types.ts rename to x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/analytics_collection_explore_table_types.ts diff --git a/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/analytics_collection_explorer/analytics_collection_explorer.test.tsx b/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/analytics_collection_explorer/analytics_collection_explorer.test.tsx new file mode 100644 index 0000000000000..bc9d1206ff41a --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/analytics_collection_explorer/analytics_collection_explorer.test.tsx @@ -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 { setMockActions, setMockValues } from '../../../../__mocks__/kea_logic'; + +import React from 'react'; + +import { shallow } from 'enzyme'; + +import { EnterpriseSearchAnalyticsPageTemplate } from '../../layout/page_template'; + +import { AnalyticsCollectionToolbar } from '../analytics_collection_toolbar/analytics_collection_toolbar'; + +import { AnalyticsCollectionExplorer } from './analytics_collection_explorer'; +import { AnalyticsCollectionExplorerTable } from './analytics_collection_explorer_table'; + +describe('AnalyticsCollectionExplorer', () => { + const mockValues = { + analyticsCollection: { event_data_stream: 'test_data_stream', name: 'Mock Collection' }, + refreshInterval: { pause: false, value: 1000 }, + timeRange: { from: 'now-15m', to: 'now' }, + }; + const mockActions = { reset: jest.fn() }; + + beforeAll(() => { + jest.clearAllMocks(); + + setMockValues(mockValues); + setMockActions(mockActions); + }); + + afterAll(() => { + jest.resetAllMocks(); + }); + + it('renders the AnalyticsCollectionExplorerTable', () => { + const wrapper = shallow(); + expect(wrapper.find(AnalyticsCollectionExplorerTable)).toHaveLength(1); + }); + + it('renders the EnterpriseSearchAnalyticsPageTemplate', () => { + const wrapper = shallow(); + expect(wrapper.find(EnterpriseSearchAnalyticsPageTemplate)).toHaveLength(1); + }); + + it('passes the expected props to EnterpriseSearchAnalyticsPageTemplate', () => { + const wrapper = shallow().find( + EnterpriseSearchAnalyticsPageTemplate + ); + + expect(wrapper.prop('pageChrome')).toEqual([mockValues.analyticsCollection.name]); + expect(wrapper.prop('analyticsName')).toEqual(mockValues.analyticsCollection.name); + expect(wrapper.prop('pageHeader')).toEqual({ + bottomBorder: false, + pageTitle: 'Explorer', + rightSideItems: [], + }); + }); +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/analytics_collection_explorer/analytics_collection_explorer.tsx b/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/analytics_collection_explorer/analytics_collection_explorer.tsx new file mode 100644 index 0000000000000..80a4b87f27d92 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/analytics_collection_explorer/analytics_collection_explorer.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 React, { useEffect } from 'react'; + +import { useActions, useValues } from 'kea'; + +import { i18n } from '@kbn/i18n'; + +import { EnterpriseSearchAnalyticsPageTemplate } from '../../layout/page_template'; +import { AnalyticsCollectionExploreTableLogic } from '../analytics_collection_explore_table_logic'; +import { AnalyticsCollectionToolbar } from '../analytics_collection_toolbar/analytics_collection_toolbar'; +import { FetchAnalyticsCollectionLogic } from '../fetch_analytics_collection_logic'; + +import { AnalyticsCollectionExplorerTable } from './analytics_collection_explorer_table'; + +export const AnalyticsCollectionExplorer: React.FC = ({}) => { + const { analyticsCollection } = useValues(FetchAnalyticsCollectionLogic); + const { reset } = useActions(AnalyticsCollectionExploreTableLogic); + + useEffect(() => { + return () => { + reset(); + }; + }, []); + + return ( + ], + }} + > + + + ); +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/analytics_collection_explorer/analytics_collection_explorer_callout.tsx b/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/analytics_collection_explorer/analytics_collection_explorer_callout.tsx new file mode 100644 index 0000000000000..da24d06f81be8 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/analytics_collection_explorer/analytics_collection_explorer_callout.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 { useValues } from 'kea'; + +import { EuiButton, EuiCallOut } from '@elastic/eui'; + +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n-react'; +import { RedirectAppLinks } from '@kbn/shared-ux-link-redirect-app'; + +import { KibanaLogic } from '../../../../shared/kibana'; +import { useDiscoverLink } from '../use_discover_link'; + +export const AnalyticsCollectionExplorerCallout: React.FC = () => { + const { application } = useValues(KibanaLogic); + const discoverLink = useDiscoverLink(); + + return discoverLink ? ( + +

+ +

+ + + + + + +
+ ) : null; +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/analytics_collection_explorer/analytics_collection_explorer_table.test.tsx b/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/analytics_collection_explorer/analytics_collection_explorer_table.test.tsx new file mode 100644 index 0000000000000..fc702a0493369 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/analytics_collection_explorer/analytics_collection_explorer_table.test.tsx @@ -0,0 +1,70 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { setMockActions, setMockValues } from '../../../../__mocks__/kea_logic'; + +import React from 'react'; + +import { mount, shallow } from 'enzyme'; + +import { ExploreTables } from '../analytics_collection_explore_table_types'; + +import { AnalyticsCollectionExplorerTable } from './analytics_collection_explorer_table'; + +describe('AnalyticsCollectionExplorerTable', () => { + const mockActions = { + onTableChange: jest.fn(), + setPageIndex: jest.fn(), + setPageSize: jest.fn(), + setSearch: jest.fn(), + setSelectedTable: jest.fn(), + }; + + beforeEach(() => { + jest.clearAllMocks(); + + setMockValues({ items: [], selectedTable: ExploreTables.TopClicked }); + setMockActions(mockActions); + }); + + it('should set default selectedTable', () => { + setMockValues({ items: [], selectedTable: null }); + const wrapper = mount(); + + wrapper.update(); + + expect(mockActions.setSelectedTable).toHaveBeenCalledWith(ExploreTables.SearchTerms, { + direction: 'desc', + field: 'count', + }); + }); + + it('should call setSelectedTable when click on a tab', () => { + const tabs = shallow().find('EuiTab'); + + expect(tabs.length).toBe(4); + + tabs.at(2).simulate('click'); + expect(mockActions.setSelectedTable).toHaveBeenCalledWith(ExploreTables.WorsePerformers, { + direction: 'desc', + field: 'count', + }); + }); + + it('should call onTableChange when table called onChange', () => { + const table = shallow().find('EuiBasicTable'); + + table.simulate('change', { + page: { index: 23, size: 44 }, + sort: { direction: 'asc', field: 'test' }, + }); + expect(mockActions.onTableChange).toHaveBeenCalledWith({ + page: { index: 23, size: 44 }, + sort: { direction: 'asc', field: 'test' }, + }); + }); +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/analytics_collection_explorer/analytics_collection_explorer_table.tsx b/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/analytics_collection_explorer/analytics_collection_explorer_table.tsx new file mode 100644 index 0000000000000..fe55aae3d1692 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/analytics_collection_explorer/analytics_collection_explorer_table.tsx @@ -0,0 +1,327 @@ +/* + * Copyright 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 } from 'react'; + +import { useActions, useValues } from 'kea'; + +import { + Criteria, + EuiBasicTable, + EuiBasicTableColumn, + EuiFieldSearch, + EuiFlexGroup, + EuiHorizontalRule, + EuiSpacer, + EuiTab, + EuiTabs, + EuiText, + useEuiTheme, +} from '@elastic/eui'; +import { + EuiTableFieldDataColumnType, + EuiTableSortingType, +} from '@elastic/eui/src/components/basic_table/table_types'; +import { UseEuiTheme } from '@elastic/eui/src/services/theme/hooks'; + +import { i18n } from '@kbn/i18n'; + +import { FormattedMessage } from '@kbn/i18n-react'; + +import { AnalyticsCollectionExploreTableLogic } from '../analytics_collection_explore_table_logic'; +import { + ExploreTableColumns, + ExploreTableItem, + ExploreTables, + SearchTermsTable, + TopClickedTable, + TopReferrersTable, + WorsePerformersTable, +} from '../analytics_collection_explore_table_types'; + +import { AnalyticsCollectionExplorerCallout } from './analytics_collection_explorer_callout'; + +interface TableSetting { + columns: Array< + EuiBasicTableColumn & { + render?: (euiTheme: UseEuiTheme['euiTheme']) => EuiTableFieldDataColumnType['render']; + } + >; + sorting: EuiTableSortingType; +} + +const tabs: Array<{ id: ExploreTables; name: string }> = [ + { + id: ExploreTables.SearchTerms, + name: i18n.translate( + 'xpack.enterpriseSearch.analytics.collections.collectionsView.explorer.searchTermsTab', + { defaultMessage: 'Search terms' } + ), + }, + { + id: ExploreTables.TopClicked, + name: i18n.translate( + 'xpack.enterpriseSearch.analytics.collections.collectionsView.explorer.topClickedTab', + { defaultMessage: 'Top clicked results' } + ), + }, + { + id: ExploreTables.WorsePerformers, + name: i18n.translate( + 'xpack.enterpriseSearch.analytics.collections.collectionsView.explorer.noResultsTab', + { defaultMessage: 'No results' } + ), + }, + { + id: ExploreTables.TopReferrers, + name: i18n.translate( + 'xpack.enterpriseSearch.analytics.collections.collectionsView.explorer.referrersTab', + { defaultMessage: 'Referrers' } + ), + }, +]; + +const tableSettings: { + [ExploreTables.SearchTerms]: TableSetting; + [ExploreTables.TopClicked]: TableSetting; + [ExploreTables.TopReferrers]: TableSetting; + [ExploreTables.WorsePerformers]: TableSetting; +} = { + [ExploreTables.SearchTerms]: { + columns: [ + { + field: ExploreTableColumns.searchTerms, + name: i18n.translate( + 'xpack.enterpriseSearch.analytics.collections.collectionsView.exploreTable.searchTerms', + { defaultMessage: 'Search Terms' } + ), + sortable: true, + truncateText: true, + }, + { + align: 'right', + field: ExploreTableColumns.count, + name: i18n.translate( + 'xpack.enterpriseSearch.analytics.collections.collectionsView.exploreTable.count', + { defaultMessage: 'Count' } + ), + sortable: true, + truncateText: true, + }, + ], + sorting: { + sort: { + direction: 'desc', + field: ExploreTableColumns.count, + }, + }, + }, + [ExploreTables.WorsePerformers]: { + columns: [ + { + field: ExploreTableColumns.query, + name: i18n.translate( + 'xpack.enterpriseSearch.analytics.collections.collectionsView.exploreTable.query', + { defaultMessage: 'Query' } + ), + sortable: true, + truncateText: true, + }, + { + align: 'right', + field: ExploreTableColumns.count, + name: i18n.translate( + 'xpack.enterpriseSearch.analytics.collections.collectionsView.exploreTable.count', + { defaultMessage: 'Count' } + ), + sortable: true, + truncateText: true, + }, + ], + sorting: { + sort: { + direction: 'desc', + field: ExploreTableColumns.count, + }, + }, + }, + [ExploreTables.TopClicked]: { + columns: [ + { + field: ExploreTableColumns.page, + name: i18n.translate( + 'xpack.enterpriseSearch.analytics.collections.collectionsView.exploreTable.page', + { defaultMessage: 'Page' } + ), + render: (euiTheme: UseEuiTheme['euiTheme']) => (value: string) => + ( + +

{value}

+
+ ), + sortable: true, + truncateText: true, + }, + { + align: 'right', + field: ExploreTableColumns.count, + name: i18n.translate( + 'xpack.enterpriseSearch.analytics.collections.collectionsView.exploreTable.count', + { defaultMessage: 'Count' } + ), + sortable: true, + truncateText: true, + }, + ], + sorting: { + sort: { + direction: 'desc', + field: ExploreTableColumns.count, + }, + }, + }, + [ExploreTables.TopReferrers]: { + columns: [ + { + field: ExploreTableColumns.page, + name: i18n.translate( + 'xpack.enterpriseSearch.analytics.collections.collectionsView.exploreTable.page', + { defaultMessage: 'Page' } + ), + render: (euiTheme: UseEuiTheme['euiTheme']) => (value: string) => + ( + +

{value}

+
+ ), + sortable: true, + truncateText: true, + }, + { + align: 'right', + field: ExploreTableColumns.sessions, + name: i18n.translate( + 'xpack.enterpriseSearch.analytics.collections.collectionsView.exploreTable.session', + { defaultMessage: 'Session' } + ), + sortable: true, + truncateText: true, + }, + ], + sorting: { + sort: { + direction: 'desc', + field: ExploreTableColumns.sessions, + }, + }, + }, +}; + +export const AnalyticsCollectionExplorerTable = () => { + const { euiTheme } = useEuiTheme(); + const { onTableChange, setSelectedTable, setSearch } = useActions( + AnalyticsCollectionExploreTableLogic + ); + const { items, isLoading, pageIndex, pageSize, search, selectedTable, sorting, totalItemsCount } = + useValues(AnalyticsCollectionExploreTableLogic); + let table = selectedTable !== null && (tableSettings[selectedTable] as TableSetting); + if (table) { + table = { + ...table, + columns: table.columns.map((column) => ({ + ...column, + render: column.render?.(euiTheme), + })) as TableSetting['columns'], + sorting: { ...table.sorting, sort: sorting || undefined }, + }; + } + const handleTableChange = ({ sort, page }: Criteria) => { + onTableChange({ page, sort }); + }; + + useEffect(() => { + if (!selectedTable) { + const firstTabId = tabs[0].id; + + setSelectedTable(firstTabId, (tableSettings[firstTabId] as TableSetting)?.sorting?.sort); + } + }, []); + + return ( + + + {tabs?.map(({ id, name }) => ( + setSelectedTable(id, (tableSettings[id] as TableSetting)?.sorting?.sort)} + isSelected={id === selectedTable} + > + {name} + + ))} + + + {table && ( + + setSearch(event.target.value)} + isClearable + incremental + fullWidth + /> + + + + + + {pageSize * pageIndex + Number(!!items.length)}- + {pageSize * pageIndex + items.length} + + ), + totalItemsCount, + }} + /> + + + + + + + + + )} + + + + ); +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/analytics_collection_overview/analytics_collection_chart.test.tsx b/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/analytics_collection_overview/analytics_collection_chart.test.tsx index 31831805a7f4f..ff0b8f4788df7 100644 --- a/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/analytics_collection_overview/analytics_collection_chart.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/analytics_collection_overview/analytics_collection_chart.test.tsx @@ -35,12 +35,9 @@ describe('AnalyticsCollectionChart', () => { from: moment().subtract(7, 'days').toISOString(), to: moment().toISOString(), }; - const mockedDataViewQuery = 'mockedDataViewQuery'; const defaultProps = { data: {}, - dataViewQuery: mockedDataViewQuery, - id: 'mockedId', isLoading: false, selectedChart: FilterBy.Searches, setSelectedChart: jest.fn(), diff --git a/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/analytics_collection_overview/analytics_collection_chart.tsx b/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/analytics_collection_overview/analytics_collection_chart.tsx index 9a86c758bf585..b518bc8326662 100644 --- a/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/analytics_collection_overview/analytics_collection_chart.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/analytics_collection_overview/analytics_collection_chart.tsx @@ -42,8 +42,7 @@ const DEFAULT_STROKE_WIDTH = 1; const HOVER_STROKE_WIDTH = 3; const CHART_HEIGHT = 490; -interface AnalyticsCollectionChartProps extends WithLensDataInputProps { - dataViewQuery: string; +interface AnalyticsCollectionChartProps extends Pick { selectedChart: FilterBy | null; setSelectedChart(chart: FilterBy): void; } @@ -303,6 +302,5 @@ export const AnalyticsCollectionChartWithLens = withLensData< visualizationType: 'lnsXY', }; }, - getDataViewQuery: (props) => props.dataViewQuery, initialValues, }); diff --git a/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/analytics_collection_overview/analytics_collection_metric.tsx b/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/analytics_collection_overview/analytics_collection_metric.tsx index 7660c9e92fa47..9c0e44c02e2f6 100644 --- a/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/analytics_collection_overview/analytics_collection_metric.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/analytics_collection_overview/analytics_collection_metric.tsx @@ -54,7 +54,6 @@ const getMetricStatus = (metric: number): MetricStatus => { }; interface AnalyticsCollectionViewMetricProps { - dataViewQuery: string; getFormula: (shift?: string) => string; isSelected?: boolean; name: string; @@ -230,6 +229,5 @@ export const AnalyticsCollectionViewMetricWithLens = withLensData< visualizationType: 'lnsMetric', }; }, - getDataViewQuery: (props) => props.dataViewQuery, initialValues, }); diff --git a/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/analytics_collection_overview/analytics_collection_overview.test.tsx b/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/analytics_collection_overview/analytics_collection_overview.test.tsx index b9e54854978fd..30cc657a22c2f 100644 --- a/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/analytics_collection_overview/analytics_collection_overview.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/analytics_collection_overview/analytics_collection_overview.test.tsx @@ -38,9 +38,9 @@ const mockValues = { }; const mockActions = { + analyticsEventsExist: jest.fn(), fetchAnalyticsCollection: jest.fn(), fetchAnalyticsCollectionDataViewId: jest.fn(), - analyticsEventsExist: jest.fn(), setTimeRange: jest.fn(), }; @@ -92,7 +92,7 @@ describe('AnalyticsOverView', () => { ); expect(wrapper?.find(AnalyticsCollectionChartWithLens)).toHaveLength(1); expect(wrapper?.find(AnalyticsCollectionChartWithLens).props()).toEqual({ - dataViewQuery: 'analytics-events-example', + collection: mockValues.analyticsCollection, id: 'analytics-collection-chart-Analytics-Collection-1', searchSessionId: 'session-id', selectedChart: 'Searches', diff --git a/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/analytics_collection_overview/analytics_collection_overview.tsx b/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/analytics_collection_overview/analytics_collection_overview.tsx index 04a8ab8f675c5..5ec96b3103929 100644 --- a/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/analytics_collection_overview/analytics_collection_overview.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/analytics_collection_overview/analytics_collection_overview.tsx @@ -18,14 +18,13 @@ import { FilterBy, getFormulaByFilter } from '../../../utils/get_formula_by_filt import { EnterpriseSearchAnalyticsPageTemplate } from '../../layout/page_template'; -import { AnalyticsCollectionExploreTable } from '../analytics_collection_explore_table/analytics_collection_explore_table'; - import { AnalyticsCollectionNoEventsCallout } from '../analytics_collection_no_events_callout/analytics_collection_no_events_callout'; import { AnalyticsCollectionToolbar } from '../analytics_collection_toolbar/analytics_collection_toolbar'; import { AnalyticsCollectionToolbarLogic } from '../analytics_collection_toolbar/analytics_collection_toolbar_logic'; import { AnalyticsCollectionChartWithLens } from './analytics_collection_chart'; import { AnalyticsCollectionViewMetricWithLens } from './analytics_collection_metric'; +import { AnalyticsCollectionOverviewTable } from './analytics_collection_overview_table'; const filters = [ { @@ -107,7 +106,7 @@ export const AnalyticsCollectionOverview: React.FC - + ); diff --git a/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/analytics_collection_explore_table/analytics_collection_explore_table.test.tsx b/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/analytics_collection_overview/analytics_collection_overview_table.test.tsx similarity index 70% rename from x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/analytics_collection_explore_table/analytics_collection_explore_table.test.tsx rename to x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/analytics_collection_overview/analytics_collection_overview_table.test.tsx index f949639d534a9..e6e553dc51792 100644 --- a/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/analytics_collection_explore_table/analytics_collection_explore_table.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/analytics_collection_overview/analytics_collection_overview_table.test.tsx @@ -15,10 +15,11 @@ import { EuiBasicTable, EuiTab } from '@elastic/eui'; import { FilterBy } from '../../../utils/get_formula_by_filter'; -import { AnalyticsCollectionExploreTable } from './analytics_collection_explore_table'; -import { ExploreTables } from './analytics_collection_explore_table_types'; +import { ExploreTables } from '../analytics_collection_explore_table_types'; -describe('AnalyticsCollectionExploreTable', () => { +import { AnalyticsCollectionOverviewTable } from './analytics_collection_overview_table'; + +describe('AnalyticsCollectionOverviewTable', () => { const mockValues = { activeTableId: 'search_terms', analyticsCollection: { @@ -41,7 +42,7 @@ describe('AnalyticsCollectionExploreTable', () => { }); it('should call setSelectedTable with the correct table id when a tab is clicked', () => { - const wrapper = shallow(); + const wrapper = shallow(); const topReferrersTab = wrapper.find(EuiTab).at(0); topReferrersTab.simulate('click'); @@ -53,14 +54,9 @@ describe('AnalyticsCollectionExploreTable', () => { }); }); - it('should call findDataView with the active table ID and search filter when mounted', () => { - mount(); - expect(mockActions.findDataView).toHaveBeenCalledWith(mockValues.analyticsCollection); - }); - it('should render a table with the selectedTable', () => { setMockValues({ ...mockValues, selectedTable: ExploreTables.WorsePerformers }); - const wrapper = mount(); + const wrapper = mount(); expect(wrapper.find(EuiBasicTable).prop('itemId')).toBe(ExploreTables.WorsePerformers); }); }); diff --git a/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/analytics_collection_explore_table/analytics_collection_explore_table.tsx b/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/analytics_collection_overview/analytics_collection_overview_table.tsx similarity index 94% rename from x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/analytics_collection_explore_table/analytics_collection_explore_table.tsx rename to x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/analytics_collection_overview/analytics_collection_overview_table.tsx index 7b77cdb05cf61..8538b11f748fe 100644 --- a/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/analytics_collection_explore_table/analytics_collection_explore_table.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/analytics_collection_overview/analytics_collection_overview_table.tsx @@ -33,9 +33,8 @@ import { generateEncodedPath } from '../../../../shared/encode_path_params'; import { KibanaLogic } from '../../../../shared/kibana'; import { COLLECTION_EXPLORER_PATH } from '../../../routes'; import { FilterBy } from '../../../utils/get_formula_by_filter'; -import { FetchAnalyticsCollectionLogic } from '../fetch_analytics_collection_logic'; -import { AnalyticsCollectionExploreTableLogic } from './analytics_collection_explore_table_logic'; +import { AnalyticsCollectionExploreTableLogic } from '../analytics_collection_explore_table_logic'; import { ExploreTableColumns, ExploreTableItem, @@ -44,7 +43,8 @@ import { TopClickedTable, TopReferrersTable, WorsePerformersTable, -} from './analytics_collection_explore_table_types'; +} from '../analytics_collection_explore_table_types'; +import { FetchAnalyticsCollectionLogic } from '../fetch_analytics_collection_logic'; const tabsByFilter: Record> = { [FilterBy.Searches]: [ @@ -230,28 +230,22 @@ const tableSettings: { }, }; -interface AnalyticsCollectionExploreTableProps { +interface AnalyticsCollectionOverviewTableProps { filterBy: FilterBy; } -export const AnalyticsCollectionExploreTable: React.FC = ({ +export const AnalyticsCollectionOverviewTable: React.FC = ({ filterBy, }) => { const { euiTheme } = useEuiTheme(); const { navigateToUrl } = useValues(KibanaLogic); const { analyticsCollection } = useValues(FetchAnalyticsCollectionLogic); - const { findDataView, setSelectedTable, setSorting } = useActions( - AnalyticsCollectionExploreTableLogic - ); + const { onTableChange, setSelectedTable } = useActions(AnalyticsCollectionExploreTableLogic); const { items, isLoading, selectedTable, sorting } = useValues( AnalyticsCollectionExploreTableLogic ); const tabs = tabsByFilter[filterBy]; - useEffect(() => { - findDataView(analyticsCollection); - }, [analyticsCollection]); - useEffect(() => { const firstTableInTabsId = tabs[0].id; @@ -292,7 +286,7 @@ export const AnalyticsCollectionExploreTable: React.FC { - setSorting(sort); + onTableChange({ sort }); }} /> ) diff --git a/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/analytics_collection_toolbar/analytics_collection_toolbar.test.tsx b/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/analytics_collection_toolbar/analytics_collection_toolbar.test.tsx index 438d533d126f2..0cdf0fb5d45fa 100644 --- a/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/analytics_collection_toolbar/analytics_collection_toolbar.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/analytics_collection_toolbar/analytics_collection_toolbar.test.tsx @@ -37,7 +37,7 @@ describe('AnalyticsCollectionToolbar', () => { events_datastream: 'test-events', name: 'test', } as AnalyticsCollection, - dataViewId: 'data-view-test', + dataView: { id: 'data-view-test' }, isLoading: false, refreshInterval: { pause: false, value: 10000 }, timeRange: { from: 'now-90d', to: 'now' }, @@ -93,7 +93,9 @@ describe('AnalyticsCollectionToolbar', () => { expect(exploreInDiscoverItem).toHaveLength(1); - expect(exploreInDiscoverItem.prop('href')).toBe("/app/discover#/?_a=(index:'data-view-test')"); + expect(exploreInDiscoverItem.prop('href')).toBe( + "/app/discover#/?_a=(index:'data-view-test')&_g=(filters:!(),refreshInterval:(pause:!f,value:10000),time:(from:now-90d,to:now))" + ); }); it('should correct link to the manage datastream link', () => { diff --git a/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/analytics_collection_toolbar/analytics_collection_toolbar.tsx b/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/analytics_collection_toolbar/analytics_collection_toolbar.tsx index 4162b13569089..66122502afb78 100644 --- a/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/analytics_collection_toolbar/analytics_collection_toolbar.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/analytics_collection_toolbar/analytics_collection_toolbar.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import React, { useEffect, useState } from 'react'; +import React, { useState } from 'react'; import { useActions, useValues } from 'kea'; @@ -23,10 +23,12 @@ import { import { OnTimeChangeProps } from '@elastic/eui/src/components/date_picker/super_date_picker/super_date_picker'; import { OnRefreshChangeProps } from '@elastic/eui/src/components/date_picker/types'; + import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n-react'; -import { RedirectAppLinks } from '@kbn/kibana-react-plugin/public'; + +import { RedirectAppLinks } from '@kbn/shared-ux-link-redirect-app'; import { generateEncodedPath } from '../../../../shared/encode_path_params'; @@ -34,6 +36,7 @@ import { KibanaLogic } from '../../../../shared/kibana'; import { COLLECTION_INTEGRATE_PATH } from '../../../routes'; import { DeleteAnalyticsCollectionLogic } from '../delete_analytics_collection_logic'; import { FetchAnalyticsCollectionLogic } from '../fetch_analytics_collection_logic'; +import { useDiscoverLink } from '../use_discover_link'; import { AnalyticsCollectionToolbarLogic } from './analytics_collection_toolbar_logic'; @@ -76,18 +79,16 @@ const defaultQuickRanges: EuiSuperDatePickerCommonRange[] = [ ]; export const AnalyticsCollectionToolbar: React.FC = () => { + const discoverLink = useDiscoverLink(); const [isPopoverOpen, setPopover] = useState(false); const { application, navigateToUrl } = useValues(KibanaLogic); const { analyticsCollection } = useValues(FetchAnalyticsCollectionLogic); - const { setTimeRange, setRefreshInterval, findDataViewId, onTimeRefresh } = useActions( + const { setTimeRange, setRefreshInterval, onTimeRefresh } = useActions( AnalyticsCollectionToolbarLogic ); - const { refreshInterval, timeRange, dataViewId } = useValues(AnalyticsCollectionToolbarLogic); + const { refreshInterval, timeRange } = useValues(AnalyticsCollectionToolbarLogic); const { deleteAnalyticsCollection } = useActions(DeleteAnalyticsCollectionLogic); const { isLoading } = useValues(DeleteAnalyticsCollectionLogic); - const discoverUrl = application.getUrlForApp('discover', { - path: `#/?_a=(index:'${dataViewId}')`, - }); const manageDatastreamUrl = application.getUrlForApp('management', { path: '/data/index_management/data_streams/' + analyticsCollection.events_datastream, }); @@ -104,10 +105,6 @@ export const AnalyticsCollectionToolbar: React.FC = () => { setRefreshInterval({ pause, value }); }; - useEffect(() => { - if (analyticsCollection) findDataViewId(analyticsCollection); - }, [analyticsCollection]); - return ( @@ -126,88 +123,92 @@ export const AnalyticsCollectionToolbar: React.FC = () => { /> - - - - - } - isOpen={isPopoverOpen} - closePopover={closePopover} - anchorPosition="downRight" - panelPaddingSize="none" - > - - - navigateToUrl( - generateEncodedPath(COLLECTION_INTEGRATE_PATH, { - name: analyticsCollection.name, - }) - ) - } - > - - - - + + + + + + } + isOpen={isPopoverOpen} + closePopover={closePopover} + anchorPosition="downRight" + panelPaddingSize="none" + > + + navigateToUrl( + generateEncodedPath(COLLECTION_INTEGRATE_PATH, { + name: analyticsCollection.name, + }) + ) + } > - - - - { - deleteAnalyticsCollection(analyticsCollection.name); - }} - > - - - - - - + + {discoverLink && ( + + + + )} + + + { + deleteAnalyticsCollection(analyticsCollection.name); + }} + > + + + + + + + ); }; diff --git a/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/analytics_collection_toolbar/analytics_collection_toolbar_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/analytics_collection_toolbar/analytics_collection_toolbar_logic.test.ts index f9c2ac5f4bfb5..ae78ff6198830 100644 --- a/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/analytics_collection_toolbar/analytics_collection_toolbar_logic.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/analytics_collection_toolbar/analytics_collection_toolbar_logic.test.ts @@ -44,7 +44,6 @@ describe('AnalyticsCollectionToolbarLogic', () => { }); const defaultProps: AnalyticsCollectionToolbarLogicValues = { _searchSessionId: null, - dataViewId: null, refreshInterval: { pause: true, value: 10000 }, searchSessionId: undefined, timeRange: { from: 'now-7d', to: 'now' }, @@ -62,11 +61,6 @@ describe('AnalyticsCollectionToolbarLogic', () => { ); }); - it('sets dataViewId', () => { - AnalyticsCollectionToolbarLogic.actions.setDataViewId('sample_data_view_id'); - expect(AnalyticsCollectionToolbarLogic.values.dataViewId).toEqual('sample_data_view_id'); - }); - it('sets refreshInterval', () => { const refreshInterval: RefreshInterval = { pause: false, value: 5000 }; AnalyticsCollectionToolbarLogic.actions.setRefreshInterval(refreshInterval); @@ -81,14 +75,6 @@ describe('AnalyticsCollectionToolbarLogic', () => { }); describe('listeners', () => { - it('should set dataViewId when findDataViewId called', async () => { - await AnalyticsCollectionToolbarLogic.actions.findDataViewId({ - events_datastream: 'some-collection', - name: 'some-collection-name', - }); - expect(AnalyticsCollectionToolbarLogic.values.dataViewId).toBe('some-data-view-id'); - }); - it('should set searchSessionId when onTimeRefresh called', () => { jest.spyOn(AnalyticsCollectionToolbarLogic.actions, 'setSearchSessionId'); diff --git a/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/analytics_collection_toolbar/analytics_collection_toolbar_logic.ts b/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/analytics_collection_toolbar/analytics_collection_toolbar_logic.ts index 23e97a022fed3..412e3c502ab44 100644 --- a/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/analytics_collection_toolbar/analytics_collection_toolbar_logic.ts +++ b/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/analytics_collection_toolbar/analytics_collection_toolbar_logic.ts @@ -11,11 +11,9 @@ import { RefreshInterval } from '@kbn/data-plugin/common'; import { TimeRange } from '@kbn/es-query'; -import { AnalyticsCollection } from '../../../../../../common/types/analytics'; import { KibanaLogic } from '../../../../shared/kibana/kibana_logic'; export interface AnalyticsCollectionToolbarLogicActions { - findDataViewId(collection: AnalyticsCollection): { collection: AnalyticsCollection }; onTimeRefresh(): void; setDataViewId(id: string): { id: string }; setRefreshInterval(refreshInterval: RefreshInterval): RefreshInterval; @@ -27,7 +25,6 @@ export interface AnalyticsCollectionToolbarLogicActions { export interface AnalyticsCollectionToolbarLogicValues { // kea forbid to set undefined as a value _searchSessionId: string | null; - dataViewId: string | null; refreshInterval: RefreshInterval; searchSessionId: string | undefined; timeRange: TimeRange; @@ -40,23 +37,12 @@ export const AnalyticsCollectionToolbarLogic = kea< MakeLogicType >({ actions: { - findDataViewId: (collection) => ({ collection }), onTimeRefresh: true, - setDataViewId: (id) => ({ id }), setRefreshInterval: ({ pause, value }) => ({ pause, value }), setSearchSessionId: (searchSessionId) => ({ searchSessionId }), setTimeRange: ({ from, to }) => ({ from, to }), }, listeners: ({ actions }) => ({ - findDataViewId: async ({ collection }) => { - const dataViewId = ( - await KibanaLogic.values.data.dataViews.find(collection.events_datastream, 1) - )?.[0]?.id; - - if (dataViewId) { - actions.setDataViewId(dataViewId); - } - }, onTimeRefresh() { actions.setSearchSessionId(KibanaLogic.values.data.search.session.start()); }, @@ -75,7 +61,6 @@ export const AnalyticsCollectionToolbarLogic = kea< null, { setSearchSessionId: (state, { searchSessionId }) => searchSessionId }, ], - dataViewId: [null, { setDataViewId: (_, { id }) => id }], refreshInterval: [ DEFAULT_REFRESH_INTERVAL, { diff --git a/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/analytics_collection_view.tsx b/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/analytics_collection_view.tsx index 0db1d77ba6b49..b51dfa0c1f5bd 100644 --- a/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/analytics_collection_view.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/analytics_collection_view.tsx @@ -25,6 +25,10 @@ import { AddAnalyticsCollection } from '../add_analytics_collections/add_analyti import { EnterpriseSearchAnalyticsPageTemplate } from '../layout/page_template'; +import { AnalyticsCollectionDataViewLogic } from './analytics_collection_data_view_logic'; + +import { AnalyticsCollectionExplorer } from './analytics_collection_explorer/analytics_collection_explorer'; + import { AnalyticsCollectionIntegrateView } from './analytics_collection_integrate/analytics_collection_integrate_view'; import { AnalyticsCollectionOverview } from './analytics_collection_overview/analytics_collection_overview'; import { AnalyticsCollectionToolbarLogic } from './analytics_collection_toolbar/analytics_collection_toolbar_logic'; @@ -33,6 +37,7 @@ import { FetchAnalyticsCollectionLogic } from './fetch_analytics_collection_logi export const AnalyticsCollectionView: React.FC = () => { useMountedLogic(AnalyticsCollectionToolbarLogic); + useMountedLogic(AnalyticsCollectionDataViewLogic); const { fetchAnalyticsCollection } = useActions(FetchAnalyticsCollectionLogic); const { analyticsCollection, isLoading } = useValues(FetchAnalyticsCollectionLogic); const { name } = useParams<{ name: string }>(); @@ -52,7 +57,9 @@ export const AnalyticsCollectionView: React.FC = () => { - + + + ); } diff --git a/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/fetch_analytics_collection_logic.ts b/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/fetch_analytics_collection_logic.ts index 19d54b5b9dad7..3d96d20f74e76 100644 --- a/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/fetch_analytics_collection_logic.ts +++ b/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/fetch_analytics_collection_logic.ts @@ -16,6 +16,7 @@ import { } from '../../api/fetch_analytics_collection/fetch_analytics_collection_api_logic'; export interface FetchAnalyticsCollectionActions { + apiSuccess: Actions<{}, FetchAnalyticsCollectionApiLogicResponse>['apiSuccess']; fetchAnalyticsCollection(name: string): AnalyticsCollection; makeRequest: Actions<{}, FetchAnalyticsCollectionApiLogicResponse>['makeRequest']; } @@ -33,7 +34,7 @@ export const FetchAnalyticsCollectionLogic = kea< fetchAnalyticsCollection: (name) => ({ name }), }, connect: { - actions: [FetchAnalyticsCollectionAPILogic, ['makeRequest']], + actions: [FetchAnalyticsCollectionAPILogic, ['makeRequest', 'apiSuccess']], values: [FetchAnalyticsCollectionAPILogic, ['data', 'status']], }, listeners: ({ actions }) => ({ diff --git a/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/use_discover_link.ts b/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/use_discover_link.ts new file mode 100644 index 0000000000000..825cf1d1c3d15 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/use_discover_link.ts @@ -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 { useValues } from 'kea'; + +import { KibanaLogic } from '../../../shared/kibana'; + +import { AnalyticsCollectionDataViewLogic } from './analytics_collection_data_view_logic'; +import { AnalyticsCollectionToolbarLogic } from './analytics_collection_toolbar/analytics_collection_toolbar_logic'; + +export const useDiscoverLink = (): string | null => { + const { application } = useValues(KibanaLogic); + const { dataView } = useValues(AnalyticsCollectionDataViewLogic); + const { refreshInterval, timeRange } = useValues(AnalyticsCollectionToolbarLogic); + + return dataView + ? application.getUrlForApp('discover', { + path: `#/?_a=(index:'${ + dataView.id + }')&_g=(filters:!(),refreshInterval:(pause:!${refreshInterval.pause + .toString() + .charAt(0)},value:${refreshInterval.value}),time:(from:${timeRange.from},to:${ + timeRange.to + }))`, + }) + : null; +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_overview/analytics_collection_card/analytics_collection_card.tsx b/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_overview/analytics_collection_card/analytics_collection_card.tsx index 031212489213b..c3022d2ea11ef 100644 --- a/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_overview/analytics_collection_card/analytics_collection_card.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_overview/analytics_collection_card/analytics_collection_card.tsx @@ -93,7 +93,6 @@ const getChartStatus = (metric: number | null): ChartStatus => { if (metric && metric < 0) return ChartStatus.DECREASE; return ChartStatus.CONSTANT; }; - export const AnalyticsCollectionCard: React.FC< AnalyticsCollectionCardProps & AnalyticsCollectionCardLensProps > = ({ collection, isLoading, isCreatedByEngine, subtitle, data, metric, secondaryMetric }) => { @@ -332,6 +331,5 @@ export const AnalyticsCollectionCardWithLens = withLensData< visualizationType: 'lnsMetric', }; }, - getDataViewQuery: ({ collection }) => collection.events_datastream, initialValues, }); diff --git a/x-pack/plugins/enterprise_search/public/applications/analytics/hoc/with_lens_data.test.tsx b/x-pack/plugins/enterprise_search/public/applications/analytics/hoc/with_lens_data.test.tsx index 03d09f145a4bb..f4883c5e9b4ca 100644 --- a/x-pack/plugins/enterprise_search/public/applications/analytics/hoc/with_lens_data.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/analytics/hoc/with_lens_data.test.tsx @@ -13,10 +13,10 @@ import { mount, shallow } from 'enzyme'; import { act } from 'react-dom/test-utils'; -import { DataView } from '@kbn/data-views-plugin/common'; - import { FormulaPublicApi } from '@kbn/lens-plugin/public'; +import { findOrCreateDataView } from '../utils/find_or_create_data_view'; + import { withLensData } from './with_lens_data'; interface MockComponentProps { @@ -29,6 +29,20 @@ interface MockComponentLensProps { const flushPromises = () => new Promise((resolve) => setImmediate(resolve)); +const mockCollection = { + event_retention_day_length: 180, + events_datastream: 'analytics-events-example2', + id: 'example2', + name: 'example2', +}; +const mockDataView = { id: 'test-data-view-id' }; + +jest.mock('../utils/find_or_create_data_view', () => { + return { + findOrCreateDataView: jest.fn(), + }; +}); + describe('withLensData', () => { const MockComponent: React.FC = ({ name }) =>
{name}
; @@ -48,85 +62,91 @@ describe('withLensData', () => { return { data: 'initial data' }; }), getAttributes: jest.fn(), - getDataViewQuery: jest.fn(), initialValues: { data: 'initial data' }, } ); - const props = { name: 'John Doe' }; + const props = { collection: mockCollection, name: 'John Doe' }; const wrapper = shallow( ); expect(wrapper.find(MockComponent).prop('data')).toEqual('initial data'); }); - it('should call getDataViewQuery with props', async () => { - const getDataViewQuery = jest.fn(); - getDataViewQuery.mockReturnValue('title-collection'); - const findMock = jest.spyOn(mockKibanaValues.data.dataViews, 'find').mockResolvedValueOnce([]); + it('should call findOrCreateDataView with collection', async () => { const WrappedComponent = withLensData( MockComponent, { dataLoadTransform: jest.fn(), getAttributes: jest.fn(), - getDataViewQuery, initialValues: { data: 'initial data' }, } ); - const props = { id: 'id', name: 'John Doe', timeRange: { from: 'now-10d', to: 'now' } }; + const props = { + collection: mockCollection, + id: 'id', + name: 'John Doe', + timeRange: { from: 'now-10d', to: 'now' }, + }; mount(); await act(async () => { await flushPromises(); }); - expect(getDataViewQuery).toHaveBeenCalledWith(props); - expect(findMock).toHaveBeenCalledWith('title-collection', 1); + expect(findOrCreateDataView).toHaveBeenCalledWith(mockCollection); }); it('should call getAttributes with the correct arguments when dataView and formula are available', async () => { const getAttributes = jest.fn(); - const dataView = {} as DataView; const formula = {} as FormulaPublicApi; mockKibanaValues.lens.stateHelperApi = jest.fn().mockResolvedValueOnce({ formula }); - jest.spyOn(mockKibanaValues.data.dataViews, 'find').mockResolvedValueOnce([dataView]); + (findOrCreateDataView as jest.Mock).mockResolvedValueOnce(mockDataView); const WrappedComponent = withLensData( MockComponent, { dataLoadTransform: jest.fn(), getAttributes, - getDataViewQuery: jest.fn(), initialValues: { data: 'initial data' }, } ); - const props = { id: 'id', name: 'John Doe', timeRange: { from: 'now-10d', to: 'now' } }; + const props = { + collection: mockCollection, + id: 'id', + name: 'John Doe', + timeRange: { from: 'now-10d', to: 'now' }, + }; mount(); await act(async () => { await flushPromises(); }); - expect(getAttributes).toHaveBeenCalledWith(dataView, formula, props); + expect(getAttributes).toHaveBeenCalledWith(mockDataView, formula, props); }); it('should not call getAttributes when dataView is not available', async () => { const getAttributes = jest.fn(); const formula = {} as FormulaPublicApi; mockKibanaValues.lens.stateHelperApi = jest.fn().mockResolvedValueOnce({ formula }); - jest.spyOn(mockKibanaValues.data.dataViews, 'find').mockResolvedValueOnce([]); + (findOrCreateDataView as jest.Mock).mockResolvedValueOnce(undefined); const WrappedComponent = withLensData( MockComponent, { dataLoadTransform: jest.fn(), getAttributes, - getDataViewQuery: jest.fn(), initialValues: { data: 'initial data' }, } ); - const props = { id: 'id', name: 'John Doe', timeRange: { from: 'now-10d', to: 'now' } }; + const props = { + collection: mockCollection, + id: 'id', + name: 'John Doe', + timeRange: { from: 'now-10d', to: 'now' }, + }; mount(); await act(async () => { await flushPromises(); diff --git a/x-pack/plugins/enterprise_search/public/applications/analytics/hoc/with_lens_data.tsx b/x-pack/plugins/enterprise_search/public/applications/analytics/hoc/with_lens_data.tsx index 7e9ecbbb5ac8c..64df74a011c88 100644 --- a/x-pack/plugins/enterprise_search/public/applications/analytics/hoc/with_lens_data.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/analytics/hoc/with_lens_data.tsx @@ -17,9 +17,13 @@ import { TimeRange } from '@kbn/es-query'; import { DefaultInspectorAdapters } from '@kbn/expressions-plugin/common'; import { FormulaPublicApi, TypedLensByValueInput } from '@kbn/lens-plugin/public'; +import { AnalyticsCollection } from '../../../../common/types/analytics'; + import { KibanaLogic } from '../../shared/kibana'; +import { findOrCreateDataView } from '../utils/find_or_create_data_view'; export interface WithLensDataInputProps { + collection: AnalyticsCollection; id: string; searchSessionId?: string; setTimeRange?(timeRange: TimeRange): void; @@ -36,7 +40,6 @@ interface WithLensDataParams { formulaApi: FormulaPublicApi, props: Props ) => TypedLensByValueInput['attributes']; - getDataViewQuery: (props: Props) => string; initialValues: OutputState; } @@ -45,14 +48,12 @@ export const withLensData = ( { dataLoadTransform, getAttributes, - getDataViewQuery, initialValues, }: WithLensDataParams, OutputState> ) => { const ComponentWithLensData: React.FC = (props) => { const { lens: { EmbeddableComponent, stateHelperApi }, - data: { dataViews }, } = useValues(KibanaLogic); const [dataView, setDataView] = useState(null); const [data, setData] = useState(initialValues); @@ -73,11 +74,7 @@ export const withLensData = ( useEffect(() => { (async () => { - const [target] = await dataViews.find(getDataViewQuery(props), 1); - - if (target) { - setDataView(target); - } + setDataView(await findOrCreateDataView(props.collection)); })(); }, [props]); useEffect(() => { diff --git a/x-pack/plugins/enterprise_search/public/applications/analytics/utils/find_or_create_data_view.test.ts b/x-pack/plugins/enterprise_search/public/applications/analytics/utils/find_or_create_data_view.test.ts new file mode 100644 index 0000000000000..a9362b622176c --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/analytics/utils/find_or_create_data_view.test.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 { DataView } from '@kbn/data-views-plugin/common'; + +import { AnalyticsCollection } from '../../../../common/types/analytics'; +import { KibanaLogic } from '../../shared/kibana/kibana_logic'; + +import { findOrCreateDataView } from './find_or_create_data_view'; + +jest.mock('../../shared/kibana/kibana_logic', () => ({ + KibanaLogic: { + values: { + data: { + dataViews: { + createAndSave: jest.fn(), + find: jest.fn(() => Promise.resolve([])), + }, + }, + }, + }, +})); + +describe('findOrCreateDataView', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('should find and set dataView when analytics collection fetched', async () => { + const dataView = { id: 'test', title: 'events1' } as DataView; + jest.spyOn(KibanaLogic.values.data.dataViews, 'find').mockResolvedValueOnce([dataView]); + + expect( + await findOrCreateDataView({ + events_datastream: 'events1', + name: 'collection1', + } as AnalyticsCollection) + ).toEqual(dataView); + expect(KibanaLogic.values.data.dataViews.createAndSave).not.toHaveBeenCalled(); + }); + + it('should create, save and set dataView when analytics collection fetched but dataView is not found', async () => { + const dataView = { id: 'test21' } as DataView; + jest.spyOn(KibanaLogic.values.data.dataViews, 'createAndSave').mockResolvedValueOnce(dataView); + + expect( + await findOrCreateDataView({ + events_datastream: 'events1', + name: 'collection1', + } as AnalyticsCollection) + ).toEqual(dataView); + expect(KibanaLogic.values.data.dataViews.createAndSave).toHaveBeenCalled(); + }); +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/analytics/utils/find_or_create_data_view.ts b/x-pack/plugins/enterprise_search/public/applications/analytics/utils/find_or_create_data_view.ts new file mode 100644 index 0000000000000..52ead6c0dd247 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/analytics/utils/find_or_create_data_view.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 { AnalyticsCollection } from '../../../../common/types/analytics'; +import { KibanaLogic } from '../../shared/kibana/kibana_logic'; + +export const findOrCreateDataView = async (collection: AnalyticsCollection) => { + const dataView = ( + await KibanaLogic.values.data.dataViews.find(collection.events_datastream, 1) + ).find((result) => result.title === collection.events_datastream); + + if (dataView) { + return dataView; + } + + return await KibanaLogic.values.data.dataViews.createAndSave( + { + allowNoIndex: true, + name: `behavioral_analytics.events-${collection.name}`, + timeFieldName: '@timestamp', + title: collection.events_datastream, + }, + true + ); +}; diff --git a/x-pack/plugins/enterprise_search/tsconfig.json b/x-pack/plugins/enterprise_search/tsconfig.json index af660c1169d5a..4ba8f2b48d814 100644 --- a/x-pack/plugins/enterprise_search/tsconfig.json +++ b/x-pack/plugins/enterprise_search/tsconfig.json @@ -58,5 +58,6 @@ "@kbn/react-field", "@kbn/field-types", "@kbn/core-elasticsearch-server-mocks", + "@kbn/shared-ux-link-redirect-app", ] } From cac928b95622a6d08b79f54deeea52f920fd11e2 Mon Sep 17 00:00:00 2001 From: Julia Rechkunova Date: Wed, 19 Apr 2023 19:17:54 +0200 Subject: [PATCH 59/78] [Discover] Drag & drop for adding columns to the table (#153538) Closes https://github.com/elastic/kibana/issues/151703 ## Summary This PR: - changes design of field list item in Lens and Discover - changes design of dimension triggers in Lens panels - changes design "Add layers" button - adds "+" action to field items in Lens - unifies the logic of rendering Lens field items for text-based and form-based views - adds Drag&Drop functionality to Discover to add columns to the table by dragging a field from the sidebar - adds functional tests for drag&drop in Discover - shows fields popover in text-based mode too so users can copy field names [Figma](https://www.figma.com/file/SvpfCqaZPb2iAYnPtd0Gnr/KUI-Library?node-id=674%3A198901&t=OnQH2EQ4fdBjsRLp-0)
Gifs ![Apr-04-2023 14-38-47](https://user-images.githubusercontent.com/1415710/229795117-712267ba-f5e0-42ca-a2e5-e23759d5ddda.gif) ![Apr-04-2023 14-40-59](https://user-images.githubusercontent.com/1415710/229795133-7b618566-e73a-4303-97d7-b2840d1fc006.gif)
### 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] 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> Co-authored-by: Andrea Del Rio Co-authored-by: Stratoula Kalafateli --- packages/kbn-dom-drag-drop/index.ts | 2 + packages/kbn-dom-drag-drop/src/constants.ts | 2 + .../kbn-dom-drag-drop/src/drag_drop.test.tsx | 10 +- packages/kbn-dom-drag-drop/src/drag_drop.tsx | 21 +- .../src/drop_overlay_wrapper.tsx | 52 +++ packages/kbn-dom-drag-drop/src/index.ts | 1 + .../src/providers/reorder_provider.tsx | 9 +- .../kbn-dom-drag-drop/src/sass/drag_drop.scss | 58 +++- .../src/sass/drag_drop_mixins.scss | 15 +- .../__snapshots__/field_button.test.tsx.snap | 69 +++- .../src/field_button/field_button.scss | 58 ++-- .../src/field_button/field_button.test.tsx | 19 +- .../src/field_button/field_button.tsx | 39 ++- .../components/layout/discover_layout.tsx | 17 +- .../layout/discover_main_content.tsx | 114 ++++--- .../sidebar/discover_field.test.tsx | 14 +- .../components/sidebar/discover_field.tsx | 316 +++++++----------- .../components/sidebar/discover_sidebar.scss | 26 -- .../components/sidebar/discover_sidebar.tsx | 7 +- .../application/main/discover_main_app.tsx | 29 +- src/plugins/discover/tsconfig.json | 1 + .../field_item_button.test.tsx.snap | 278 +++++++++++++++ .../field_item_button/field_item_button.scss | 35 +- .../field_item_button.test.tsx | 150 +++++++++ .../field_item_button/field_item_button.tsx | 165 ++++++++- .../unified_field_list/public/index.ts | 1 + src/plugins/unified_field_list/tsconfig.json | 1 + .../apps/discover/group3/_drag_drop.ts | 71 ++++ test/functional/apps/discover/group3/index.ts | 1 + test/functional/page_objects/discover_page.ts | 49 ++- .../public/datasources/common/field_item.scss | 4 + .../field_item.test.tsx | 31 +- .../{form_based => common}/field_item.tsx | 198 ++++++----- .../common/get_field_item_actions.tsx | 56 ++++ .../datasources/form_based/datapanel.test.tsx | 23 +- .../datasources/form_based/datapanel.tsx | 12 +- .../datasources/form_based/field_item.scss | 26 -- .../datasources/text_based/datapanel.test.tsx | 13 +- .../datasources/text_based/datapanel.tsx | 43 ++- .../text_based/text_based_languages.tsx | 18 +- .../editor_frame/config_panel/add_layer.tsx | 6 +- .../config_panel/buttons/dimension_button.tsx | 58 ++-- .../buttons/empty_dimension_button.tsx | 2 +- .../config_panel/layer_panel.scss | 46 ++- .../config_panel/layer_panel.test.tsx | 5 +- .../editor_frame/config_panel/layer_panel.tsx | 79 +++-- .../dimension_trigger/index.tsx | 16 +- .../translations/translations/fr-FR.json | 8 - .../translations/translations/ja-JP.json | 8 - .../translations/translations/zh-CN.json | 8 - .../apps/lens/group1/smokescreen.ts | 6 +- .../test/functional/page_objects/lens_page.ts | 10 +- .../functional/services/transform/wizard.ts | 4 +- .../localization/tests/lens/smokescreen.ts | 6 +- 54 files changed, 1598 insertions(+), 718 deletions(-) create mode 100644 packages/kbn-dom-drag-drop/src/drop_overlay_wrapper.tsx create mode 100644 src/plugins/unified_field_list/public/components/field_item_button/__snapshots__/field_item_button.test.tsx.snap create mode 100644 src/plugins/unified_field_list/public/components/field_item_button/field_item_button.test.tsx create mode 100644 test/functional/apps/discover/group3/_drag_drop.ts create mode 100644 x-pack/plugins/lens/public/datasources/common/field_item.scss rename x-pack/plugins/lens/public/datasources/{form_based => common}/field_item.test.tsx (94%) rename x-pack/plugins/lens/public/datasources/{form_based => common}/field_item.tsx (72%) create mode 100644 x-pack/plugins/lens/public/datasources/common/get_field_item_actions.tsx delete mode 100644 x-pack/plugins/lens/public/datasources/form_based/field_item.scss diff --git a/packages/kbn-dom-drag-drop/index.ts b/packages/kbn-dom-drag-drop/index.ts index 618613deca73e..7a7f599934890 100644 --- a/packages/kbn-dom-drag-drop/index.ts +++ b/packages/kbn-dom-drag-drop/index.ts @@ -16,6 +16,8 @@ export { RootDragDropProvider, ChildDragDropProvider, ReorderProvider, + DropOverlayWrapper, + type DropOverlayWrapperProps, } from './src'; export { DropTargetSwapDuplicateCombine } from './src/drop_targets'; diff --git a/packages/kbn-dom-drag-drop/src/constants.ts b/packages/kbn-dom-drag-drop/src/constants.ts index 2cc21a99c7013..e6b211c314048 100644 --- a/packages/kbn-dom-drag-drop/src/constants.ts +++ b/packages/kbn-dom-drag-drop/src/constants.ts @@ -7,3 +7,5 @@ */ export const DEFAULT_DATA_TEST_SUBJ = 'domDragDrop'; +export const REORDER_ITEM_HEIGHT = 32; +export const REORDER_ITEM_MARGIN = 8; diff --git a/packages/kbn-dom-drag-drop/src/drag_drop.test.tsx b/packages/kbn-dom-drag-drop/src/drag_drop.test.tsx index 498d6a0fa8664..fac4b76f2a33d 100644 --- a/packages/kbn-dom-drag-drop/src/drag_drop.test.tsx +++ b/packages/kbn-dom-drag-drop/src/drag_drop.test.tsx @@ -468,7 +468,7 @@ describe('DragDrop', () => { ghost: { children: , style: { - height: 0, + minHeight: 0, width: 0, }, }, @@ -1100,12 +1100,12 @@ describe('DragDrop', () => { expect( component.find('[data-test-subj="testDragDrop-translatableDrop"]').at(0).prop('style') ).toEqual({ - transform: 'translateY(-8px)', + transform: 'translateY(-4px)', }); expect( component.find('[data-test-subj="testDragDrop-translatableDrop"]').at(1).prop('style') ).toEqual({ - transform: 'translateY(-8px)', + transform: 'translateY(-4px)', }); component @@ -1258,12 +1258,12 @@ describe('DragDrop', () => { expect( component.find('[data-test-subj="testDragDrop-reorderableDrag"]').at(0).prop('style') ).toEqual({ - transform: 'translateY(+8px)', + transform: 'translateY(+4px)', }); expect( component.find('[data-test-subj="testDragDrop-translatableDrop"]').at(0).prop('style') ).toEqual({ - transform: 'translateY(-40px)', + transform: 'translateY(-32px)', }); expect( component.find('[data-test-subj="testDragDrop-translatableDrop"]').at(1).prop('style') diff --git a/packages/kbn-dom-drag-drop/src/drag_drop.tsx b/packages/kbn-dom-drag-drop/src/drag_drop.tsx index 47909a818cea9..ce3d6147d813f 100644 --- a/packages/kbn-dom-drag-drop/src/drag_drop.tsx +++ b/packages/kbn-dom-drag-drop/src/drag_drop.tsx @@ -24,6 +24,7 @@ import { Ghost, } from './providers'; import { DropType } from './types'; +import { REORDER_ITEM_MARGIN } from './constants'; import './sass/drag_drop.scss'; /** @@ -71,11 +72,15 @@ interface BaseProps { * The React element which will be passed the draggable handlers */ children: ReactElement; + + /** + * Disable any drag & drop behaviour + */ + isDisabled?: boolean; /** * Indicates whether or not this component is draggable. */ draggable?: boolean; - /** * Additional class names to apply when another element is over the drop target */ @@ -152,7 +157,7 @@ interface DropsInnerProps extends BaseProps { isNotDroppable: boolean; } -const lnsLayerPanelDimensionMargin = 8; +const REORDER_OFFSET = REORDER_ITEM_MARGIN / 2; /** * DragDrop component @@ -174,6 +179,10 @@ export const DragDrop = (props: BaseProps) => { onTrackUICounterEvent, } = useContext(DragContext); + if (props.isDisabled) { + return props.children; + } + const { value, draggable, dropTypes, reorderableGroup } = props; const isDragging = !!(draggable && value.id === dragging?.id); @@ -358,7 +367,7 @@ const DragInner = memo(function DragInner({ ghost: keyboardModeOn ? { children, - style: { width: currentTarget.offsetWidth, height: currentTarget.offsetHeight }, + style: { width: currentTarget.offsetWidth, minHeight: currentTarget?.offsetHeight }, } : undefined, }); @@ -417,12 +426,14 @@ const DragInner = memo(function DragInner({ keyboardMode && activeDropTarget && activeDropTarget.dropType !== 'reorder'; + return (
@@ -772,7 +783,7 @@ const ReorderableDrag = memo(function ReorderableDrag( | KeyboardEvent['currentTarget'] ) => { if (currentTarget) { - const height = currentTarget.offsetHeight + lnsLayerPanelDimensionMargin; + const height = currentTarget.offsetHeight + REORDER_OFFSET; setReorderState((s: ReorderState) => ({ ...s, draggingHeight: height, @@ -875,7 +886,7 @@ const ReorderableDrag = memo(function ReorderableDrag( areItemsReordered ? { transform: `translateY(${direction === '+' ? '-' : '+'}${reorderedItems.reduce( - (acc, cur) => acc + Number(cur.height || 0) + lnsLayerPanelDimensionMargin, + (acc, cur) => acc + Number(cur.height || 0) + REORDER_OFFSET, 0 )}px)`, } diff --git a/packages/kbn-dom-drag-drop/src/drop_overlay_wrapper.tsx b/packages/kbn-dom-drag-drop/src/drop_overlay_wrapper.tsx new file mode 100644 index 0000000000000..590106157f304 --- /dev/null +++ b/packages/kbn-dom-drag-drop/src/drop_overlay_wrapper.tsx @@ -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 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 classnames from 'classnames'; + +/** + * DropOverlayWrapper Props + */ +export interface DropOverlayWrapperProps { + isVisible: boolean; + className?: string; + overlayProps?: object; +} + +/** + * This prevents the in-place droppable styles (under children) and allows to rather show an overlay with droppable styles (on top of children) + * @param isVisible + * @param children + * @param overlayProps + * @param className + * @param otherProps + * @constructor + */ +export const DropOverlayWrapper: React.FC = ({ + isVisible, + children, + overlayProps, + className, + ...otherProps +}) => { + return ( +
+ {children} + {isVisible && ( +
+ )} +
+ ); +}; diff --git a/packages/kbn-dom-drag-drop/src/index.ts b/packages/kbn-dom-drag-drop/src/index.ts index 340dba349612c..9fa4f37103522 100644 --- a/packages/kbn-dom-drag-drop/src/index.ts +++ b/packages/kbn-dom-drag-drop/src/index.ts @@ -9,3 +9,4 @@ export * from './types'; export * from './providers'; export * from './drag_drop'; +export { DropOverlayWrapper, type DropOverlayWrapperProps } from './drop_overlay_wrapper'; diff --git a/packages/kbn-dom-drag-drop/src/providers/reorder_provider.tsx b/packages/kbn-dom-drag-drop/src/providers/reorder_provider.tsx index 5dc83dbce915f..c2aa11eb8bc8d 100644 --- a/packages/kbn-dom-drag-drop/src/providers/reorder_provider.tsx +++ b/packages/kbn-dom-drag-drop/src/providers/reorder_provider.tsx @@ -8,7 +8,7 @@ import React, { useState, useMemo } from 'react'; import classNames from 'classnames'; -import { DEFAULT_DATA_TEST_SUBJ } from '../constants'; +import { DEFAULT_DATA_TEST_SUBJ, REORDER_ITEM_HEIGHT } from '../constants'; /** * Reorder state @@ -54,7 +54,7 @@ export const ReorderContext = React.createContext({ reorderState: { reorderedItems: [], direction: '-', - draggingHeight: 40, + draggingHeight: REORDER_ITEM_HEIGHT, isReorderOn: false, groupId: '', }, @@ -66,6 +66,7 @@ export const ReorderContext = React.createContext({ * @param id * @param children * @param className + * @param draggingHeight * @param dataTestSubj * @constructor */ @@ -73,17 +74,19 @@ export function ReorderProvider({ id, children, className, + draggingHeight = REORDER_ITEM_HEIGHT, dataTestSubj = DEFAULT_DATA_TEST_SUBJ, }: { id: string; children: React.ReactNode; className?: string; + draggingHeight?: number; dataTestSubj?: string; }) { const [state, setState] = useState({ reorderedItems: [], direction: '-', - draggingHeight: 40, + draggingHeight, isReorderOn: false, groupId: id, }); diff --git a/packages/kbn-dom-drag-drop/src/sass/drag_drop.scss b/packages/kbn-dom-drag-drop/src/sass/drag_drop.scss index d5587ae8ec6a4..56ce648266e7b 100644 --- a/packages/kbn-dom-drag-drop/src/sass/drag_drop.scss +++ b/packages/kbn-dom-drag-drop/src/sass/drag_drop.scss @@ -1,7 +1,6 @@ @import './drag_drop_mixins'; .domDragDrop { - user-select: none; transition: $euiAnimSpeedFast ease-in-out; transition-property: background-color, border-color, opacity; z-index: $domDragDropZLevel1; @@ -12,11 +11,11 @@ border: $euiBorderWidthThin dashed $euiBorderColor; position: absolute !important; // sass-lint:disable-line no-important margin: 0 !important; // sass-lint:disable-line no-important - bottom: 100%; + top: 0; width: 100%; left: 0; opacity: .9; - transform: translate(-12px, 8px); + transform: translate($euiSizeXS, $euiSizeXS); z-index: $domDragDropZLevel3; pointer-events: none; outline: $euiFocusRingSize solid currentColor; // Safari & Firefox @@ -29,7 +28,8 @@ @include mixinDomDragDropHover; // Include a possible nested button like when using FieldButton - > .kbnFieldButton__button { + & .kbnFieldButton__button, + & .euiLink { cursor: grab; } @@ -39,14 +39,17 @@ } // Drop area -.domDragDrop-isDroppable { +.domDragDrop-isDroppable:not(.domDragDrop__dropOverlayWrapper) { @include mixinDomDroppable; } // Drop area when there's an item being dragged .domDragDrop-isDropTarget { - @include mixinDomDroppable; - @include mixinDomDroppableActive; + &:not(.domDragDrop__dropOverlayWrapper) { + @include mixinDomDroppable; + @include mixinDomDroppableActive; + } + > * { pointer-events: none; } @@ -57,7 +60,7 @@ } // Drop area while hovering with item -.domDragDrop-isActiveDropTarget { +.domDragDrop-isActiveDropTarget:not(.domDragDrop__dropOverlayWrapper) { z-index: $domDragDropZLevel3; @include mixinDomDroppableActiveHover; } @@ -72,9 +75,9 @@ text-decoration: line-through; } -.domDragDrop-notCompatible { +.domDragDrop-notCompatible:not(.domDragDrop__dropOverlayWrapper) { background-color: $euiColorHighlight !important; - border: $euiBorderWidthThin dashed $euiBorderColor !important; + border: $euiBorderWidthThin dashed $euiColorVis5 !important; &.domDragDrop-isActiveDropTarget { background-color: rgba(251, 208, 17, .25) !important; border-color: $euiColorVis5 !important; @@ -91,12 +94,12 @@ } } -$lnsLayerPanelDimensionMargin: 8px; +$reorderItemMargin: $euiSizeS; .domDragDrop__reorderableDrop { position: absolute; width: 100%; top: 0; - height: calc(100% + #{$lnsLayerPanelDimensionMargin}); + height: calc(100% + #{$reorderItemMargin / 2}); } .domDragDrop-translatableDrop { @@ -146,6 +149,10 @@ $lnsLayerPanelDimensionMargin: 8px; } } +.domDragDrop--isDragStarted { + opacity: .5; +} + .domDragDrop__extraDrops { opacity: 0; visibility: hidden; @@ -193,7 +200,7 @@ $lnsLayerPanelDimensionMargin: 8px; .domDragDrop__extraDrop { position: relative; - height: $euiSizeXS * 10; + height: $euiSizeXS * 8; min-width: $euiSize * 7; color: $euiColorSuccessText; padding: $euiSizeXS; @@ -201,3 +208,28 @@ $lnsLayerPanelDimensionMargin: 8px; color: $euiColorWarningText; } } + +.domDragDrop__dropOverlayWrapper { + position: relative; + height: 100%; +} + +.domDragDrop__dropOverlay { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + z-index: $domDragDropZLevel3; + transition: $euiAnimSpeedFast ease-in-out; + transition-property: background-color, border-color, opacity; + + .domDragDrop-isDropTarget & { + @include mixinDomDroppable($euiBorderWidthThick); + @include mixinDomDroppableActive($euiBorderWidthThick); + } + + .domDragDrop-isActiveDropTarget & { + @include mixinDomDroppableActiveHover($euiBorderWidthThick); + } +} diff --git a/packages/kbn-dom-drag-drop/src/sass/drag_drop_mixins.scss b/packages/kbn-dom-drag-drop/src/sass/drag_drop_mixins.scss index fbd460a67f7d0..fb495b4fcfba4 100644 --- a/packages/kbn-dom-drag-drop/src/sass/drag_drop_mixins.scss +++ b/packages/kbn-dom-drag-drop/src/sass/drag_drop_mixins.scss @@ -13,31 +13,32 @@ $domDragDropZLevel3: 3; @mixin mixinDomDraggable { @include euiSlightShadow; background: $euiColorEmptyShade; - border: $euiBorderWidthThin dashed transparent; cursor: grab; } // Static styles for a drop area -@mixin mixinDomDroppable { - border: $euiBorderWidthThin dashed $euiBorderColor !important; +@mixin mixinDomDroppable($borderWidth: $euiBorderWidthThin) { + border: $borderWidth dashed transparent; } // Hovering state for drag item and drop area @mixin mixinDomDragDropHover { &:hover { - border: $euiBorderWidthThin dashed $euiColorMediumShade !important; + transform: translateX($euiSizeXS); + transition: transform $euiAnimSpeedSlow ease-out; } } // Style for drop area when there's an item being dragged -@mixin mixinDomDroppableActive { +@mixin mixinDomDroppableActive($borderWidth: $euiBorderWidthThin) { background-color: transparentize($euiColorVis0, .9) !important; + border: $borderWidth dashed $euiColorVis0 !important; } // Style for drop area while hovering with item -@mixin mixinDomDroppableActiveHover { +@mixin mixinDomDroppableActiveHover($borderWidth: $euiBorderWidthThin) { background-color: transparentize($euiColorVis0, .75) !important; - border: $euiBorderWidthThin dashed $euiColorVis0 !important; + border: $borderWidth dashed $euiColorVis0 !important; } // Style for drop area that is not allowed for current item diff --git a/packages/kbn-react-field/src/field_button/__snapshots__/field_button.test.tsx.snap b/packages/kbn-react-field/src/field_button/__snapshots__/field_button.test.tsx.snap index e65b5fcb8fbbd..0b4ceaf06e863 100644 --- a/packages/kbn-react-field/src/field_button/__snapshots__/field_button.test.tsx.snap +++ b/packages/kbn-react-field/src/field_button/__snapshots__/field_button.test.tsx.snap @@ -2,7 +2,7 @@ exports[`fieldAction is rendered 1`] = `
@@ -50,7 +58,7 @@ exports[`fieldIcon is rendered 1`] = ` exports[`isActive defaults to false 1`] = `
@@ -67,7 +79,7 @@ exports[`isActive defaults to false 1`] = ` exports[`isActive renders true 1`] = `
`; -exports[`isDraggable is rendered 1`] = ` +exports[`sizes s is applied 1`] = `
`; -exports[`sizes m is applied 1`] = ` +exports[`sizes xs is applied 1`] = `
`; -exports[`sizes s is applied 1`] = ` +exports[`with drag handle is rendered 1`] = `
+
+ + drag + +
diff --git a/packages/kbn-react-field/src/field_button/field_button.scss b/packages/kbn-react-field/src/field_button/field_button.scss index f71e097ab7138..5fd87c38b930e 100644 --- a/packages/kbn-react-field/src/field_button/field_button.scss +++ b/packages/kbn-react-field/src/field_button/field_button.scss @@ -2,8 +2,9 @@ @include euiFontSizeS; border-radius: $euiBorderRadius; margin-bottom: $euiSizeXS; + padding: 0 $euiSizeS; display: flex; - align-items: center; + align-items: flex-start; transition: box-shadow $euiAnimSpeedFast $euiAnimSlightResistance, background-color $euiAnimSpeedFast $euiAnimSlightResistance; // sass-lint:disable-line indentation @@ -13,29 +14,10 @@ } } -.kbnFieldButton--isDraggable { - background: lightOrDarkTheme($euiColorEmptyShade, $euiColorLightestShade); - - &:hover, - &:focus, - &:focus-within { - @include euiBottomShadowMedium; - border-radius: $euiBorderRadius; - z-index: 2; - } - - .kbnFieldButton__button { - &:hover, - &:focus { - cursor: grab; - } - } -} - .kbnFieldButton__button { flex-grow: 1; text-align: left; - padding: $euiSizeS; + padding: $euiSizeS 0; display: flex; align-items: flex-start; line-height: normal; @@ -44,33 +26,55 @@ .kbnFieldButton__fieldIcon { flex-shrink: 0; line-height: 0; - margin-right: $euiSizeS; } .kbnFieldButton__name { flex-grow: 1; word-break: break-word; + padding: 0 $euiSizeS; } .kbnFieldButton__infoIcon { + display: flex; + align-items: center; + justify-content: center; + min-height: $euiSize; flex-shrink: 0; - margin-left: $euiSizeXS; + line-height: 0; } .kbnFieldButton__fieldAction { + margin-left: $euiSizeS; +} + +.kbnFieldButton__dragHandle { margin-right: $euiSizeS; } +.kbnFieldButton__fieldAction, +.kbnFieldButton__dragHandle { + line-height: $euiSizeXL; +} + // Reduce text size and spacing for the small size -.kbnFieldButton--small { +.kbnFieldButton--xs { font-size: $euiFontSizeXS; .kbnFieldButton__button { - padding: $euiSizeXS; + padding: $euiSizeXS 0; } - .kbnFieldButton__fieldIcon, .kbnFieldButton__fieldAction { - margin-right: $euiSizeXS; + margin-left: $euiSizeXS; + } + + .kbnFieldButton__fieldAction, + .kbnFieldButton__dragHandle { + line-height: $euiSizeL; } } + +.kbnFieldButton--flushBoth { + padding-left: 0; + padding-right: 0; +} diff --git a/packages/kbn-react-field/src/field_button/field_button.test.tsx b/packages/kbn-react-field/src/field_button/field_button.test.tsx index e61b41187ba7e..270144c7d346b 100644 --- a/packages/kbn-react-field/src/field_button/field_button.test.tsx +++ b/packages/kbn-react-field/src/field_button/field_button.test.tsx @@ -21,9 +21,11 @@ describe('sizes', () => { }); }); -describe('isDraggable', () => { +describe('with drag handle', () => { it('is rendered', () => { - const component = shallow(); + const component = shallow( + drag} /> + ); expect(component).toMatchSnapshot(); }); }); @@ -31,7 +33,7 @@ describe('isDraggable', () => { describe('fieldIcon', () => { it('is rendered', () => { const component = shallow( - fieldIcon} /> + fieldIcon} /> ); expect(component).toMatchSnapshot(); }); @@ -40,7 +42,12 @@ describe('fieldIcon', () => { describe('fieldAction', () => { it('is rendered', () => { const component = shallow( - fieldAction} /> + fieldAction} + /> ); expect(component).toMatchSnapshot(); }); @@ -48,11 +55,11 @@ describe('fieldAction', () => { describe('isActive', () => { it('defaults to false', () => { - const component = shallow(); + const component = shallow(); expect(component).toMatchSnapshot(); }); it('renders true', () => { - const component = shallow(); + const component = shallow(); expect(component).toMatchSnapshot(); }); }); diff --git a/packages/kbn-react-field/src/field_button/field_button.tsx b/packages/kbn-react-field/src/field_button/field_button.tsx index 4871fea049710..8d87e0e04cb63 100644 --- a/packages/kbn-react-field/src/field_button/field_button.tsx +++ b/packages/kbn-react-field/src/field_button/field_button.tsx @@ -6,10 +6,10 @@ * Side Public License, v 1. */ -import './field_button.scss'; import classNames from 'classnames'; import React, { ReactNode, HTMLAttributes, ButtonHTMLAttributes } from 'react'; import { CommonProps } from '@elastic/eui'; +import './field_button.scss'; export interface FieldButtonProps extends HTMLAttributes { /** @@ -34,13 +34,20 @@ export interface FieldButtonProps extends HTMLAttributes { */ isActive?: boolean; /** - * Styles the component differently to indicate it is draggable + * Custom drag handle element + */ + dragHandle?: React.ReactElement; + /** + * Use the xs size in condensed areas */ - isDraggable?: boolean; + size: ButtonSize; /** - * Use the small size in condensed areas + * Whether to skip side paddings + */ + flush?: 'both'; + /** + * Custom class name */ - size?: ButtonSize; className?: string; /** * The component will render a `