From b75546f7eb59f49e0d2daefa904736fe7e481f52 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yulia=20=C4=8Cech?= <6585477+yuliacech@users.noreply.github.com> Date: Wed, 26 Apr 2023 13:33:58 +0200 Subject: [PATCH 01/73] [Guided onboarding] Use Kibana features to grant access (#155065) ## Summary Fixes https://github.com/elastic/kibana/issues/149132 This PR adds a Kibana feature for the guided onboarding plugin for better permissions handling. By default `kibana_admin` and `editor` roles are granted access to guided onboarding. The role `viewer` on the other hand doesn't have enough permissions to see or use guided onboarding. For any roles that don't have the correct permissions, guided onboarding is completely disabled, the same as it's disabled on-prem. When creating a new role, the feature "Setup guides" can be enabled or disabled. ### How to test 1. Add `xpack.cloud.id: 'testID'` to `/config/kibana.dev.yml` 1. Start ES with `yarn es snapshot` and Kibana with `yarn start`` 2. Login as elastic and create a test user with the role `viewer` 3. Clear everything from your browser's local storage 4. Login as the test user and check the following - On the first visit, the "on-prem" welcome message is shown (not the guided onboarding landing page) - The url `/app/home#/getting_started` is unknown and redirects back to the home page - There is no button "Setup guides" in the header - There is no link "Setup guides" in the help menu ### 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 --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> --- .../public/components/app.tsx | 60 ++++++++++++------- .../guided_onboarding/common/constants.ts | 2 + src/plugins/guided_onboarding/kibana.jsonc | 3 +- src/plugins/guided_onboarding/public/mocks.ts | 1 + .../guided_onboarding/public/plugin.tsx | 9 ++- .../public/services/api.service.ts | 18 +++--- src/plugins/guided_onboarding/public/types.ts | 1 + .../guided_onboarding/server/feature.ts | 42 +++++++++++++ .../guided_onboarding/server/plugin.ts | 6 +- src/plugins/guided_onboarding/tsconfig.json | 1 + .../__snapshots__/home.test.tsx.snap | 35 +++++++++-- .../components/add_data/add_data.tsx | 10 ++-- .../application/components/home.test.tsx | 8 ++- .../public/application/components/home.tsx | 5 +- .../public/application/components/home_app.js | 9 ++- .../cloud_links/kibana.jsonc | 3 +- .../cloud_links/public/plugin.test.ts | 51 +++++++++++----- .../cloud_links/public/plugin.tsx | 25 ++++---- .../cloud_links/tsconfig.json | 1 + .../apis/features/features/features.ts | 1 + .../apis/security/privileges.ts | 1 + .../apis/security/privileges_basic.ts | 2 + .../security_and_spaces/tests/nav_links.ts | 16 ++++- 23 files changed, 229 insertions(+), 81 deletions(-) create mode 100644 src/plugins/guided_onboarding/server/feature.ts diff --git a/examples/guided_onboarding_example/public/components/app.tsx b/examples/guided_onboarding_example/public/components/app.tsx index 1a1083a10f1f3..2812697f37c51 100755 --- a/examples/guided_onboarding_example/public/components/app.tsx +++ b/examples/guided_onboarding_example/public/components/app.tsx @@ -11,13 +11,7 @@ import { FormattedMessage, I18nProvider } from '@kbn/i18n-react'; import { Router, Switch } from 'react-router-dom'; import { Route } from '@kbn/shared-ux-router'; -import { - EuiPage, - EuiPageBody, - EuiPageContent_Deprecated as EuiPageContent, - EuiPageHeader, - EuiTitle, -} from '@elastic/eui'; +import { EuiPageTemplate } from '@elastic/eui'; import { CoreStart, ScopedHistory } from '@kbn/core/public'; @@ -39,19 +33,17 @@ export const GuidedOnboardingExampleApp = (props: GuidedOnboardingExampleAppDeps return ( - - - - -

- -

-
-
- + + + } + /> + {guidedOnboarding.guidedOnboardingApi?.isEnabled ? ( + @@ -75,9 +67,31 @@ export const GuidedOnboardingExampleApp = (props: GuidedOnboardingExampleAppDeps /> - -
-
+ + ) : ( + + + + } + body={ +

+ +

+ } + /> + )} +
); }; diff --git a/src/plugins/guided_onboarding/common/constants.ts b/src/plugins/guided_onboarding/common/constants.ts index 1ab45d3659d73..899666bc757ce 100755 --- a/src/plugins/guided_onboarding/common/constants.ts +++ b/src/plugins/guided_onboarding/common/constants.ts @@ -9,4 +9,6 @@ export const PLUGIN_ID = 'guidedOnboarding'; export const PLUGIN_NAME = 'guidedOnboarding'; +export const PLUGIN_FEATURE = 'guidedOnboardingFeature'; + export const API_BASE_PATH = '/internal/guided_onboarding'; diff --git a/src/plugins/guided_onboarding/kibana.jsonc b/src/plugins/guided_onboarding/kibana.jsonc index e816f0e027fe9..300df79253dac 100644 --- a/src/plugins/guided_onboarding/kibana.jsonc +++ b/src/plugins/guided_onboarding/kibana.jsonc @@ -8,7 +8,8 @@ "server": true, "browser": true, "optionalPlugins": [ - "cloud" + "cloud", + "features" ], "requiredBundles": [ "kibanaReact" diff --git a/src/plugins/guided_onboarding/public/mocks.ts b/src/plugins/guided_onboarding/public/mocks.ts index e190ddbcc219e..a6fb3ae40da92 100644 --- a/src/plugins/guided_onboarding/public/mocks.ts +++ b/src/plugins/guided_onboarding/public/mocks.ts @@ -28,6 +28,7 @@ const apiServiceMock: jest.Mocked = { isGuidePanelOpen$: new BehaviorSubject(false), isLoading$: new BehaviorSubject(false), getGuideConfig: jest.fn(), + isEnabled: true, }, }; diff --git a/src/plugins/guided_onboarding/public/plugin.tsx b/src/plugins/guided_onboarding/public/plugin.tsx index 97eac765c5ddd..8f37552ae53de 100755 --- a/src/plugins/guided_onboarding/public/plugin.tsx +++ b/src/plugins/guided_onboarding/public/plugin.tsx @@ -21,6 +21,8 @@ import { } from '@kbn/core/public'; import { KibanaThemeProvider } from '@kbn/kibana-react-plugin/public'; + +import { PLUGIN_FEATURE } from '../common/constants'; import type { AppPluginStartDependencies, GuidedOnboardingPluginSetup, @@ -43,11 +45,12 @@ export class GuidedOnboardingPlugin ): GuidedOnboardingPluginStart { const { chrome, http, theme, application, notifications, uiSettings } = core; + // Guided onboarding UI is only available on cloud and if the access to the Kibana feature is granted + const isEnabled = !!(cloud?.isCloudEnabled && application.capabilities[PLUGIN_FEATURE].enabled); // Initialize services - apiService.setup(http, !!cloud?.isCloudEnabled); + apiService.setup(http, isEnabled); - // Guided onboarding UI is only available on cloud - if (cloud?.isCloudEnabled) { + if (isEnabled) { chrome.navControls.registerExtension({ order: 1000, mount: (target) => diff --git a/src/plugins/guided_onboarding/public/services/api.service.ts b/src/plugins/guided_onboarding/public/services/api.service.ts index d3d20143f600d..271a9afa9b569 100644 --- a/src/plugins/guided_onboarding/public/services/api.service.ts +++ b/src/plugins/guided_onboarding/public/services/api.service.ts @@ -41,15 +41,15 @@ import { import { ConfigService } from './config.service'; export class ApiService implements GuidedOnboardingApi { - private isCloudEnabled: boolean | undefined; + private _isEnabled: boolean = false; private client: HttpSetup | undefined; private pluginState$!: BehaviorSubject; public isLoading$ = new BehaviorSubject(false); public isGuidePanelOpen$: BehaviorSubject = new BehaviorSubject(false); private configService = new ConfigService(); - public setup(httpClient: HttpSetup, isCloudEnabled: boolean) { - this.isCloudEnabled = isCloudEnabled; + public setup(httpClient: HttpSetup, isEnabled: boolean) { + this._isEnabled = isEnabled; this.client = httpClient; this.pluginState$ = new BehaviorSubject(undefined); this.isGuidePanelOpen$ = new BehaviorSubject(false); @@ -94,7 +94,7 @@ export class ApiService implements GuidedOnboardingApi { * Subsequently, the observable is updated automatically, when the state changes. */ public fetchPluginState$(): Observable { - if (!this.isCloudEnabled) { + if (!this._isEnabled) { return of(undefined); } if (!this.client) { @@ -118,7 +118,7 @@ export class ApiService implements GuidedOnboardingApi { * where all guides are displayed with their corresponding status. */ public async fetchAllGuidesState(): Promise<{ state: GuideState[] } | undefined> { - if (!this.isCloudEnabled) { + if (!this._isEnabled) { return undefined; } if (!this.client) { @@ -143,7 +143,7 @@ export class ApiService implements GuidedOnboardingApi { state: { status?: PluginStatus; guide?: GuideState }, panelState: boolean ): Promise<{ pluginState: PluginState } | undefined> { - if (!this.isCloudEnabled) { + if (!this._isEnabled) { return undefined; } if (!this.client) { @@ -467,7 +467,7 @@ export class ApiService implements GuidedOnboardingApi { * @return {Promise} a promise with the guide config or undefined if the config is not found */ public async getGuideConfig(guideId: GuideId): Promise { - if (!this.isCloudEnabled) { + if (!this._isEnabled) { return undefined; } if (!this.client) { @@ -478,6 +478,10 @@ export class ApiService implements GuidedOnboardingApi { this.isLoading$.next(false); return config; } + + public get isEnabled() { + return this._isEnabled; + } } export const apiService = new ApiService(); diff --git a/src/plugins/guided_onboarding/public/types.ts b/src/plugins/guided_onboarding/public/types.ts index 1103c2ee350da..9f348b9910956 100755 --- a/src/plugins/guided_onboarding/public/types.ts +++ b/src/plugins/guided_onboarding/public/types.ts @@ -62,4 +62,5 @@ export interface GuidedOnboardingApi { isGuidePanelOpen$: Observable; isLoading$: Observable; getGuideConfig: (guideId: GuideId) => Promise; + readonly isEnabled: boolean; } diff --git a/src/plugins/guided_onboarding/server/feature.ts b/src/plugins/guided_onboarding/server/feature.ts new file mode 100644 index 0000000000000..fce2cb9033b9d --- /dev/null +++ b/src/plugins/guided_onboarding/server/feature.ts @@ -0,0 +1,42 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import type { KibanaFeatureConfig } from '@kbn/features-plugin/common'; +import { i18n } from '@kbn/i18n'; +import { DEFAULT_APP_CATEGORIES } from '@kbn/core/server'; +import { PLUGIN_FEATURE, PLUGIN_ID } from '../common/constants'; +import { guideStateSavedObjectsType, pluginStateSavedObjectsType } from './saved_objects'; + +export const GUIDED_ONBOARDING_FEATURE: KibanaFeatureConfig = { + id: PLUGIN_FEATURE, + name: i18n.translate('guidedOnboarding.featureRegistry.featureName', { + defaultMessage: 'Setup guides', + }), + category: DEFAULT_APP_CATEGORIES.management, + app: [PLUGIN_ID], + privileges: { + all: { + app: [PLUGIN_ID], + savedObject: { + all: [guideStateSavedObjectsType, pluginStateSavedObjectsType], + read: [], + }, + ui: ['enabled'], + }, + read: { + // we haven't implemented "read-only" access yet, so this feature can only be granted + // as "all" or "none" + disabled: true, + savedObject: { + all: [], + read: [], + }, + ui: [], + }, + }, +}; diff --git a/src/plugins/guided_onboarding/server/plugin.ts b/src/plugins/guided_onboarding/server/plugin.ts index f264771d780ee..3a5138d20133b 100755 --- a/src/plugins/guided_onboarding/server/plugin.ts +++ b/src/plugins/guided_onboarding/server/plugin.ts @@ -9,6 +9,8 @@ import { PluginInitializerContext, CoreSetup, Plugin, Logger } from '@kbn/core/server'; import type { GuideId, GuideConfig } from '@kbn/guided-onboarding'; +import { PluginSetupContract as FeaturesPluginSetup } from '@kbn/features-plugin/server'; +import { GUIDED_ONBOARDING_FEATURE } from './feature'; import { GuidedOnboardingPluginSetup, GuidedOnboardingPluginStart } from './types'; import { defineRoutes } from './routes'; import { guideStateSavedObjects, pluginStateSavedObjects } from './saved_objects'; @@ -25,7 +27,7 @@ export class GuidedOnboardingPlugin this.guidesConfig = {} as GuidesConfig; } - public setup(core: CoreSetup) { + public setup(core: CoreSetup, plugins: { features?: FeaturesPluginSetup }) { this.logger.debug('guidedOnboarding: Setup'); const router = core.http.createRouter(); @@ -36,6 +38,8 @@ export class GuidedOnboardingPlugin core.savedObjects.registerType(guideStateSavedObjects); core.savedObjects.registerType(pluginStateSavedObjects); + plugins.features?.registerKibanaFeature(GUIDED_ONBOARDING_FEATURE); + return { registerGuideConfig: (guideId: GuideId, guideConfig: GuideConfig) => { if (this.guidesConfig[guideId]) { diff --git a/src/plugins/guided_onboarding/tsconfig.json b/src/plugins/guided_onboarding/tsconfig.json index 42026215e18fd..88569a7a43238 100644 --- a/src/plugins/guided_onboarding/tsconfig.json +++ b/src/plugins/guided_onboarding/tsconfig.json @@ -18,6 +18,7 @@ "@kbn/core-http-browser", "@kbn/core-http-browser-mocks", "@kbn/config-schema", + "@kbn/features-plugin", ], "exclude": [ "target/**/*", diff --git a/src/plugins/home/public/application/components/__snapshots__/home.test.tsx.snap b/src/plugins/home/public/application/components/__snapshots__/home.test.tsx.snap index 53df35833013f..03dfb38204295 100644 --- a/src/plugins/home/public/application/components/__snapshots__/home.test.tsx.snap +++ b/src/plugins/home/public/application/components/__snapshots__/home.test.tsx.snap @@ -308,12 +308,19 @@ exports[`home isNewKibanaInstance should safely handle exceptions 1`] = ` Array [ "./home#/getting_started", ], + Array [ + "./home#/getting_started", + ], ], "results": Array [ Object { "type": "return", "value": undefined, }, + Object { + "type": "return", + "value": undefined, + }, ], }, } @@ -335,12 +342,19 @@ exports[`home isNewKibanaInstance should safely handle exceptions 1`] = ` Array [ "./home#/getting_started", ], + Array [ + "./home#/getting_started", + ], ], "results": Array [ Object { "type": "return", "value": undefined, }, + Object { + "type": "return", + "value": undefined, + }, ], }, } @@ -389,12 +403,19 @@ exports[`home isNewKibanaInstance should set isNewKibanaInstance to false when t Array [ "./home#/getting_started", ], + Array [ + "./home#/getting_started", + ], ], "results": Array [ Object { "type": "return", "value": undefined, }, + Object { + "type": "return", + "value": undefined, + }, ], }, } @@ -416,12 +437,19 @@ exports[`home isNewKibanaInstance should set isNewKibanaInstance to false when t Array [ "./home#/getting_started", ], + Array [ + "./home#/getting_started", + ], ], "results": Array [ Object { "type": "return", "value": undefined, }, + Object { + "type": "return", + "value": undefined, + }, ], }, } @@ -437,12 +465,7 @@ exports[`home isNewKibanaInstance should set isNewKibanaInstance to false when t `; -exports[`home isNewKibanaInstance should set isNewKibanaInstance to true when there are no index patterns 1`] = ` - -`; +exports[`home isNewKibanaInstance should set isNewKibanaInstance to true when there are no index patterns 1`] = `""`; exports[`home should render home component 1`] = ` <_KibanaPageTemplate diff --git a/src/plugins/home/public/application/components/add_data/add_data.tsx b/src/plugins/home/public/application/components/add_data/add_data.tsx index 086e28cbb1f83..5440ad09d2c15 100644 --- a/src/plugins/home/public/application/components/add_data/add_data.tsx +++ b/src/plugins/home/public/application/components/add_data/add_data.tsx @@ -35,7 +35,7 @@ interface Props { } export const AddData: FC = ({ addBasePath, application, isDarkMode, isCloudEnabled }) => { - const { trackUiMetric } = getServices(); + const { trackUiMetric, guidedOnboardingService } = getServices(); const canAccessIntegrations = application.capabilities.navLinks.integrations; if (canAccessIntegrations) { return ( @@ -70,7 +70,7 @@ export const AddData: FC = ({ addBasePath, application, isDarkMode, isClo - {isCloudEnabled && ( + {guidedOnboardingService?.isEnabled && ( {/* eslint-disable-next-line @elastic/eui/href-or-on-click */} = ({ addBasePath, application, isDarkMode, isClo {/* eslint-disable-next-line @elastic/eui/href-or-on-click */} { diff --git a/src/plugins/home/public/application/components/home.test.tsx b/src/plugins/home/public/application/components/home.test.tsx index 806ceed7d7591..c7240d4309078 100644 --- a/src/plugins/home/public/application/components/home.test.tsx +++ b/src/plugins/home/public/application/components/home.test.tsx @@ -15,6 +15,8 @@ import { Welcome } from './welcome'; let mockHasIntegrationsPermission = true; const mockNavigateToUrl = jest.fn(); +let mockIsEnabled = false; + jest.mock('../kibana_services', () => ({ getServices: () => ({ getBasePath: () => 'path', @@ -31,6 +33,9 @@ jest.mock('../kibana_services', () => ({ }, }, }, + guidedOnboardingService: { + isEnabled: mockIsEnabled, + }, }), })); @@ -234,7 +239,8 @@ describe('home', () => { expect(component.find(Welcome).exists()).toBe(false); }); - test('should redirect to guided onboarding on Cloud instead of welcome screen', async () => { + test('should redirect to guided onboarding on Cloud instead of welcome screen if guided onboarding is enabled', async () => { + mockIsEnabled = true; const isCloudEnabled = true; const hasUserDataView = jest.fn(async () => false); diff --git a/src/plugins/home/public/application/components/home.tsx b/src/plugins/home/public/application/components/home.tsx index 7a65c48807a33..176b620430a56 100644 --- a/src/plugins/home/public/application/components/home.tsx +++ b/src/plugins/home/public/application/components/home.tsx @@ -188,15 +188,14 @@ export class Home extends Component { public render() { const { isLoading, isWelcomeEnabled, isNewKibanaInstance } = this.state; - const { isCloudEnabled } = this.props; - const { application } = getServices(); + const { application, guidedOnboardingService } = getServices(); if (isWelcomeEnabled) { if (isLoading) { return this.renderLoading(); } if (isNewKibanaInstance) { - if (isCloudEnabled) { + if (guidedOnboardingService?.isEnabled) { application.navigateToUrl('./home#/getting_started'); return null; } diff --git a/src/plugins/home/public/application/components/home_app.js b/src/plugins/home/public/application/components/home_app.js index 58d9c2a0f3fc4..f800a26c787ba 100644 --- a/src/plugins/home/public/application/components/home_app.js +++ b/src/plugins/home/public/application/components/home_app.js @@ -29,6 +29,7 @@ export function HomeApp({ directories, solutions }) { addBasePath, environmentService, dataViewsService, + guidedOnboardingService, } = getServices(); const environment = environmentService.getEnvironment(); const isCloudEnabled = environment.cloud; @@ -69,9 +70,11 @@ export function HomeApp({ directories, solutions }) { - - - + {guidedOnboardingService.isEnabled && ( + + + + )} { let plugin: CloudLinksPlugin; @@ -32,26 +33,46 @@ describe('Cloud Links Plugin - public', () => { }); describe('Onboarding Setup Guide link registration', () => { - test('registers the Onboarding Setup Guide link when cloud is enabled and it is an authenticated page', () => { - const coreStart = coreMock.createStart(); - coreStart.http.anonymousPaths.isAnonymous.mockReturnValue(false); - const cloud = { ...cloudMock.createStart(), isCloudEnabled: true }; - plugin.start(coreStart, { cloud }); - expect(coreStart.chrome.registerGlobalHelpExtensionMenuLink).toHaveBeenCalledTimes(1); + describe('guided onboarding is enabled', () => { + const guidedOnboarding = guidedOnboardingMock.createStart(); + test('registers the Onboarding Setup Guide link when cloud and guided onboarding is enabled and it is an authenticated page', () => { + const coreStart = coreMock.createStart(); + coreStart.http.anonymousPaths.isAnonymous.mockReturnValue(false); + const cloud = { ...cloudMock.createStart(), isCloudEnabled: true }; + + plugin.start(coreStart, { cloud, guidedOnboarding }); + expect(coreStart.chrome.registerGlobalHelpExtensionMenuLink).toHaveBeenCalledTimes(1); + }); + + test('does not register the Onboarding Setup Guide link when cloud is enabled but it is an unauthenticated page', () => { + const coreStart = coreMock.createStart(); + coreStart.http.anonymousPaths.isAnonymous.mockReturnValue(true); + const cloud = { ...cloudMock.createStart(), isCloudEnabled: true }; + plugin.start(coreStart, { cloud, guidedOnboarding }); + expect(coreStart.chrome.registerGlobalHelpExtensionMenuLink).not.toHaveBeenCalled(); + }); + + test('does not register the Onboarding Setup Guide link when cloud is not enabled', () => { + const coreStart = coreMock.createStart(); + const cloud = { ...cloudMock.createStart(), isCloudEnabled: false }; + plugin.start(coreStart, { cloud, guidedOnboarding }); + expect(coreStart.chrome.registerGlobalHelpExtensionMenuLink).not.toHaveBeenCalled(); + }); }); - test('does not register the Onboarding Setup Guide link when cloud is enabled but it is an unauthenticated page', () => { + test('do not register the Onboarding Setup Guide link when guided onboarding is disabled', () => { + let { guidedOnboardingApi } = guidedOnboardingMock.createStart(); + guidedOnboardingApi = { + ...guidedOnboardingApi!, + isEnabled: false, + }; + const guidedOnboarding = { guidedOnboardingApi }; + const coreStart = coreMock.createStart(); - coreStart.http.anonymousPaths.isAnonymous.mockReturnValue(true); + coreStart.http.anonymousPaths.isAnonymous.mockReturnValue(false); const cloud = { ...cloudMock.createStart(), isCloudEnabled: true }; - plugin.start(coreStart, { cloud }); - expect(coreStart.chrome.registerGlobalHelpExtensionMenuLink).not.toHaveBeenCalled(); - }); - test('does not register the Onboarding Setup Guide link when cloud is not enabled', () => { - const coreStart = coreMock.createStart(); - const cloud = { ...cloudMock.createStart(), isCloudEnabled: false }; - plugin.start(coreStart, { cloud }); + plugin.start(coreStart, { cloud, guidedOnboarding }); expect(coreStart.chrome.registerGlobalHelpExtensionMenuLink).not.toHaveBeenCalled(); }); }); diff --git a/x-pack/plugins/cloud_integrations/cloud_links/public/plugin.tsx b/x-pack/plugins/cloud_integrations/cloud_links/public/plugin.tsx index b5c3c4aeeb555..a2f8af345ac0a 100755 --- a/x-pack/plugins/cloud_integrations/cloud_links/public/plugin.tsx +++ b/x-pack/plugins/cloud_integrations/cloud_links/public/plugin.tsx @@ -10,6 +10,7 @@ import { FormattedMessage } from '@kbn/i18n-react'; import type { CoreStart, Plugin } from '@kbn/core/public'; import type { CloudSetup, CloudStart } from '@kbn/cloud-plugin/public'; import type { SecurityPluginSetup, SecurityPluginStart } from '@kbn/security-plugin/public'; +import type { GuidedOnboardingPluginStart } from '@kbn/guided-onboarding-plugin/public'; import { maybeAddCloudLinks } from './maybe_add_cloud_links'; interface CloudLinksDepsSetup { @@ -20,6 +21,7 @@ interface CloudLinksDepsSetup { interface CloudLinksDepsStart { cloud?: CloudStart; security?: SecurityPluginStart; + guidedOnboarding?: GuidedOnboardingPluginStart; } export class CloudLinksPlugin @@ -27,18 +29,19 @@ export class CloudLinksPlugin { public setup() {} - public start(core: CoreStart, { cloud, security }: CloudLinksDepsStart) { + public start(core: CoreStart, { cloud, security, guidedOnboarding }: CloudLinksDepsStart) { if (cloud?.isCloudEnabled && !core.http.anonymousPaths.isAnonymous(window.location.pathname)) { - core.chrome.registerGlobalHelpExtensionMenuLink({ - linkType: 'custom', - href: core.http.basePath.prepend('/app/home#/getting_started'), - content: ( - - ), - 'data-test-subj': 'cloudOnboardingSetupGuideLink', - priority: 1000, // We want this link to be at the very top. - }); - + if (guidedOnboarding?.guidedOnboardingApi?.isEnabled) { + core.chrome.registerGlobalHelpExtensionMenuLink({ + linkType: 'custom', + href: core.http.basePath.prepend('/app/home#/getting_started'), + content: ( + + ), + 'data-test-subj': 'cloudOnboardingSetupGuideLink', + priority: 1000, // We want this link to be at the very top. + }); + } if (security) { maybeAddCloudLinks({ security, chrome: core.chrome, cloud }); } diff --git a/x-pack/plugins/cloud_integrations/cloud_links/tsconfig.json b/x-pack/plugins/cloud_integrations/cloud_links/tsconfig.json index 2354df693cb95..ab4f52a347591 100644 --- a/x-pack/plugins/cloud_integrations/cloud_links/tsconfig.json +++ b/x-pack/plugins/cloud_integrations/cloud_links/tsconfig.json @@ -16,6 +16,7 @@ "@kbn/security-plugin", "@kbn/i18n", "@kbn/i18n-react", + "@kbn/guided-onboarding-plugin", ], "exclude": [ "target/**/*", diff --git a/x-pack/test/api_integration/apis/features/features/features.ts b/x-pack/test/api_integration/apis/features/features/features.ts index 538782b272dfc..d5038a7c51ded 100644 --- a/x-pack/test/api_integration/apis/features/features/features.ts +++ b/x-pack/test/api_integration/apis/features/features/features.ts @@ -105,6 +105,7 @@ export default function ({ getService }: FtrProviderContext) { 'advancedSettings', 'indexPatterns', 'graph', + 'guidedOnboardingFeature', 'monitoring', 'observabilityCases', 'savedObjectsManagement', diff --git a/x-pack/test/api_integration/apis/security/privileges.ts b/x-pack/test/api_integration/apis/security/privileges.ts index d22b46b58f42a..c03f9fb91db65 100644 --- a/x-pack/test/api_integration/apis/security/privileges.ts +++ b/x-pack/test/api_integration/apis/security/privileges.ts @@ -104,6 +104,7 @@ export default function ({ getService }: FtrProviderContext) { 'readFlappingSettings', ], maintenanceWindow: ['all', 'read', 'minimal_all', 'minimal_read'], + guidedOnboardingFeature: ['all', 'read', 'minimal_all', 'minimal_read'], }, reserved: ['fleet-setup', 'ml_user', 'ml_admin', 'ml_apm_user', 'monitoring'], }; diff --git a/x-pack/test/api_integration/apis/security/privileges_basic.ts b/x-pack/test/api_integration/apis/security/privileges_basic.ts index 3f083b0bd5228..201590cdcaf5a 100644 --- a/x-pack/test/api_integration/apis/security/privileges_basic.ts +++ b/x-pack/test/api_integration/apis/security/privileges_basic.ts @@ -50,6 +50,7 @@ export default function ({ getService }: FtrProviderContext) { filesSharedImage: ['all', 'read', 'minimal_all', 'minimal_read'], rulesSettings: ['all', 'read', 'minimal_all', 'minimal_read'], maintenanceWindow: ['all', 'read', 'minimal_all', 'minimal_read'], + guidedOnboardingFeature: ['all', 'read', 'minimal_all', 'minimal_read'], }, global: ['all', 'read'], space: ['all', 'read'], @@ -176,6 +177,7 @@ export default function ({ getService }: FtrProviderContext) { 'readFlappingSettings', ], maintenanceWindow: ['all', 'read', 'minimal_all', 'minimal_read'], + guidedOnboardingFeature: ['all', 'read', 'minimal_all', 'minimal_read'], }, reserved: ['fleet-setup', 'ml_user', 'ml_admin', 'ml_apm_user', 'monitoring'], }; diff --git a/x-pack/test/ui_capabilities/security_and_spaces/tests/nav_links.ts b/x-pack/test/ui_capabilities/security_and_spaces/tests/nav_links.ts index 5167b611de2b4..f72a9a467b264 100644 --- a/x-pack/test/ui_capabilities/security_and_spaces/tests/nav_links.ts +++ b/x-pack/test/ui_capabilities/security_and_spaces/tests/nav_links.ts @@ -45,6 +45,19 @@ export default function navLinksTests({ getService }: FtrProviderContext) { expect(uiCapabilities.value!.navLinks).to.eql(navLinksBuilder.except('monitoring')); break; case 'everything_space_all at everything_space': + expect(uiCapabilities.success).to.be(true); + expect(uiCapabilities.value).to.have.property('navLinks'); + expect(uiCapabilities.value!.navLinks).to.eql( + navLinksBuilder.except( + 'monitoring', + 'enterpriseSearch', + 'enterpriseSearchContent', + 'enterpriseSearchAnalytics', + 'appSearch', + 'workplaceSearch' + ) + ); + break; case 'global_read at everything_space': case 'dual_privileges_read at everything_space': case 'everything_space_read at everything_space': @@ -57,7 +70,8 @@ export default function navLinksTests({ getService }: FtrProviderContext) { 'enterpriseSearchContent', 'enterpriseSearchAnalytics', 'appSearch', - 'workplaceSearch' + 'workplaceSearch', + 'guidedOnboardingFeature' ) ); break; From 3ce1ce389b61f15bf6113343b2017ddcd5dcd779 Mon Sep 17 00:00:00 2001 From: Marco Liberati Date: Wed, 26 Apr 2023 13:39:19 +0200 Subject: [PATCH 02/73] [Lens] Use proper way to generate absolute short URL (#155512) ## Summary Generate short URL absolute version as described in https://github.com/elastic/kibana/issues/153323#issuecomment-1517720382 ### Checklist Delete any items that are not applicable to this PR. - [ ] Any text added follows [EUI's writing guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses sentence case text and includes [i18n support](https://github.com/elastic/kibana/blob/main/packages/kbn-i18n/README.md) - [ ] [Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html) was added for features that require explanation or tutorials - [ ] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios - [ ] Any UI touched in this PR is usable by keyboard only (learn more about [keyboard accessibility](https://webaim.org/techniques/keyboard/)) - [ ] Any UI touched in this PR does not create any new axe failures (run axe in browser: [FF](https://addons.mozilla.org/en-US/firefox/addon/axe-devtools/), [Chrome](https://chrome.google.com/webstore/detail/axe-web-accessibility-tes/lhdoppojpmngadmnindnejefpokejbdd?hl=en-US)) - [ ] If a plugin configuration key changed, check if it needs to be allowlisted in the cloud and added to the [docker list](https://github.com/elastic/kibana/blob/main/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker) - [ ] This renders correctly on smaller devices using a responsive layout. (You can test this [in your browser](https://www.browserstack.com/guide/responsive-testing-on-local-server)) - [ ] This was checked for [cross-browser compatibility](https://www.elastic.co/support/matrix#matrix_browsers) ### Risk Matrix Delete this section if it is not applicable to this PR. Before closing this PR, invite QA, stakeholders, and other developers to identify risks that should be tested prior to the change/feature release. When forming the risk matrix, consider some of the following examples and how they may potentially impact the change: | Risk | Probability | Severity | Mitigation/Notes | |---------------------------|-------------|----------|-------------------------| | Multiple Spaces—unexpected behavior in non-default Kibana Space. | Low | High | Integration tests will verify that all features are still supported in non-default Kibana Space and when user switches between spaces. | | Multiple nodes—Elasticsearch polling might have race conditions when multiple Kibana nodes are polling for the same tasks. | High | Low | Tasks are idempotent, so executing them multiple times will not result in logical error, but will degrade performance. To test for this case we add plenty of unit tests around this logic and document manual testing procedure. | | Code should gracefully handle cases when feature X or plugin Y are disabled. | Medium | High | Unit tests will verify that any feature flag or plugin combination still results in our service operational. | | [See more potential risk examples](https://github.com/elastic/kibana/blob/main/RISK_MATRIX.mdx) | ### For maintainers - [ ] This was checked for breaking API changes and was [labeled appropriately](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process) --- x-pack/plugins/lens/public/app_plugin/app.tsx | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/x-pack/plugins/lens/public/app_plugin/app.tsx b/x-pack/plugins/lens/public/app_plugin/app.tsx index c18b3f5461cd4..ebba2083fdf32 100644 --- a/x-pack/plugins/lens/public/app_plugin/app.tsx +++ b/x-pack/plugins/lens/public/app_plugin/app.tsx @@ -457,17 +457,14 @@ export function App({ } if (locator && shortUrls) { // This is a stripped down version of what the share URL plugin is doing - const relativeUrl = await shortUrls.create({ locator, params }); - const absoluteShortUrl = application.getUrlForApp('', { - path: `/r/s/${relativeUrl.data.slug}`, - absolute: true, - }); + const shortUrl = await shortUrls.createWithLocator({ locator, params }); + const absoluteShortUrl = await shortUrl.locator.getUrl(shortUrl.params, { absolute: true }); shareURLCache.current = { params: cacheKey, url: absoluteShortUrl }; return absoluteShortUrl; } return ''; }, - [locator, application, shortUrls] + [locator, shortUrls] ); const returnToOriginSwitchLabelForContext = From c640c25c74cc7a1084159263d895eb8b3ceedbb2 Mon Sep 17 00:00:00 2001 From: Vadim Kibana <82822460+vadimkibana@users.noreply.github.com> Date: Wed, 26 Apr 2023 13:48:46 +0200 Subject: [PATCH 03/73] [Files] Adds bulk delete method (#155628) ## Summary Closes https://github.com/elastic/kibana/issues/154286 ### 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) --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> --- .../server/file_service/file_action_types.ts | 10 ++++++++ .../files/server/file_service/file_service.ts | 8 +++++++ .../file_service/file_service_factory.ts | 5 +++- .../file_service/internal_file_service.ts | 13 +++++++++++ .../integration_tests/file_service.test.ts | 23 +++++++++++++++++-- src/plugins/files/server/mocks.ts | 1 + 6 files changed, 57 insertions(+), 3 deletions(-) diff --git a/src/plugins/files/server/file_service/file_action_types.ts b/src/plugins/files/server/file_service/file_action_types.ts index 96795ac93b387..f0aad5e86e8eb 100644 --- a/src/plugins/files/server/file_service/file_action_types.ts +++ b/src/plugins/files/server/file_service/file_action_types.ts @@ -62,6 +62,16 @@ export interface DeleteFileArgs { id: string; } +/** + * Arguments to delete files in a bulk request. + */ +export interface BulkDeleteFilesArgs { + /** + * File IDs. + */ + ids: string[]; +} + /** * Arguments to get a file by ID. */ diff --git a/src/plugins/files/server/file_service/file_service.ts b/src/plugins/files/server/file_service/file_service.ts index 9dc1b0769cedf..0828d03ada7fd 100644 --- a/src/plugins/files/server/file_service/file_service.ts +++ b/src/plugins/files/server/file_service/file_service.ts @@ -12,6 +12,7 @@ import type { CreateFileArgs, UpdateFileArgs, DeleteFileArgs, + BulkDeleteFilesArgs, GetByIdArgs, FindFileArgs, } from './file_action_types'; @@ -43,6 +44,13 @@ export interface FileServiceStart { */ delete(args: DeleteFileArgs): Promise; + /** + * Delete multiple files at once. + * + * @param args - delete files args + */ + bulkDelete(args: BulkDeleteFilesArgs): Promise>>; + /** * Get a file by ID. Will throw if file cannot be found. * diff --git a/src/plugins/files/server/file_service/file_service_factory.ts b/src/plugins/files/server/file_service/file_service_factory.ts index 50ceafb6fc249..bebb5b72c2b3d 100644 --- a/src/plugins/files/server/file_service/file_service_factory.ts +++ b/src/plugins/files/server/file_service/file_service_factory.ts @@ -94,7 +94,10 @@ export class FileServiceFactoryImpl implements FileServiceFactory { await internalFileService.updateFile(args); }, async delete(args) { - return internalFileService.deleteFile(args); + return await internalFileService.deleteFile(args); + }, + async bulkDelete(args) { + return await internalFileService.bulkDeleteFiles(args); }, async getById(args: GetByIdArgs) { return internalFileService.getById(args) as Promise>; diff --git a/src/plugins/files/server/file_service/internal_file_service.ts b/src/plugins/files/server/file_service/internal_file_service.ts index cd7a45740de4e..653929f7b1236 100644 --- a/src/plugins/files/server/file_service/internal_file_service.ts +++ b/src/plugins/files/server/file_service/internal_file_service.ts @@ -8,6 +8,7 @@ import { Logger, SavedObjectsErrorHelpers } from '@kbn/core/server'; import { AuditEvent, AuditLogger } from '@kbn/security-plugin/server'; +import pLimit from 'p-limit'; import { BlobStorageService } from '../blob_storage_service'; import { InternalFileShareService } from '../file_share_service'; @@ -20,10 +21,14 @@ import type { CreateFileArgs, UpdateFileArgs, DeleteFileArgs, + BulkDeleteFilesArgs, FindFileArgs, GetByIdArgs, } from './file_action_types'; import { createFileClient, FileClientImpl } from '../file_client/file_client'; + +const bulkDeleteConcurrency = pLimit(10); + /** * Service containing methods for working with files. * @@ -64,6 +69,14 @@ export class InternalFileService { await file.delete(); } + public async bulkDeleteFiles({ + ids, + }: BulkDeleteFilesArgs): Promise>> { + const promises = ids.map((id) => bulkDeleteConcurrency(() => this.deleteFile({ id }))); + const result = await Promise.allSettled(promises); + return result; + } + private async get(id: string) { try { const { metadata } = await this.metadataClient.get({ id }); diff --git a/src/plugins/files/server/integration_tests/file_service.test.ts b/src/plugins/files/server/integration_tests/file_service.test.ts index 3492eb8e5f12c..5ca1fa00bdd0a 100644 --- a/src/plugins/files/server/integration_tests/file_service.test.ts +++ b/src/plugins/files/server/integration_tests/file_service.test.ts @@ -99,7 +99,7 @@ describe('FileService', () => { return file; } afterEach(async () => { - await Promise.all(disposables.map((file) => file.delete())); + await fileService.bulkDelete({ ids: disposables.map((d) => d.id) }); const { files } = await fileService.find({ kind: [fileKind] }); expect(files.length).toBe(0); disposables = []; @@ -246,7 +246,7 @@ describe('FileService', () => { expect(result3.files.length).toBe(2); }); - it('deletes files', async () => { + it('deletes a single file', async () => { const file = await fileService.create({ fileKind, name: 'test' }); const result = await fileService.find({ kind: [fileKind] }); expect(result.files.length).toBe(1); @@ -254,6 +254,25 @@ describe('FileService', () => { expect(await fileService.find({ kind: [fileKind] })).toEqual({ files: [], total: 0 }); }); + it('deletes a single file using the bulk method', async () => { + const file = await fileService.create({ fileKind, name: 'test' }); + const result = await fileService.find({ kind: [fileKind] }); + expect(result.files.length).toBe(1); + await fileService.bulkDelete({ ids: [file.id] }); + expect(await fileService.find({ kind: [fileKind] })).toEqual({ files: [], total: 0 }); + }); + + it('deletes multiple files using the bulk method', async () => { + const promises = Array.from({ length: 15 }, (v, i) => + fileService.create({ fileKind, name: 'test ' + i }) + ); + const files = await Promise.all(promises); + const result = await fileService.find({ kind: [fileKind] }); + expect(result.files.length).toBe(15); + await fileService.bulkDelete({ ids: files.map((file) => file.id) }); + expect(await fileService.find({ kind: [fileKind] })).toEqual({ files: [], total: 0 }); + }); + interface CustomMeta { some: string; } diff --git a/src/plugins/files/server/mocks.ts b/src/plugins/files/server/mocks.ts index 8472717b544a1..da94d95b273c4 100644 --- a/src/plugins/files/server/mocks.ts +++ b/src/plugins/files/server/mocks.ts @@ -15,6 +15,7 @@ import { FileClient, FileServiceFactory, FileServiceStart, FilesSetup } from '.' export const createFileServiceMock = (): DeeplyMockedKeys => ({ create: jest.fn(), delete: jest.fn(), + bulkDelete: jest.fn(), deleteShareObject: jest.fn(), find: jest.fn(), getById: jest.fn(), From 57f3b38324ec256885ff3683d12efab54cda565c Mon Sep 17 00:00:00 2001 From: Xavier Mouligneau Date: Wed, 26 Apr 2023 07:53:49 -0400 Subject: [PATCH 04/73] [RAM] Conditional actions feedback on pr review (#155804) ## Summary - Fixes: https://github.com/elastic/kibana/pull/155384#discussion_r1175847253 - Fixes language around conditional filter to not confuse our user like it was before image ### Checklist - [x] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios --------- Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../server/rules_client/lib/validate_actions.test.ts | 2 +- .../alerting/server/rules_client/lib/validate_actions.ts | 2 +- .../alerting/server/rules_client/tests/create.test.ts | 4 ++-- .../action_connector_form/action_alerts_filter_query.tsx | 2 +- .../action_alerts_filter_timeframe.tsx | 8 +++++--- .../sections/action_connector_form/action_notify_when.tsx | 4 ++-- 6 files changed, 12 insertions(+), 10 deletions(-) diff --git a/x-pack/plugins/alerting/server/rules_client/lib/validate_actions.test.ts b/x-pack/plugins/alerting/server/rules_client/lib/validate_actions.test.ts index 229c009df3eec..04236b9ecc1cc 100644 --- a/x-pack/plugins/alerting/server/rules_client/lib/validate_actions.test.ts +++ b/x-pack/plugins/alerting/server/rules_client/lib/validate_actions.test.ts @@ -164,7 +164,7 @@ describe('validateActions', () => { false ) ).rejects.toThrowErrorMatchingInlineSnapshot( - '"Failed to validate actions due to the following error: Action throttle cannot be shorter than the schedule interval of 1m: default (1s)"' + '"Failed to validate actions due to the following error: Action frequency cannot be shorter than the schedule interval of 1m: default (1s)"' ); }); diff --git a/x-pack/plugins/alerting/server/rules_client/lib/validate_actions.ts b/x-pack/plugins/alerting/server/rules_client/lib/validate_actions.ts index b86e30556f090..54a0f42dd6924 100644 --- a/x-pack/plugins/alerting/server/rules_client/lib/validate_actions.ts +++ b/x-pack/plugins/alerting/server/rules_client/lib/validate_actions.ts @@ -175,7 +175,7 @@ export async function validateActions( errors.push( i18n.translate('xpack.alerting.rulesClient.validateActions.actionsWithInvalidThrottles', { defaultMessage: - 'Action throttle cannot be shorter than the schedule interval of {scheduleIntervalText}: {groups}', + 'Action frequency cannot be shorter than the schedule interval of {scheduleIntervalText}: {groups}', values: { scheduleIntervalText: data.schedule.interval, groups: actionsWithInvalidThrottles diff --git a/x-pack/plugins/alerting/server/rules_client/tests/create.test.ts b/x-pack/plugins/alerting/server/rules_client/tests/create.test.ts index aedf130e1f846..7e21881410e74 100644 --- a/x-pack/plugins/alerting/server/rules_client/tests/create.test.ts +++ b/x-pack/plugins/alerting/server/rules_client/tests/create.test.ts @@ -3045,7 +3045,7 @@ describe('create()', () => { ], }); await expect(rulesClient.create({ data })).rejects.toThrowErrorMatchingInlineSnapshot( - `"Failed to validate actions due to the following error: Action throttle cannot be shorter than the schedule interval of 3h: default (1h), group2 (3m)"` + `"Failed to validate actions due to the following error: Action frequency cannot be shorter than the schedule interval of 3h: default (1h), group2 (3m)"` ); expect(unsecuredSavedObjectsClient.create).not.toHaveBeenCalled(); expect(taskManager.schedule).not.toHaveBeenCalled(); @@ -3122,7 +3122,7 @@ describe('create()', () => { await expect(rulesClient.create({ data })).rejects.toThrowErrorMatchingInlineSnapshot(` "Failed to validate actions due to the following 2 errors: - Actions missing frequency parameters: group3 - - Action throttle cannot be shorter than the schedule interval of 3h: default (1h), group2 (3m)" + - Action frequency cannot be shorter than the schedule interval of 3h: default (1h), group2 (3m)" `); expect(unsecuredSavedObjectsClient.create).not.toHaveBeenCalled(); expect(taskManager.schedule).not.toHaveBeenCalled(); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_alerts_filter_query.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_alerts_filter_query.tsx index 3cd22cc70a429..682bdbb52d81d 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_alerts_filter_query.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_alerts_filter_query.tsx @@ -61,7 +61,7 @@ export const ActionAlertsFilterQuery: React.FC = ( label={i18n.translate( 'xpack.triggersActionsUI.sections.actionTypeForm.ActionAlertsFilterQueryToggleLabel', { - defaultMessage: 'Send alert notification only if alert fields match a query', + defaultMessage: 'if alert matches a query', } )} checked={queryEnabled} diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_alerts_filter_timeframe.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_alerts_filter_timeframe.tsx index 05fcd8fba77a7..fa58e8d0c3f25 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_alerts_filter_timeframe.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_alerts_filter_timeframe.tsx @@ -49,7 +49,7 @@ const useDefaultTimezone = () => { const useTimeframe = (initialTimeframe?: AlertsFilterTimeframe) => { const timezone = useDefaultTimezone(); const DEFAULT_TIMEFRAME = { - days: [], + days: ISO_WEEKDAYS, timezone, hours: { start: '00:00', @@ -114,7 +114,9 @@ export const ActionAlertsFilterTimeframe: React.FC d !== day) : [...timeframe.days, day]; - updateTimeframe({ days: newDays }); + if (newDays.length !== 0) { + updateTimeframe({ days: newDays }); + } }, [timeframe, updateTimeframe] ); @@ -144,7 +146,7 @@ export const ActionAlertsFilterTimeframe: React.FC { From 95d2604a685024f4151f01e0d4d4c3e6a4e9d117 Mon Sep 17 00:00:00 2001 From: Julia Rechkunova Date: Wed, 26 Apr 2023 14:03:23 +0200 Subject: [PATCH 05/73] [Discover] Show "Temporary" badge for ad-hoc data views in Alerts flyout (#155717) ## Summary This PR fixes data views list in Alerts flyout by showing "Temporary" badge for ad-hoc data views in it. It was missing before. Screenshot 2023-04-25 at 16 04 25 --- .../data_view_select_popover.test.tsx | 52 ++++++++++++++++++- .../components/data_view_select_popover.tsx | 8 +-- 2 files changed, 56 insertions(+), 4 deletions(-) diff --git a/x-pack/plugins/stack_alerts/public/rule_types/components/data_view_select_popover.test.tsx b/x-pack/plugins/stack_alerts/public/rule_types/components/data_view_select_popover.test.tsx index 5812d61c7011f..78969e3e5dd21 100644 --- a/x-pack/plugins/stack_alerts/public/rule_types/components/data_view_select_popover.test.tsx +++ b/x-pack/plugins/stack_alerts/public/rule_types/components/data_view_select_popover.test.tsx @@ -12,6 +12,7 @@ import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public'; import { dataViewPluginMocks } from '@kbn/data-views-plugin/public/mocks'; import type { DataView } from '@kbn/data-views-plugin/public'; import { indexPatternEditorPluginMock as dataViewEditorPluginMock } from '@kbn/data-view-editor-plugin/public/mocks'; +import { DataViewSelector } from '@kbn/unified-search-plugin/public'; import { act } from 'react-dom/test-utils'; const selectedDataView = { @@ -29,7 +30,7 @@ const props: DataViewSelectPopoverProps = { dataView: selectedDataView, }; -const dataViewIds = ['mock-data-logs-id', 'mock-ecommerce-id', 'mock-test-id']; +const dataViewIds = ['mock-data-logs-id', 'mock-ecommerce-id', 'mock-test-id', 'mock-ad-hoc-id']; const dataViewOptions = [ selectedDataView, @@ -59,6 +60,15 @@ const dataViewOptions = [ isPersisted: jest.fn(() => true), getName: () => 'test', }, + { + id: 'mock-ad-hoc-id', + namespaces: ['default'], + title: 'ad-hoc data view', + typeMeta: {}, + isTimeBased: jest.fn(), + isPersisted: jest.fn(() => false), + getName: () => 'ad-hoc data view', + }, ]; const mount = () => { @@ -98,4 +108,44 @@ describe('DataViewSelectPopover', () => { const getIdsResult = await dataViewsMock.getIds.mock.results[0].value; expect(getIdsResult).toBe(dataViewIds); }); + + test('should open a popover on click', async () => { + const { wrapper } = mount(); + + await act(async () => { + await nextTick(); + wrapper.update(); + }); + + await wrapper.find('[data-test-subj="selectDataViewExpression"]').first().simulate('click'); + + expect(wrapper.find(DataViewSelector).prop('dataViewsList')).toMatchInlineSnapshot(` + Array [ + Object { + "id": "mock-data-logs-id", + "isAdhoc": false, + "name": undefined, + "title": "kibana_sample_data_logs", + }, + Object { + "id": "mock-ecommerce-id", + "isAdhoc": false, + "name": undefined, + "title": "kibana_sample_data_ecommerce", + }, + Object { + "id": "mock-test-id", + "isAdhoc": false, + "name": undefined, + "title": "test", + }, + Object { + "id": "mock-ad-hoc-id", + "isAdhoc": true, + "name": undefined, + "title": "ad-hoc data view", + }, + ] + `); + }); }); diff --git a/x-pack/plugins/stack_alerts/public/rule_types/components/data_view_select_popover.tsx b/x-pack/plugins/stack_alerts/public/rule_types/components/data_view_select_popover.tsx index 7180db27afdc6..ac71501dd5c34 100644 --- a/x-pack/plugins/stack_alerts/public/rule_types/components/data_view_select_popover.tsx +++ b/x-pack/plugins/stack_alerts/public/rule_types/components/data_view_select_popover.tsx @@ -20,8 +20,9 @@ import { EuiText, useEuiPaddingCSS, } from '@elastic/eui'; -import type { DataViewListItem, DataView } from '@kbn/data-views-plugin/public'; +import type { DataView } from '@kbn/data-views-plugin/public'; import { DataViewSelector } from '@kbn/unified-search-plugin/public'; +import type { DataViewListItemEnhanced } from '@kbn/unified-search-plugin/public/dataview_picker/dataview_list'; import { useTriggerUiActionServices } from '../es_query/util'; import { EsQueryRuleMetaData } from '../es_query/types'; @@ -32,11 +33,12 @@ export interface DataViewSelectPopoverProps { onChangeMetaData: (metadata: EsQueryRuleMetaData) => void; } -const toDataViewListItem = (dataView: DataView): DataViewListItem => { +const toDataViewListItem = (dataView: DataView): DataViewListItemEnhanced => { return { id: dataView.id!, title: dataView.title, name: dataView.name, + isAdhoc: !dataView.isPersisted(), }; }; @@ -47,7 +49,7 @@ export const DataViewSelectPopover: React.FunctionComponent { const { dataViews, dataViewEditor } = useTriggerUiActionServices(); - const [dataViewItems, setDataViewsItems] = useState([]); + const [dataViewItems, setDataViewsItems] = useState([]); const [dataViewPopoverOpen, setDataViewPopoverOpen] = useState(false); const closeDataViewEditor = useRef<() => void | undefined>(); From 202f13f7be8b78caf2025233d0f6d3ff437c4ff9 Mon Sep 17 00:00:00 2001 From: Sergi Massaneda Date: Wed, 26 Apr 2023 14:05:36 +0200 Subject: [PATCH 06/73] [SecuritySolution] Refactor security packages (#155365) ## Summary closes: https://github.com/elastic/kibana/issues/155301 Moves _packages/security-solution/_ to _x-pack/packages/security-solution/_ Moves _x-pack/packages/kbn-securitysolution-*_ into the new _x-pack/packages/security-solution/_ It contains 3 packages now: - data_view/ - ecs_data_quality_dashboard/ - side_nav/ Package names and ids have not changed. ## Other - eslint configured for all the packages in the directory - i18n prefix `securitySolutionPackages` configured for all packages in the directory - generic storybook configuration, run with: `yarn storybook security_solution_packages` --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> --- .eslintrc.js | 10 +- .github/CODEOWNERS | 8 +- .i18nrc.json | 4 +- package.json | 8 +- .../styled_components_files.js | 3 +- .../security-solution/side_nav/jest.config.js | 13 - .../storybook/config/index.ts | 9 - src/dev/storybook/aliases.ts | 3 +- tsconfig.base.json | 16 +- .../body/data_quality_details/translations.ts | 22 - .../errors_viewer/translations.ts | 20 - .../index_properties/translations.ts | 406 ---------------- .../pattern/pattern_summary/translations.ts | 29 -- .../pattern/translations.ts | 34 -- .../stat_label/translations.ts | 132 ----- .../storage_treemap/translations.ts | 20 - .../summary_table/translations.ts | 86 ---- .../tabs/incompatible_tab/translations.ts | 20 - .../ilm_phases_empty_prompt/translations.ts | 62 --- .../impl/data_quality/translations.ts | 240 ---------- .../use_add_to_new_case/translations.ts | 25 - .../jest.config.js | 24 - .../packages}/security-solution/README.mdx | 0 .../data_table}/README.md | 0 .../data_table}/common/constants.ts | 0 .../common/types/data_table/index.ts | 0 .../data_table}/common/types/detail_panel.ts | 0 .../common/types/header_actions/index.ts | 0 .../data_table}/common/types/index.ts | 0 .../data_table}/common/types/risk_scores.ts | 0 .../common/types/session_view/index.ts | 0 .../column_headers/default_headers.ts | 0 .../column_headers/helpers.test.tsx | 0 .../data_table/column_headers/helpers.tsx | 13 +- .../data_table/column_headers/translations.ts | 2 +- .../components/data_table/constants.ts | 0 .../data_table/data_table.stories.tsx | 14 +- .../components/data_table/helpers.test.tsx | 0 .../components/data_table/helpers.tsx | 0 .../components/data_table/index.test.tsx | 0 .../components/data_table/index.tsx | 2 +- .../components/data_table/pagination.ts | 0 .../components/data_table/types.ts | 0 .../components/data_table/utils.ts | 0 .../components/toolbar/bulk_actions/types.ts | 0 .../components/toolbar/unit/index.ts | 0 .../components/toolbar/unit/styles.tsx | 0 .../components/toolbar/unit/translations.ts | 2 +- .../data_table}/hooks/use_selector.tsx | 0 .../data_table}/index.ts | 0 .../data_table}/jest.config.js | 4 +- .../data_table}/kibana.jsonc | 0 .../demo_data/endpoint/library_load_event.ts | 0 ...cess_execution_malware_prevention_alert.ts | 0 .../endpoint/registry_modification_event.ts | 0 .../data_table}/mock/demo_data/timeline.ts | 0 .../data_table}/mock/global_state.ts | 2 +- .../data_table}/mock/header.ts | 0 .../data_table}/mock/mock_local_storage.ts | 0 .../data_table}/mock/mock_source.ts | 0 .../data_table}/mock/mock_timeline_data.ts | 0 .../data_table}/mock/test_providers.tsx | 1 + .../data_table}/package.json | 0 .../data_table}/store/data_table/actions.ts | 0 .../data_table}/store/data_table/defaults.ts | 0 .../store/data_table/helpers.test.tsx | 7 +- .../data_table}/store/data_table/helpers.ts | 0 .../data_table}/store/data_table/index.ts | 0 .../data_table}/store/data_table/inputs.ts | 0 .../data_table}/store/data_table/model.ts | 8 +- .../data_table}/store/data_table/reducer.ts | 0 .../data_table}/store/data_table/selectors.ts | 0 .../store/data_table/translations.ts | 4 +- .../data_table}/store/data_table/types.ts | 0 .../data_table}/tsconfig.json | 2 +- .../data_table}/utils/use_mount_appended.ts | 4 +- .../ecs_data_quality_dashboard}/README.md | 0 .../ecs_allowed_values/index.test.tsx | 0 .../ecs_allowed_values/index.tsx | 0 .../get_common_table_columns/index.test.tsx | 0 .../get_common_table_columns/index.tsx | 0 .../index.test.tsx | 0 .../index.tsx | 0 .../compare_fields_table/helpers.test.tsx | 0 .../compare_fields_table/helpers.tsx | 0 .../compare_fields_table/index.test.tsx | 0 .../compare_fields_table/index.tsx | 0 .../index_invalid_values/index.test.tsx | 0 .../index_invalid_values/index.tsx | 0 .../compare_fields_table/translations.ts | 27 +- .../allowed_values/helpers.test.tsx | 0 .../allowed_values/helpers.tsx | 0 .../body/data_quality_details/index.test.tsx | 0 .../body/data_quality_details/index.tsx | 0 .../indices_details/index.test.tsx | 0 .../indices_details/index.tsx | 0 .../storage_details/helpers.test.ts | 0 .../storage_details/helpers.ts | 0 .../storage_details/index.test.tsx | 0 .../storage_details/index.tsx | 0 .../data_quality_panel/body/index.test.tsx | 0 .../data_quality_panel/body/index.tsx | 0 .../check_status/index.test.tsx | 0 .../check_status/index.tsx | 0 .../errors_popover/index.test.tsx | 0 .../errors_popover/index.tsx | 0 .../errors_popover/translations.ts | 45 +- .../errors_viewer/helpers.test.tsx | 0 .../errors_viewer/helpers.tsx | 0 .../errors_viewer/index.test.tsx | 0 .../errors_viewer/index.tsx | 0 .../errors_viewer/translations.ts | 29 ++ .../data_quality_summary/index.test.tsx | 0 .../data_quality_summary/index.tsx | 0 .../summary_actions/actions/index.test.tsx | 0 .../summary_actions/actions/index.tsx | 0 .../check_all/check_index.test.ts | 0 .../summary_actions/check_all/check_index.ts | 0 .../summary_actions/check_all/helpers.test.ts | 0 .../summary_actions/check_all/helpers.ts | 0 .../summary_actions/check_all/index.test.tsx | 0 .../summary_actions/check_all/index.tsx | 0 .../summary_actions/check_all/translations.ts | 11 +- .../summary_actions/index.test.tsx | 0 .../summary_actions/index.tsx | 0 .../error_empty_prompt/index.test.tsx | 0 .../error_empty_prompt/index.tsx | 0 .../ilm_phase_counts/index.test.tsx | 0 .../ilm_phase_counts/index.tsx | 0 .../empty_prompt_body.test.tsx | 0 .../index_properties/empty_prompt_body.tsx | 0 .../empty_prompt_title.test.tsx | 0 .../index_properties/empty_prompt_title.tsx | 0 .../index_properties/helpers.test.ts | 0 .../index_properties/helpers.ts | 0 .../index_properties/index.test.tsx | 0 .../index_properties/index.tsx | 0 .../index_properties/markdown/helpers.test.ts | 0 .../index_properties/markdown/helpers.ts | 0 .../index_properties/translations.ts | 451 ++++++++++++++++++ .../loading_empty_prompt/index.tsx | 0 .../pattern/helpers.test.ts | 0 .../data_quality_panel/pattern/helpers.ts | 0 .../data_quality_panel/pattern/index.test.tsx | 0 .../data_quality_panel/pattern/index.tsx | 0 .../pattern/pattern_summary/index.tsx | 0 .../pattern_label/helpers.test.ts | 0 .../pattern_summary/pattern_label/helpers.ts | 0 .../pattern_summary/pattern_label/index.tsx | 0 .../pattern_label/translations.ts | 13 +- .../pattern_summary/stats_rollup/index.tsx | 0 .../pattern/pattern_summary/translations.ts | 29 ++ .../pattern/translations.ts | 24 + .../remote_clusters_callout/index.test.tsx | 0 .../remote_clusters_callout/index.tsx | 0 .../remote_clusters_callout/translations.ts | 11 +- .../same_family/index.test.tsx | 0 .../data_quality_panel/same_family/index.tsx | 0 .../same_family/translations.ts | 9 +- .../data_quality_panel/stat_label/index.tsx | 0 .../stat_label/translations.ts | 174 +++++++ .../storage_treemap/index.test.tsx | 0 .../storage_treemap/index.tsx | 0 .../storage_treemap/no_data/index.test.tsx | 0 .../storage_treemap/no_data/index.tsx | 0 .../storage_treemap/translations.ts | 26 + .../summary_table/helpers.test.tsx | 0 .../summary_table/helpers.tsx | 0 .../summary_table/index.test.tsx | 0 .../summary_table/index.tsx | 0 .../summary_table/translations.ts | 119 +++++ .../data_quality_panel/tabs/all_tab/index.tsx | 0 .../callouts/custom_callout/index.test.tsx | 0 .../tabs/callouts/custom_callout/index.tsx | 0 .../incompatible_callout/helpers.test.ts | 0 .../callouts/incompatible_callout/helpers.ts | 0 .../incompatible_callout/index.test.tsx | 0 .../callouts/incompatible_callout/index.tsx | 0 .../missing_timestamp_callout/index.tsx | 0 .../tabs/custom_tab/helpers.test.ts | 0 .../tabs/custom_tab/helpers.ts | 0 .../tabs/custom_tab/index.tsx | 0 .../tabs/ecs_compliant_tab/index.tsx | 0 .../data_quality_panel/tabs/helpers.test.tsx | 0 .../data_quality_panel/tabs/helpers.tsx | 0 .../tabs/incompatible_tab/helpers.test.ts | 0 .../tabs/incompatible_tab/helpers.ts | 0 .../tabs/incompatible_tab/index.tsx | 0 .../tabs/incompatible_tab/translations.ts | 26 + .../data_quality_panel/tabs/styles.tsx | 0 .../summary_tab/callout_summary/index.tsx | 0 .../tabs/summary_tab/helpers.test.ts | 0 .../tabs/summary_tab/helpers.ts | 0 .../tabs/summary_tab/index.tsx | 0 .../chart_legend/chart_legend_item.tsx | 0 .../chart_legend/index.tsx | 0 .../ecs_summary_donut_chart/helpers.test.ts | 0 .../ecs_summary_donut_chart/helpers.ts | 0 .../ecs_summary_donut_chart/index.tsx | 0 .../ecs_summary_donut_chart/translations.ts | 11 +- .../impl/data_quality/helpers.test.ts | 0 .../impl/data_quality/helpers.ts | 0 .../ilm_phases_empty_prompt/index.test.tsx | 0 .../ilm_phases_empty_prompt/index.tsx | 0 .../ilm_phases_empty_prompt/translations.ts | 80 ++++ .../impl/data_quality/index.test.tsx | 0 .../impl/data_quality/index.tsx | 0 .../allowed_values/mock_allowed_values.ts | 0 .../data_quality_check_result/mock_index.tsx | 0 .../mock_enriched_field_metadata.ts | 0 .../mock/ilm_explain/mock_ilm_explain.ts | 0 ...ndices_get_mapping_index_mapping_record.ts | 0 .../mock_mappings_properties.ts | 0 .../mock_mappings_response.ts | 0 .../mock_partitioned_field_metadata.tsx | 0 .../mock_alerts_pattern_rollup.ts | 0 .../mock_auditbeat_pattern_rollup.ts | 0 .../mock_packetbeat_pattern_rollup.ts | 0 .../data_quality/mock/stats/mock_stats.tsx | 0 .../mock/stats/mock_stats_green_index.ts | 0 .../mock/stats/mock_stats_yellow_index.tsx | 0 .../mock/test_providers/test_providers.tsx | 0 .../unallowed_values/mock_unallowed_values.ts | 0 .../impl/data_quality/styles.tsx | 0 .../impl/data_quality/translations.ts | 273 +++++++++++ .../impl/data_quality/types.ts | 0 .../use_add_to_new_case/index.tsx | 0 .../use_add_to_new_case/translations.ts | 31 ++ .../data_quality/use_ilm_explain/index.tsx | 0 .../data_quality/use_mappings/helpers.test.ts | 0 .../impl/data_quality/use_mappings/helpers.ts | 0 .../impl/data_quality/use_mappings/index.tsx | 0 .../use_results_rollup/helpers.test.ts | 0 .../use_results_rollup/helpers.ts | 0 .../data_quality/use_results_rollup/index.tsx | 0 .../impl/data_quality/use_stats/index.tsx | 0 .../use_unallowed_values/helpers.test.ts | 0 .../use_unallowed_values/helpers.ts | 0 .../use_unallowed_values/index.ts | 0 .../ecs_data_quality_dashboard}/index.ts | 0 .../ecs_data_quality_dashboard/jest.config.js | 26 + .../ecs_data_quality_dashboard}/kibana.jsonc | 0 .../ecs_data_quality_dashboard}/package.json | 0 .../setup_tests.ts | 0 .../ecs_data_quality_dashboard}/tsconfig.json | 2 +- .../security-solution/side_nav/README.mdx | 0 .../security-solution/side_nav/index.ts | 5 +- .../security-solution/side_nav/jest.config.js | 12 + .../security-solution/side_nav/kibana.jsonc | 0 .../security-solution/side_nav/package.json | 2 +- .../side_nav/src/beta_badge.tsx | 5 +- .../security-solution/side_nav/src/index.tsx | 5 +- .../src/solution_side_nav.stories.tsx | 6 +- .../side_nav/src/solution_side_nav.styles.ts | 5 +- .../side_nav/src/solution_side_nav.test.tsx | 5 +- .../side_nav/src/solution_side_nav.tsx | 210 ++++---- .../src/solution_side_nav_panel.styles.ts | 5 +- .../src/solution_side_nav_panel.test.tsx | 8 +- .../side_nav/src/solution_side_nav_panel.tsx | 19 +- .../side_nav/src/telemetry/const.ts | 5 +- .../src/telemetry/telemetry_context.tsx | 5 +- .../security-solution/side_nav/src/types.ts | 5 +- .../security-solution/side_nav/tsconfig.json | 2 +- .../storybook/config/README.mdx | 0 .../storybook/config/constants.ts | 5 +- .../storybook/config/index.ts} | 2 +- .../storybook/config/kibana.jsonc | 0 .../storybook/config/main.ts | 5 +- .../storybook/config/manager.ts | 5 +- .../storybook/config/package.json | 2 +- .../storybook/config/preview.ts | 5 +- .../storybook/config/tsconfig.json | 2 +- .../translations/translations/fr-FR.json | 171 ------- .../translations/translations/ja-JP.json | 171 ------- .../translations/translations/zh-CN.json | 171 ------- yarn.lock | 8 +- 276 files changed, 1601 insertions(+), 1926 deletions(-) delete mode 100644 packages/security-solution/side_nav/jest.config.js delete mode 100755 packages/security-solution/storybook/config/index.ts delete mode 100644 x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/body/data_quality_details/translations.ts delete mode 100644 x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/data_quality_summary/errors_viewer/translations.ts delete mode 100644 x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/index_properties/translations.ts delete mode 100644 x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/pattern/pattern_summary/translations.ts delete mode 100644 x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/pattern/translations.ts delete mode 100644 x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/stat_label/translations.ts delete mode 100644 x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/storage_treemap/translations.ts delete mode 100644 x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/summary_table/translations.ts delete mode 100644 x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/tabs/incompatible_tab/translations.ts delete mode 100644 x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/ilm_phases_empty_prompt/translations.ts delete mode 100644 x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/translations.ts delete mode 100644 x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/use_add_to_new_case/translations.ts delete mode 100644 x-pack/packages/kbn-ecs-data-quality-dashboard/jest.config.js rename {packages => x-pack/packages}/security-solution/README.mdx (100%) rename x-pack/packages/{kbn-securitysolution-data-table => security-solution/data_table}/README.md (100%) rename x-pack/packages/{kbn-securitysolution-data-table => security-solution/data_table}/common/constants.ts (100%) rename x-pack/packages/{kbn-securitysolution-data-table => security-solution/data_table}/common/types/data_table/index.ts (100%) rename x-pack/packages/{kbn-securitysolution-data-table => security-solution/data_table}/common/types/detail_panel.ts (100%) rename x-pack/packages/{kbn-securitysolution-data-table => security-solution/data_table}/common/types/header_actions/index.ts (100%) rename x-pack/packages/{kbn-securitysolution-data-table => security-solution/data_table}/common/types/index.ts (100%) rename x-pack/packages/{kbn-securitysolution-data-table => security-solution/data_table}/common/types/risk_scores.ts (100%) rename x-pack/packages/{kbn-securitysolution-data-table => security-solution/data_table}/common/types/session_view/index.ts (100%) rename x-pack/packages/{kbn-securitysolution-data-table => security-solution/data_table}/components/data_table/column_headers/default_headers.ts (100%) rename x-pack/packages/{kbn-securitysolution-data-table => security-solution/data_table}/components/data_table/column_headers/helpers.test.tsx (100%) rename x-pack/packages/{kbn-securitysolution-data-table => security-solution/data_table}/components/data_table/column_headers/helpers.tsx (95%) rename x-pack/packages/{kbn-securitysolution-data-table => security-solution/data_table}/components/data_table/column_headers/translations.ts (80%) rename x-pack/packages/{kbn-securitysolution-data-table => security-solution/data_table}/components/data_table/constants.ts (100%) rename x-pack/packages/{kbn-securitysolution-data-table => security-solution/data_table}/components/data_table/data_table.stories.tsx (93%) rename x-pack/packages/{kbn-securitysolution-data-table => security-solution/data_table}/components/data_table/helpers.test.tsx (100%) rename x-pack/packages/{kbn-securitysolution-data-table => security-solution/data_table}/components/data_table/helpers.tsx (100%) rename x-pack/packages/{kbn-securitysolution-data-table => security-solution/data_table}/components/data_table/index.test.tsx (100%) rename x-pack/packages/{kbn-securitysolution-data-table => security-solution/data_table}/components/data_table/index.tsx (99%) rename x-pack/packages/{kbn-securitysolution-data-table => security-solution/data_table}/components/data_table/pagination.ts (100%) rename x-pack/packages/{kbn-securitysolution-data-table => security-solution/data_table}/components/data_table/types.ts (100%) rename x-pack/packages/{kbn-securitysolution-data-table => security-solution/data_table}/components/data_table/utils.ts (100%) rename x-pack/packages/{kbn-securitysolution-data-table => security-solution/data_table}/components/toolbar/bulk_actions/types.ts (100%) rename x-pack/packages/{kbn-securitysolution-data-table => security-solution/data_table}/components/toolbar/unit/index.ts (100%) rename x-pack/packages/{kbn-securitysolution-data-table => security-solution/data_table}/components/toolbar/unit/styles.tsx (100%) rename x-pack/packages/{kbn-securitysolution-data-table => security-solution/data_table}/components/toolbar/unit/translations.ts (86%) rename x-pack/packages/{kbn-securitysolution-data-table => security-solution/data_table}/hooks/use_selector.tsx (100%) rename x-pack/packages/{kbn-securitysolution-data-table => security-solution/data_table}/index.ts (100%) rename x-pack/packages/{kbn-securitysolution-data-table => security-solution/data_table}/jest.config.js (75%) rename x-pack/packages/{kbn-securitysolution-data-table => security-solution/data_table}/kibana.jsonc (100%) rename x-pack/packages/{kbn-securitysolution-data-table => security-solution/data_table}/mock/demo_data/endpoint/library_load_event.ts (100%) rename x-pack/packages/{kbn-securitysolution-data-table => security-solution/data_table}/mock/demo_data/endpoint/process_execution_malware_prevention_alert.ts (100%) rename x-pack/packages/{kbn-securitysolution-data-table => security-solution/data_table}/mock/demo_data/endpoint/registry_modification_event.ts (100%) rename x-pack/packages/{kbn-securitysolution-data-table => security-solution/data_table}/mock/demo_data/timeline.ts (100%) rename x-pack/packages/{kbn-securitysolution-data-table => security-solution/data_table}/mock/global_state.ts (97%) rename x-pack/packages/{kbn-securitysolution-data-table => security-solution/data_table}/mock/header.ts (100%) rename x-pack/packages/{kbn-securitysolution-data-table => security-solution/data_table}/mock/mock_local_storage.ts (100%) rename x-pack/packages/{kbn-securitysolution-data-table => security-solution/data_table}/mock/mock_source.ts (100%) rename x-pack/packages/{kbn-securitysolution-data-table => security-solution/data_table}/mock/mock_timeline_data.ts (100%) rename x-pack/packages/{kbn-securitysolution-data-table => security-solution/data_table}/mock/test_providers.tsx (97%) rename x-pack/packages/{kbn-securitysolution-data-table => security-solution/data_table}/package.json (100%) rename x-pack/packages/{kbn-securitysolution-data-table => security-solution/data_table}/store/data_table/actions.ts (100%) rename x-pack/packages/{kbn-securitysolution-data-table => security-solution/data_table}/store/data_table/defaults.ts (100%) rename x-pack/packages/{kbn-securitysolution-data-table => security-solution/data_table}/store/data_table/helpers.test.tsx (95%) rename x-pack/packages/{kbn-securitysolution-data-table => security-solution/data_table}/store/data_table/helpers.ts (100%) rename x-pack/packages/{kbn-securitysolution-data-table => security-solution/data_table}/store/data_table/index.ts (100%) rename x-pack/packages/{kbn-securitysolution-data-table => security-solution/data_table}/store/data_table/inputs.ts (100%) rename x-pack/packages/{kbn-securitysolution-data-table => security-solution/data_table}/store/data_table/model.ts (95%) rename x-pack/packages/{kbn-securitysolution-data-table => security-solution/data_table}/store/data_table/reducer.ts (100%) rename x-pack/packages/{kbn-securitysolution-data-table => security-solution/data_table}/store/data_table/selectors.ts (100%) rename x-pack/packages/{kbn-securitysolution-data-table => security-solution/data_table}/store/data_table/translations.ts (80%) rename x-pack/packages/{kbn-securitysolution-data-table => security-solution/data_table}/store/data_table/types.ts (100%) rename x-pack/packages/{kbn-securitysolution-data-table => security-solution/data_table}/tsconfig.json (92%) rename x-pack/packages/{kbn-securitysolution-data-table => security-solution/data_table}/utils/use_mount_appended.ts (87%) rename x-pack/packages/{kbn-ecs-data-quality-dashboard => security-solution/ecs_data_quality_dashboard}/README.md (100%) rename x-pack/packages/{kbn-ecs-data-quality-dashboard => security-solution/ecs_data_quality_dashboard}/impl/data_quality/compare_fields_table/ecs_allowed_values/index.test.tsx (100%) rename x-pack/packages/{kbn-ecs-data-quality-dashboard => security-solution/ecs_data_quality_dashboard}/impl/data_quality/compare_fields_table/ecs_allowed_values/index.tsx (100%) rename x-pack/packages/{kbn-ecs-data-quality-dashboard => security-solution/ecs_data_quality_dashboard}/impl/data_quality/compare_fields_table/get_common_table_columns/index.test.tsx (100%) rename x-pack/packages/{kbn-ecs-data-quality-dashboard => security-solution/ecs_data_quality_dashboard}/impl/data_quality/compare_fields_table/get_common_table_columns/index.tsx (100%) rename x-pack/packages/{kbn-ecs-data-quality-dashboard => security-solution/ecs_data_quality_dashboard}/impl/data_quality/compare_fields_table/get_incompatible_mappings_table_columns/index.test.tsx (100%) rename x-pack/packages/{kbn-ecs-data-quality-dashboard => security-solution/ecs_data_quality_dashboard}/impl/data_quality/compare_fields_table/get_incompatible_mappings_table_columns/index.tsx (100%) rename x-pack/packages/{kbn-ecs-data-quality-dashboard => security-solution/ecs_data_quality_dashboard}/impl/data_quality/compare_fields_table/helpers.test.tsx (100%) rename x-pack/packages/{kbn-ecs-data-quality-dashboard => security-solution/ecs_data_quality_dashboard}/impl/data_quality/compare_fields_table/helpers.tsx (100%) rename x-pack/packages/{kbn-ecs-data-quality-dashboard => security-solution/ecs_data_quality_dashboard}/impl/data_quality/compare_fields_table/index.test.tsx (100%) rename x-pack/packages/{kbn-ecs-data-quality-dashboard => security-solution/ecs_data_quality_dashboard}/impl/data_quality/compare_fields_table/index.tsx (100%) rename x-pack/packages/{kbn-ecs-data-quality-dashboard => security-solution/ecs_data_quality_dashboard}/impl/data_quality/compare_fields_table/index_invalid_values/index.test.tsx (100%) rename x-pack/packages/{kbn-ecs-data-quality-dashboard => security-solution/ecs_data_quality_dashboard}/impl/data_quality/compare_fields_table/index_invalid_values/index.tsx (100%) rename x-pack/packages/{kbn-ecs-data-quality-dashboard => security-solution/ecs_data_quality_dashboard}/impl/data_quality/compare_fields_table/translations.ts (54%) rename x-pack/packages/{kbn-ecs-data-quality-dashboard => security-solution/ecs_data_quality_dashboard}/impl/data_quality/data_quality_panel/allowed_values/helpers.test.tsx (100%) rename x-pack/packages/{kbn-ecs-data-quality-dashboard => security-solution/ecs_data_quality_dashboard}/impl/data_quality/data_quality_panel/allowed_values/helpers.tsx (100%) rename x-pack/packages/{kbn-ecs-data-quality-dashboard => security-solution/ecs_data_quality_dashboard}/impl/data_quality/data_quality_panel/body/data_quality_details/index.test.tsx (100%) rename x-pack/packages/{kbn-ecs-data-quality-dashboard => security-solution/ecs_data_quality_dashboard}/impl/data_quality/data_quality_panel/body/data_quality_details/index.tsx (100%) rename x-pack/packages/{kbn-ecs-data-quality-dashboard => security-solution/ecs_data_quality_dashboard}/impl/data_quality/data_quality_panel/body/data_quality_details/indices_details/index.test.tsx (100%) rename x-pack/packages/{kbn-ecs-data-quality-dashboard => security-solution/ecs_data_quality_dashboard}/impl/data_quality/data_quality_panel/body/data_quality_details/indices_details/index.tsx (100%) rename x-pack/packages/{kbn-ecs-data-quality-dashboard => security-solution/ecs_data_quality_dashboard}/impl/data_quality/data_quality_panel/body/data_quality_details/storage_details/helpers.test.ts (100%) rename x-pack/packages/{kbn-ecs-data-quality-dashboard => security-solution/ecs_data_quality_dashboard}/impl/data_quality/data_quality_panel/body/data_quality_details/storage_details/helpers.ts (100%) rename x-pack/packages/{kbn-ecs-data-quality-dashboard => security-solution/ecs_data_quality_dashboard}/impl/data_quality/data_quality_panel/body/data_quality_details/storage_details/index.test.tsx (100%) rename x-pack/packages/{kbn-ecs-data-quality-dashboard => security-solution/ecs_data_quality_dashboard}/impl/data_quality/data_quality_panel/body/data_quality_details/storage_details/index.tsx (100%) rename x-pack/packages/{kbn-ecs-data-quality-dashboard => security-solution/ecs_data_quality_dashboard}/impl/data_quality/data_quality_panel/body/index.test.tsx (100%) rename x-pack/packages/{kbn-ecs-data-quality-dashboard => security-solution/ecs_data_quality_dashboard}/impl/data_quality/data_quality_panel/body/index.tsx (100%) rename x-pack/packages/{kbn-ecs-data-quality-dashboard => security-solution/ecs_data_quality_dashboard}/impl/data_quality/data_quality_panel/data_quality_summary/check_status/index.test.tsx (100%) rename x-pack/packages/{kbn-ecs-data-quality-dashboard => security-solution/ecs_data_quality_dashboard}/impl/data_quality/data_quality_panel/data_quality_summary/check_status/index.tsx (100%) rename x-pack/packages/{kbn-ecs-data-quality-dashboard => security-solution/ecs_data_quality_dashboard}/impl/data_quality/data_quality_panel/data_quality_summary/errors_popover/index.test.tsx (100%) rename x-pack/packages/{kbn-ecs-data-quality-dashboard => security-solution/ecs_data_quality_dashboard}/impl/data_quality/data_quality_panel/data_quality_summary/errors_popover/index.tsx (100%) rename x-pack/packages/{kbn-ecs-data-quality-dashboard => security-solution/ecs_data_quality_dashboard}/impl/data_quality/data_quality_panel/data_quality_summary/errors_popover/translations.ts (50%) rename x-pack/packages/{kbn-ecs-data-quality-dashboard => security-solution/ecs_data_quality_dashboard}/impl/data_quality/data_quality_panel/data_quality_summary/errors_viewer/helpers.test.tsx (100%) rename x-pack/packages/{kbn-ecs-data-quality-dashboard => security-solution/ecs_data_quality_dashboard}/impl/data_quality/data_quality_panel/data_quality_summary/errors_viewer/helpers.tsx (100%) rename x-pack/packages/{kbn-ecs-data-quality-dashboard => security-solution/ecs_data_quality_dashboard}/impl/data_quality/data_quality_panel/data_quality_summary/errors_viewer/index.test.tsx (100%) rename x-pack/packages/{kbn-ecs-data-quality-dashboard => security-solution/ecs_data_quality_dashboard}/impl/data_quality/data_quality_panel/data_quality_summary/errors_viewer/index.tsx (100%) create mode 100644 x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/data_quality_summary/errors_viewer/translations.ts rename x-pack/packages/{kbn-ecs-data-quality-dashboard => security-solution/ecs_data_quality_dashboard}/impl/data_quality/data_quality_panel/data_quality_summary/index.test.tsx (100%) rename x-pack/packages/{kbn-ecs-data-quality-dashboard => security-solution/ecs_data_quality_dashboard}/impl/data_quality/data_quality_panel/data_quality_summary/index.tsx (100%) rename x-pack/packages/{kbn-ecs-data-quality-dashboard => security-solution/ecs_data_quality_dashboard}/impl/data_quality/data_quality_panel/data_quality_summary/summary_actions/actions/index.test.tsx (100%) rename x-pack/packages/{kbn-ecs-data-quality-dashboard => security-solution/ecs_data_quality_dashboard}/impl/data_quality/data_quality_panel/data_quality_summary/summary_actions/actions/index.tsx (100%) rename x-pack/packages/{kbn-ecs-data-quality-dashboard => security-solution/ecs_data_quality_dashboard}/impl/data_quality/data_quality_panel/data_quality_summary/summary_actions/check_all/check_index.test.ts (100%) rename x-pack/packages/{kbn-ecs-data-quality-dashboard => security-solution/ecs_data_quality_dashboard}/impl/data_quality/data_quality_panel/data_quality_summary/summary_actions/check_all/check_index.ts (100%) rename x-pack/packages/{kbn-ecs-data-quality-dashboard => security-solution/ecs_data_quality_dashboard}/impl/data_quality/data_quality_panel/data_quality_summary/summary_actions/check_all/helpers.test.ts (100%) rename x-pack/packages/{kbn-ecs-data-quality-dashboard => security-solution/ecs_data_quality_dashboard}/impl/data_quality/data_quality_panel/data_quality_summary/summary_actions/check_all/helpers.ts (100%) rename x-pack/packages/{kbn-ecs-data-quality-dashboard => security-solution/ecs_data_quality_dashboard}/impl/data_quality/data_quality_panel/data_quality_summary/summary_actions/check_all/index.test.tsx (100%) rename x-pack/packages/{kbn-ecs-data-quality-dashboard => security-solution/ecs_data_quality_dashboard}/impl/data_quality/data_quality_panel/data_quality_summary/summary_actions/check_all/index.tsx (100%) rename x-pack/packages/{kbn-ecs-data-quality-dashboard => security-solution/ecs_data_quality_dashboard}/impl/data_quality/data_quality_panel/data_quality_summary/summary_actions/check_all/translations.ts (61%) rename x-pack/packages/{kbn-ecs-data-quality-dashboard => security-solution/ecs_data_quality_dashboard}/impl/data_quality/data_quality_panel/data_quality_summary/summary_actions/index.test.tsx (100%) rename x-pack/packages/{kbn-ecs-data-quality-dashboard => security-solution/ecs_data_quality_dashboard}/impl/data_quality/data_quality_panel/data_quality_summary/summary_actions/index.tsx (100%) rename x-pack/packages/{kbn-ecs-data-quality-dashboard => security-solution/ecs_data_quality_dashboard}/impl/data_quality/data_quality_panel/error_empty_prompt/index.test.tsx (100%) rename x-pack/packages/{kbn-ecs-data-quality-dashboard => security-solution/ecs_data_quality_dashboard}/impl/data_quality/data_quality_panel/error_empty_prompt/index.tsx (100%) rename x-pack/packages/{kbn-ecs-data-quality-dashboard => security-solution/ecs_data_quality_dashboard}/impl/data_quality/data_quality_panel/ilm_phase_counts/index.test.tsx (100%) rename x-pack/packages/{kbn-ecs-data-quality-dashboard => security-solution/ecs_data_quality_dashboard}/impl/data_quality/data_quality_panel/ilm_phase_counts/index.tsx (100%) rename x-pack/packages/{kbn-ecs-data-quality-dashboard => security-solution/ecs_data_quality_dashboard}/impl/data_quality/data_quality_panel/index_properties/empty_prompt_body.test.tsx (100%) rename x-pack/packages/{kbn-ecs-data-quality-dashboard => security-solution/ecs_data_quality_dashboard}/impl/data_quality/data_quality_panel/index_properties/empty_prompt_body.tsx (100%) rename x-pack/packages/{kbn-ecs-data-quality-dashboard => security-solution/ecs_data_quality_dashboard}/impl/data_quality/data_quality_panel/index_properties/empty_prompt_title.test.tsx (100%) rename x-pack/packages/{kbn-ecs-data-quality-dashboard => security-solution/ecs_data_quality_dashboard}/impl/data_quality/data_quality_panel/index_properties/empty_prompt_title.tsx (100%) rename x-pack/packages/{kbn-ecs-data-quality-dashboard => security-solution/ecs_data_quality_dashboard}/impl/data_quality/data_quality_panel/index_properties/helpers.test.ts (100%) rename x-pack/packages/{kbn-ecs-data-quality-dashboard => security-solution/ecs_data_quality_dashboard}/impl/data_quality/data_quality_panel/index_properties/helpers.ts (100%) rename x-pack/packages/{kbn-ecs-data-quality-dashboard => security-solution/ecs_data_quality_dashboard}/impl/data_quality/data_quality_panel/index_properties/index.test.tsx (100%) rename x-pack/packages/{kbn-ecs-data-quality-dashboard => security-solution/ecs_data_quality_dashboard}/impl/data_quality/data_quality_panel/index_properties/index.tsx (100%) rename x-pack/packages/{kbn-ecs-data-quality-dashboard => security-solution/ecs_data_quality_dashboard}/impl/data_quality/data_quality_panel/index_properties/markdown/helpers.test.ts (100%) rename x-pack/packages/{kbn-ecs-data-quality-dashboard => security-solution/ecs_data_quality_dashboard}/impl/data_quality/data_quality_panel/index_properties/markdown/helpers.ts (100%) create mode 100644 x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/index_properties/translations.ts rename x-pack/packages/{kbn-ecs-data-quality-dashboard => security-solution/ecs_data_quality_dashboard}/impl/data_quality/data_quality_panel/loading_empty_prompt/index.tsx (100%) rename x-pack/packages/{kbn-ecs-data-quality-dashboard => security-solution/ecs_data_quality_dashboard}/impl/data_quality/data_quality_panel/pattern/helpers.test.ts (100%) rename x-pack/packages/{kbn-ecs-data-quality-dashboard => security-solution/ecs_data_quality_dashboard}/impl/data_quality/data_quality_panel/pattern/helpers.ts (100%) rename x-pack/packages/{kbn-ecs-data-quality-dashboard => security-solution/ecs_data_quality_dashboard}/impl/data_quality/data_quality_panel/pattern/index.test.tsx (100%) rename x-pack/packages/{kbn-ecs-data-quality-dashboard => security-solution/ecs_data_quality_dashboard}/impl/data_quality/data_quality_panel/pattern/index.tsx (100%) rename x-pack/packages/{kbn-ecs-data-quality-dashboard => security-solution/ecs_data_quality_dashboard}/impl/data_quality/data_quality_panel/pattern/pattern_summary/index.tsx (100%) rename x-pack/packages/{kbn-ecs-data-quality-dashboard => security-solution/ecs_data_quality_dashboard}/impl/data_quality/data_quality_panel/pattern/pattern_summary/pattern_label/helpers.test.ts (100%) rename x-pack/packages/{kbn-ecs-data-quality-dashboard => security-solution/ecs_data_quality_dashboard}/impl/data_quality/data_quality_panel/pattern/pattern_summary/pattern_label/helpers.ts (100%) rename x-pack/packages/{kbn-ecs-data-quality-dashboard => security-solution/ecs_data_quality_dashboard}/impl/data_quality/data_quality_panel/pattern/pattern_summary/pattern_label/index.tsx (100%) rename x-pack/packages/{kbn-ecs-data-quality-dashboard => security-solution/ecs_data_quality_dashboard}/impl/data_quality/data_quality_panel/pattern/pattern_summary/pattern_label/translations.ts (59%) rename x-pack/packages/{kbn-ecs-data-quality-dashboard => security-solution/ecs_data_quality_dashboard}/impl/data_quality/data_quality_panel/pattern/pattern_summary/stats_rollup/index.tsx (100%) create mode 100644 x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/pattern/pattern_summary/translations.ts create mode 100644 x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/pattern/translations.ts rename x-pack/packages/{kbn-ecs-data-quality-dashboard => security-solution/ecs_data_quality_dashboard}/impl/data_quality/data_quality_panel/remote_clusters_callout/index.test.tsx (100%) rename x-pack/packages/{kbn-ecs-data-quality-dashboard => security-solution/ecs_data_quality_dashboard}/impl/data_quality/data_quality_panel/remote_clusters_callout/index.tsx (100%) rename x-pack/packages/{kbn-ecs-data-quality-dashboard => security-solution/ecs_data_quality_dashboard}/impl/data_quality/data_quality_panel/remote_clusters_callout/translations.ts (62%) rename x-pack/packages/{kbn-ecs-data-quality-dashboard => security-solution/ecs_data_quality_dashboard}/impl/data_quality/data_quality_panel/same_family/index.test.tsx (100%) rename x-pack/packages/{kbn-ecs-data-quality-dashboard => security-solution/ecs_data_quality_dashboard}/impl/data_quality/data_quality_panel/same_family/index.tsx (100%) rename x-pack/packages/{kbn-ecs-data-quality-dashboard => security-solution/ecs_data_quality_dashboard}/impl/data_quality/data_quality_panel/same_family/translations.ts (63%) rename x-pack/packages/{kbn-ecs-data-quality-dashboard => security-solution/ecs_data_quality_dashboard}/impl/data_quality/data_quality_panel/stat_label/index.tsx (100%) create mode 100644 x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/stat_label/translations.ts rename x-pack/packages/{kbn-ecs-data-quality-dashboard => security-solution/ecs_data_quality_dashboard}/impl/data_quality/data_quality_panel/storage_treemap/index.test.tsx (100%) rename x-pack/packages/{kbn-ecs-data-quality-dashboard => security-solution/ecs_data_quality_dashboard}/impl/data_quality/data_quality_panel/storage_treemap/index.tsx (100%) rename x-pack/packages/{kbn-ecs-data-quality-dashboard => security-solution/ecs_data_quality_dashboard}/impl/data_quality/data_quality_panel/storage_treemap/no_data/index.test.tsx (100%) rename x-pack/packages/{kbn-ecs-data-quality-dashboard => security-solution/ecs_data_quality_dashboard}/impl/data_quality/data_quality_panel/storage_treemap/no_data/index.tsx (100%) create mode 100644 x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/storage_treemap/translations.ts rename x-pack/packages/{kbn-ecs-data-quality-dashboard => security-solution/ecs_data_quality_dashboard}/impl/data_quality/data_quality_panel/summary_table/helpers.test.tsx (100%) rename x-pack/packages/{kbn-ecs-data-quality-dashboard => security-solution/ecs_data_quality_dashboard}/impl/data_quality/data_quality_panel/summary_table/helpers.tsx (100%) rename x-pack/packages/{kbn-ecs-data-quality-dashboard => security-solution/ecs_data_quality_dashboard}/impl/data_quality/data_quality_panel/summary_table/index.test.tsx (100%) rename x-pack/packages/{kbn-ecs-data-quality-dashboard => security-solution/ecs_data_quality_dashboard}/impl/data_quality/data_quality_panel/summary_table/index.tsx (100%) create mode 100644 x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/summary_table/translations.ts rename x-pack/packages/{kbn-ecs-data-quality-dashboard => security-solution/ecs_data_quality_dashboard}/impl/data_quality/data_quality_panel/tabs/all_tab/index.tsx (100%) rename x-pack/packages/{kbn-ecs-data-quality-dashboard => security-solution/ecs_data_quality_dashboard}/impl/data_quality/data_quality_panel/tabs/callouts/custom_callout/index.test.tsx (100%) rename x-pack/packages/{kbn-ecs-data-quality-dashboard => security-solution/ecs_data_quality_dashboard}/impl/data_quality/data_quality_panel/tabs/callouts/custom_callout/index.tsx (100%) rename x-pack/packages/{kbn-ecs-data-quality-dashboard => security-solution/ecs_data_quality_dashboard}/impl/data_quality/data_quality_panel/tabs/callouts/incompatible_callout/helpers.test.ts (100%) rename x-pack/packages/{kbn-ecs-data-quality-dashboard => security-solution/ecs_data_quality_dashboard}/impl/data_quality/data_quality_panel/tabs/callouts/incompatible_callout/helpers.ts (100%) rename x-pack/packages/{kbn-ecs-data-quality-dashboard => security-solution/ecs_data_quality_dashboard}/impl/data_quality/data_quality_panel/tabs/callouts/incompatible_callout/index.test.tsx (100%) rename x-pack/packages/{kbn-ecs-data-quality-dashboard => security-solution/ecs_data_quality_dashboard}/impl/data_quality/data_quality_panel/tabs/callouts/incompatible_callout/index.tsx (100%) rename x-pack/packages/{kbn-ecs-data-quality-dashboard => security-solution/ecs_data_quality_dashboard}/impl/data_quality/data_quality_panel/tabs/callouts/missing_timestamp_callout/index.tsx (100%) rename x-pack/packages/{kbn-ecs-data-quality-dashboard => security-solution/ecs_data_quality_dashboard}/impl/data_quality/data_quality_panel/tabs/custom_tab/helpers.test.ts (100%) rename x-pack/packages/{kbn-ecs-data-quality-dashboard => security-solution/ecs_data_quality_dashboard}/impl/data_quality/data_quality_panel/tabs/custom_tab/helpers.ts (100%) rename x-pack/packages/{kbn-ecs-data-quality-dashboard => security-solution/ecs_data_quality_dashboard}/impl/data_quality/data_quality_panel/tabs/custom_tab/index.tsx (100%) rename x-pack/packages/{kbn-ecs-data-quality-dashboard => security-solution/ecs_data_quality_dashboard}/impl/data_quality/data_quality_panel/tabs/ecs_compliant_tab/index.tsx (100%) rename x-pack/packages/{kbn-ecs-data-quality-dashboard => security-solution/ecs_data_quality_dashboard}/impl/data_quality/data_quality_panel/tabs/helpers.test.tsx (100%) rename x-pack/packages/{kbn-ecs-data-quality-dashboard => security-solution/ecs_data_quality_dashboard}/impl/data_quality/data_quality_panel/tabs/helpers.tsx (100%) rename x-pack/packages/{kbn-ecs-data-quality-dashboard => security-solution/ecs_data_quality_dashboard}/impl/data_quality/data_quality_panel/tabs/incompatible_tab/helpers.test.ts (100%) rename x-pack/packages/{kbn-ecs-data-quality-dashboard => security-solution/ecs_data_quality_dashboard}/impl/data_quality/data_quality_panel/tabs/incompatible_tab/helpers.ts (100%) rename x-pack/packages/{kbn-ecs-data-quality-dashboard => security-solution/ecs_data_quality_dashboard}/impl/data_quality/data_quality_panel/tabs/incompatible_tab/index.tsx (100%) create mode 100644 x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/tabs/incompatible_tab/translations.ts rename x-pack/packages/{kbn-ecs-data-quality-dashboard => security-solution/ecs_data_quality_dashboard}/impl/data_quality/data_quality_panel/tabs/styles.tsx (100%) rename x-pack/packages/{kbn-ecs-data-quality-dashboard => security-solution/ecs_data_quality_dashboard}/impl/data_quality/data_quality_panel/tabs/summary_tab/callout_summary/index.tsx (100%) rename x-pack/packages/{kbn-ecs-data-quality-dashboard => security-solution/ecs_data_quality_dashboard}/impl/data_quality/data_quality_panel/tabs/summary_tab/helpers.test.ts (100%) rename x-pack/packages/{kbn-ecs-data-quality-dashboard => security-solution/ecs_data_quality_dashboard}/impl/data_quality/data_quality_panel/tabs/summary_tab/helpers.ts (100%) rename x-pack/packages/{kbn-ecs-data-quality-dashboard => security-solution/ecs_data_quality_dashboard}/impl/data_quality/data_quality_panel/tabs/summary_tab/index.tsx (100%) rename x-pack/packages/{kbn-ecs-data-quality-dashboard => security-solution/ecs_data_quality_dashboard}/impl/data_quality/ecs_summary_donut_chart/chart_legend/chart_legend_item.tsx (100%) rename x-pack/packages/{kbn-ecs-data-quality-dashboard => security-solution/ecs_data_quality_dashboard}/impl/data_quality/ecs_summary_donut_chart/chart_legend/index.tsx (100%) rename x-pack/packages/{kbn-ecs-data-quality-dashboard => security-solution/ecs_data_quality_dashboard}/impl/data_quality/ecs_summary_donut_chart/helpers.test.ts (100%) rename x-pack/packages/{kbn-ecs-data-quality-dashboard => security-solution/ecs_data_quality_dashboard}/impl/data_quality/ecs_summary_donut_chart/helpers.ts (100%) rename x-pack/packages/{kbn-ecs-data-quality-dashboard => security-solution/ecs_data_quality_dashboard}/impl/data_quality/ecs_summary_donut_chart/index.tsx (100%) rename x-pack/packages/{kbn-ecs-data-quality-dashboard => security-solution/ecs_data_quality_dashboard}/impl/data_quality/ecs_summary_donut_chart/translations.ts (60%) rename x-pack/packages/{kbn-ecs-data-quality-dashboard => security-solution/ecs_data_quality_dashboard}/impl/data_quality/helpers.test.ts (100%) rename x-pack/packages/{kbn-ecs-data-quality-dashboard => security-solution/ecs_data_quality_dashboard}/impl/data_quality/helpers.ts (100%) rename x-pack/packages/{kbn-ecs-data-quality-dashboard => security-solution/ecs_data_quality_dashboard}/impl/data_quality/ilm_phases_empty_prompt/index.test.tsx (100%) rename x-pack/packages/{kbn-ecs-data-quality-dashboard => security-solution/ecs_data_quality_dashboard}/impl/data_quality/ilm_phases_empty_prompt/index.tsx (100%) create mode 100644 x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/ilm_phases_empty_prompt/translations.ts rename x-pack/packages/{kbn-ecs-data-quality-dashboard => security-solution/ecs_data_quality_dashboard}/impl/data_quality/index.test.tsx (100%) rename x-pack/packages/{kbn-ecs-data-quality-dashboard => security-solution/ecs_data_quality_dashboard}/impl/data_quality/index.tsx (100%) rename x-pack/packages/{kbn-ecs-data-quality-dashboard => security-solution/ecs_data_quality_dashboard}/impl/data_quality/mock/allowed_values/mock_allowed_values.ts (100%) rename x-pack/packages/{kbn-ecs-data-quality-dashboard => security-solution/ecs_data_quality_dashboard}/impl/data_quality/mock/data_quality_check_result/mock_index.tsx (100%) rename x-pack/packages/{kbn-ecs-data-quality-dashboard => security-solution/ecs_data_quality_dashboard}/impl/data_quality/mock/enriched_field_metadata/mock_enriched_field_metadata.ts (100%) rename x-pack/packages/{kbn-ecs-data-quality-dashboard => security-solution/ecs_data_quality_dashboard}/impl/data_quality/mock/ilm_explain/mock_ilm_explain.ts (100%) rename x-pack/packages/{kbn-ecs-data-quality-dashboard => security-solution/ecs_data_quality_dashboard}/impl/data_quality/mock/indices_get_mapping_index_mapping_record/mock_indices_get_mapping_index_mapping_record.ts (100%) rename x-pack/packages/{kbn-ecs-data-quality-dashboard => security-solution/ecs_data_quality_dashboard}/impl/data_quality/mock/mappings_properties/mock_mappings_properties.ts (100%) rename x-pack/packages/{kbn-ecs-data-quality-dashboard => security-solution/ecs_data_quality_dashboard}/impl/data_quality/mock/mappings_response/mock_mappings_response.ts (100%) rename x-pack/packages/{kbn-ecs-data-quality-dashboard => security-solution/ecs_data_quality_dashboard}/impl/data_quality/mock/partitioned_field_metadata/mock_partitioned_field_metadata.tsx (100%) rename x-pack/packages/{kbn-ecs-data-quality-dashboard => security-solution/ecs_data_quality_dashboard}/impl/data_quality/mock/pattern_rollup/mock_alerts_pattern_rollup.ts (100%) rename x-pack/packages/{kbn-ecs-data-quality-dashboard => security-solution/ecs_data_quality_dashboard}/impl/data_quality/mock/pattern_rollup/mock_auditbeat_pattern_rollup.ts (100%) rename x-pack/packages/{kbn-ecs-data-quality-dashboard => security-solution/ecs_data_quality_dashboard}/impl/data_quality/mock/pattern_rollup/mock_packetbeat_pattern_rollup.ts (100%) rename x-pack/packages/{kbn-ecs-data-quality-dashboard => security-solution/ecs_data_quality_dashboard}/impl/data_quality/mock/stats/mock_stats.tsx (100%) rename x-pack/packages/{kbn-ecs-data-quality-dashboard => security-solution/ecs_data_quality_dashboard}/impl/data_quality/mock/stats/mock_stats_green_index.ts (100%) rename x-pack/packages/{kbn-ecs-data-quality-dashboard => security-solution/ecs_data_quality_dashboard}/impl/data_quality/mock/stats/mock_stats_yellow_index.tsx (100%) rename x-pack/packages/{kbn-ecs-data-quality-dashboard => security-solution/ecs_data_quality_dashboard}/impl/data_quality/mock/test_providers/test_providers.tsx (100%) rename x-pack/packages/{kbn-ecs-data-quality-dashboard => security-solution/ecs_data_quality_dashboard}/impl/data_quality/mock/unallowed_values/mock_unallowed_values.ts (100%) rename x-pack/packages/{kbn-ecs-data-quality-dashboard => security-solution/ecs_data_quality_dashboard}/impl/data_quality/styles.tsx (100%) create mode 100644 x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/translations.ts rename x-pack/packages/{kbn-ecs-data-quality-dashboard => security-solution/ecs_data_quality_dashboard}/impl/data_quality/types.ts (100%) rename x-pack/packages/{kbn-ecs-data-quality-dashboard => security-solution/ecs_data_quality_dashboard}/impl/data_quality/use_add_to_new_case/index.tsx (100%) create mode 100644 x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/use_add_to_new_case/translations.ts rename x-pack/packages/{kbn-ecs-data-quality-dashboard => security-solution/ecs_data_quality_dashboard}/impl/data_quality/use_ilm_explain/index.tsx (100%) rename x-pack/packages/{kbn-ecs-data-quality-dashboard => security-solution/ecs_data_quality_dashboard}/impl/data_quality/use_mappings/helpers.test.ts (100%) rename x-pack/packages/{kbn-ecs-data-quality-dashboard => security-solution/ecs_data_quality_dashboard}/impl/data_quality/use_mappings/helpers.ts (100%) rename x-pack/packages/{kbn-ecs-data-quality-dashboard => security-solution/ecs_data_quality_dashboard}/impl/data_quality/use_mappings/index.tsx (100%) rename x-pack/packages/{kbn-ecs-data-quality-dashboard => security-solution/ecs_data_quality_dashboard}/impl/data_quality/use_results_rollup/helpers.test.ts (100%) rename x-pack/packages/{kbn-ecs-data-quality-dashboard => security-solution/ecs_data_quality_dashboard}/impl/data_quality/use_results_rollup/helpers.ts (100%) rename x-pack/packages/{kbn-ecs-data-quality-dashboard => security-solution/ecs_data_quality_dashboard}/impl/data_quality/use_results_rollup/index.tsx (100%) rename x-pack/packages/{kbn-ecs-data-quality-dashboard => security-solution/ecs_data_quality_dashboard}/impl/data_quality/use_stats/index.tsx (100%) rename x-pack/packages/{kbn-ecs-data-quality-dashboard => security-solution/ecs_data_quality_dashboard}/impl/data_quality/use_unallowed_values/helpers.test.ts (100%) rename x-pack/packages/{kbn-ecs-data-quality-dashboard => security-solution/ecs_data_quality_dashboard}/impl/data_quality/use_unallowed_values/helpers.ts (100%) rename x-pack/packages/{kbn-ecs-data-quality-dashboard => security-solution/ecs_data_quality_dashboard}/impl/data_quality/use_unallowed_values/index.ts (100%) rename x-pack/packages/{kbn-ecs-data-quality-dashboard => security-solution/ecs_data_quality_dashboard}/index.ts (100%) create mode 100644 x-pack/packages/security-solution/ecs_data_quality_dashboard/jest.config.js rename x-pack/packages/{kbn-ecs-data-quality-dashboard => security-solution/ecs_data_quality_dashboard}/kibana.jsonc (100%) rename x-pack/packages/{kbn-ecs-data-quality-dashboard => security-solution/ecs_data_quality_dashboard}/package.json (100%) rename x-pack/packages/{kbn-ecs-data-quality-dashboard => security-solution/ecs_data_quality_dashboard}/setup_tests.ts (100%) rename x-pack/packages/{kbn-ecs-data-quality-dashboard => security-solution/ecs_data_quality_dashboard}/tsconfig.json (87%) rename {packages => x-pack/packages}/security-solution/side_nav/README.mdx (100%) rename {packages => x-pack/packages}/security-solution/side_nav/index.ts (63%) create mode 100644 x-pack/packages/security-solution/side_nav/jest.config.js rename {packages => x-pack/packages}/security-solution/side_nav/kibana.jsonc (100%) rename {packages => x-pack/packages}/security-solution/side_nav/package.json (65%) rename {packages => x-pack/packages}/security-solution/side_nav/src/beta_badge.tsx (82%) rename {packages => x-pack/packages}/security-solution/side_nav/src/index.tsx (76%) rename {packages => x-pack/packages}/security-solution/side_nav/src/solution_side_nav.stories.tsx (96%) rename {packages => x-pack/packages}/security-solution/side_nav/src/solution_side_nav.styles.ts (83%) rename {packages => x-pack/packages}/security-solution/side_nav/src/solution_side_nav.test.tsx (96%) rename {packages => x-pack/packages}/security-solution/side_nav/src/solution_side_nav.tsx (55%) rename {packages => x-pack/packages}/security-solution/side_nav/src/solution_side_nav_panel.styles.ts (92%) rename {packages => x-pack/packages}/security-solution/side_nav/src/solution_side_nav_panel.test.tsx (95%) rename {packages => x-pack/packages}/security-solution/side_nav/src/solution_side_nav_panel.tsx (94%) rename {packages => x-pack/packages}/security-solution/side_nav/src/telemetry/const.ts (63%) rename {packages => x-pack/packages}/security-solution/side_nav/src/telemetry/telemetry_context.tsx (81%) rename {packages => x-pack/packages}/security-solution/side_nav/src/types.ts (84%) rename {packages => x-pack/packages}/security-solution/side_nav/tsconfig.json (90%) rename {packages => x-pack/packages}/security-solution/storybook/config/README.mdx (100%) rename {packages => x-pack/packages}/security-solution/storybook/config/constants.ts (69%) rename x-pack/packages/{kbn-securitysolution-data-table/.storybook/main.js => security-solution/storybook/config/index.ts} (81%) mode change 100644 => 100755 rename {packages => x-pack/packages}/security-solution/storybook/config/kibana.jsonc (100%) rename {packages => x-pack/packages}/security-solution/storybook/config/main.ts (64%) rename {packages => x-pack/packages}/security-solution/storybook/config/manager.ts (73%) rename {packages => x-pack/packages}/security-solution/storybook/config/package.json (67%) rename {packages => x-pack/packages}/security-solution/storybook/config/preview.ts (75%) rename {packages => x-pack/packages}/security-solution/storybook/config/tsconfig.json (84%) diff --git a/.eslintrc.js b/.eslintrc.js index f98b8524bf36e..8cf08f2819248 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -980,7 +980,7 @@ module.exports = { // front end and common typescript and javascript files only files: [ 'x-pack/plugins/ecs_data_quality_dashboard/common/**/*.{js,mjs,ts,tsx}', - 'x-pack/packages/kbn-ecs-data-quality-dashboard/common/**/*.{js,mjs,ts,tsx}', + 'x-pack/packages/security-solution/**/*.{js,mjs,ts,tsx}', 'x-pack/plugins/security_solution/public/**/*.{js,mjs,ts,tsx}', 'x-pack/plugins/security_solution/common/**/*.{js,mjs,ts,tsx}', 'x-pack/plugins/timelines/public/**/*.{js,mjs,ts,tsx}', @@ -1007,14 +1007,14 @@ module.exports = { // This should be a very small set as most linter rules are useful for tests as well. files: [ 'x-pack/plugins/ecs_data_quality_dashboard/**/*.{ts,tsx}', - 'x-pack/packages/kbn-ecs-data-quality-dashboard/**/*.{ts,tsx}', + 'x-pack/packages/security-solution/**/*.{ts,tsx}', 'x-pack/plugins/security_solution/**/*.{ts,tsx}', 'x-pack/plugins/timelines/**/*.{ts,tsx}', 'x-pack/plugins/cases/**/*.{ts,tsx}', ], excludedFiles: [ 'x-pack/plugins/ecs_data_quality_dashboard/**/*.{test,mock,test_helper}.{ts,tsx}', - 'x-pack/packages/kbn-ecs-data-quality-dashboard/**/*.{test,mock,test_helper}.{ts,tsx}', + 'x-pack/packages/security-solution/**/*.{test,mock,test_helper}.{ts,tsx}', 'x-pack/plugins/security_solution/**/*.{test,mock,test_helper}.{ts,tsx}', 'x-pack/plugins/timelines/**/*.{test,mock,test_helper}.{ts,tsx}', 'x-pack/plugins/cases/**/*.{test,mock,test_helper}.{ts,tsx}', @@ -1027,7 +1027,7 @@ module.exports = { // typescript only for front and back end files: [ 'x-pack/plugins/ecs_data_quality_dashboard/**/*.{ts,tsx}', - 'x-pack/packages/kbn-ecs-data-quality-dashboard/**/*.{ts,tsx}', + 'x-pack/packages/security-solution/**/*.{ts,tsx}', 'x-pack/plugins/security_solution/**/*.{ts,tsx}', 'x-pack/plugins/timelines/**/*.{ts,tsx}', 'x-pack/plugins/cases/**/*.{ts,tsx}', @@ -1059,7 +1059,7 @@ module.exports = { // typescript and javascript for front and back end files: [ 'x-pack/plugins/ecs_data_quality_dashboard/**/*.{js,mjs,ts,tsx}', - 'x-pack/packages/kbn-ecs-data-quality-dashboard/**/*.{js,mjs,ts,tsx}', + 'x-pack/packages/security-solution/**/*.{js,mjs,ts,tsx}', 'x-pack/plugins/security_solution/**/*.{js,mjs,ts,tsx}', 'x-pack/plugins/timelines/**/*.{js,mjs,ts,tsx}', 'x-pack/plugins/cases/**/*.{js,mjs,ts,tsx}', diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 5a58a6099e7c6..dff2737a1cd4c 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -319,7 +319,7 @@ packages/kbn-docs-utils @elastic/kibana-operations packages/kbn-dom-drag-drop @elastic/kibana-visualizations @elastic/kibana-data-discovery packages/kbn-ebt-tools @elastic/kibana-core packages/kbn-ecs @elastic/kibana-core @elastic/security-threat-hunting-investigations -x-pack/packages/kbn-ecs-data-quality-dashboard @elastic/security-threat-hunting-investigations +x-pack/packages/security-solution/ecs_data_quality_dashboard @elastic/security-threat-hunting-investigations x-pack/plugins/ecs_data_quality_dashboard @elastic/security-threat-hunting-investigations test/plugin_functional/plugins/elasticsearch_client_plugin @elastic/kibana-core x-pack/test/plugin_api_integration/plugins/elasticsearch_client @elastic/kibana-core @@ -549,11 +549,11 @@ x-pack/test/security_api_integration/packages/helpers @elastic/kibana-core x-pack/plugins/security @elastic/kibana-security x-pack/test/cases_api_integration/common/plugins/security_solution @elastic/response-ops x-pack/plugins/security_solution @elastic/security-solution -packages/security-solution/side_nav @elastic/security-threat-hunting-explore -packages/security-solution/storybook/config @elastic/security-threat-hunting-explore +x-pack/packages/security-solution/side_nav @elastic/security-threat-hunting-explore +x-pack/packages/security-solution/storybook/config @elastic/security-threat-hunting-explore x-pack/test/security_functional/plugins/test_endpoints @elastic/kibana-security packages/kbn-securitysolution-autocomplete @elastic/security-solution-platform -x-pack/packages/kbn-securitysolution-data-table @elastic/security-threat-hunting-investigations +x-pack/packages/security-solution/data_table @elastic/security-threat-hunting-investigations packages/kbn-securitysolution-ecs @elastic/security-threat-hunting-explore packages/kbn-securitysolution-es-utils @elastic/security-solution-platform packages/kbn-securitysolution-exception-list-components @elastic/security-solution-platform diff --git a/.i18nrc.json b/.i18nrc.json index d64d2989dc44c..31ab7a91e206c 100644 --- a/.i18nrc.json +++ b/.i18nrc.json @@ -17,7 +17,6 @@ "domDragDrop": "packages/kbn-dom-drag-drop", "controls": "src/plugins/controls", "data": "src/plugins/data", - "ecsDataQualityDashboard": "x-pack/packages/kbn-ecs-data-quality-dashboard", "observabilityAlertDetails": "x-pack/packages/observability/alert_details", "dataViews": "src/plugins/data_views", "devTools": "src/plugins/dev_tools", @@ -80,11 +79,10 @@ "savedObjects": "src/plugins/saved_objects", "savedObjectsFinder": "src/plugins/saved_objects_finder", "savedObjectsManagement": "src/plugins/saved_objects_management", - "securitySolutionDataTable": "packages/kbn-securitysolution-data-table", "server": "src/legacy/server", "share": "src/plugins/share", "sharedUXPackages": "packages/shared-ux", - "securitySolutionPackages": "packages/security-solution", + "securitySolutionPackages": "x-pack/packages/security-solution", "coloring": "packages/kbn-coloring/src", "languageDocumentationPopover": "packages/kbn-language-documentation-popover/src", "statusPage": "src/legacy/core_plugins/status_page", diff --git a/package.json b/package.json index bd9cc352f6af7..e6761869fa3c7 100644 --- a/package.json +++ b/package.json @@ -358,7 +358,7 @@ "@kbn/dom-drag-drop": "link:packages/kbn-dom-drag-drop", "@kbn/ebt-tools": "link:packages/kbn-ebt-tools", "@kbn/ecs": "link:packages/kbn-ecs", - "@kbn/ecs-data-quality-dashboard": "link:x-pack/packages/kbn-ecs-data-quality-dashboard", + "@kbn/ecs-data-quality-dashboard": "link:x-pack/packages/security-solution/ecs_data_quality_dashboard", "@kbn/ecs-data-quality-dashboard-plugin": "link:x-pack/plugins/ecs_data_quality_dashboard", "@kbn/elasticsearch-client-plugin": "link:test/plugin_functional/plugins/elasticsearch_client_plugin", "@kbn/elasticsearch-client-xpack-plugin": "link:x-pack/test/plugin_api_integration/plugins/elasticsearch_client", @@ -550,11 +550,11 @@ "@kbn/security-plugin": "link:x-pack/plugins/security", "@kbn/security-solution-fixtures-plugin": "link:x-pack/test/cases_api_integration/common/plugins/security_solution", "@kbn/security-solution-plugin": "link:x-pack/plugins/security_solution", - "@kbn/security-solution-side-nav": "link:packages/security-solution/side_nav", - "@kbn/security-solution-storybook-config": "link:packages/security-solution/storybook/config", + "@kbn/security-solution-side-nav": "link:x-pack/packages/security-solution/side_nav", + "@kbn/security-solution-storybook-config": "link:x-pack/packages/security-solution/storybook/config", "@kbn/security-test-endpoints-plugin": "link:x-pack/test/security_functional/plugins/test_endpoints", "@kbn/securitysolution-autocomplete": "link:packages/kbn-securitysolution-autocomplete", - "@kbn/securitysolution-data-table": "link:x-pack/packages/kbn-securitysolution-data-table", + "@kbn/securitysolution-data-table": "link:x-pack/packages/security-solution/data_table", "@kbn/securitysolution-ecs": "link:packages/kbn-securitysolution-ecs", "@kbn/securitysolution-es-utils": "link:packages/kbn-securitysolution-es-utils", "@kbn/securitysolution-exception-list-components": "link:packages/kbn-securitysolution-exception-list-components", diff --git a/packages/kbn-babel-preset/styled_components_files.js b/packages/kbn-babel-preset/styled_components_files.js index f9b64a5f5804b..7c164cbc2edc1 100644 --- a/packages/kbn-babel-preset/styled_components_files.js +++ b/packages/kbn-babel-preset/styled_components_files.js @@ -12,9 +12,10 @@ module.exports = { * Used by `kbn-babel-preset` and `kbn-eslint-config`. */ USES_STYLED_COMPONENTS: [ - /packages[\/\\](kbn-ui-shared-deps-(npm|src)|kbn-ecs-data-quality-dashboard)[\/\\]/, + /packages[\/\\]kbn-ui-shared-deps-(npm|src)[\/\\]/, /src[\/\\]plugins[\/\\](kibana_react)[\/\\]/, /x-pack[\/\\]plugins[\/\\](apm|beats_management|cases|fleet|infra|lists|observability|observability_shared|exploratory_view|osquery|security_solution|timelines|synthetics|ux)[\/\\]/, /x-pack[\/\\]test[\/\\]plugin_functional[\/\\]plugins[\/\\]resolver_test[\/\\]/, + /x-pack[\/\\]packages[\/\\]security-solution[\/\\]ecs_data_quality_dashboard[\/\\]/, ], }; diff --git a/packages/security-solution/side_nav/jest.config.js b/packages/security-solution/side_nav/jest.config.js deleted file mode 100644 index 84ffd6a1f7720..0000000000000 --- a/packages/security-solution/side_nav/jest.config.js +++ /dev/null @@ -1,13 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -module.exports = { - preset: '@kbn/test', - rootDir: '../../..', - roots: ['/packages/security-solution/side_nav'], -}; diff --git a/packages/security-solution/storybook/config/index.ts b/packages/security-solution/storybook/config/index.ts deleted file mode 100755 index 5a73da614bf27..0000000000000 --- a/packages/security-solution/storybook/config/index.ts +++ /dev/null @@ -1,9 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -export { TITLE, URL } from './constants'; diff --git a/src/dev/storybook/aliases.ts b/src/dev/storybook/aliases.ts index c612ef05d4b7a..24dc93c33894e 100644 --- a/src/dev/storybook/aliases.ts +++ b/src/dev/storybook/aliases.ts @@ -45,8 +45,7 @@ export const storybookAliases = { observability: 'x-pack/plugins/observability/.storybook', presentation: 'src/plugins/presentation_util/storybook', security_solution: 'x-pack/plugins/security_solution/.storybook', - security_solution_packages: 'packages/security-solution/storybook/config', - security_solution_data_table: 'x-pack/packages/kbn-securitysolution-data-table/.storybook', + security_solution_packages: 'x-pack/packages/security-solution/storybook/config', shared_ux: 'packages/shared-ux/storybook/config', threat_intelligence: 'x-pack/plugins/threat_intelligence/.storybook', triggers_actions_ui: 'x-pack/plugins/triggers_actions_ui/.storybook', diff --git a/tsconfig.base.json b/tsconfig.base.json index b6f66fe1f806e..a5de10e9f4fe3 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -632,8 +632,8 @@ "@kbn/ebt-tools/*": ["packages/kbn-ebt-tools/*"], "@kbn/ecs": ["packages/kbn-ecs"], "@kbn/ecs/*": ["packages/kbn-ecs/*"], - "@kbn/ecs-data-quality-dashboard": ["x-pack/packages/kbn-ecs-data-quality-dashboard"], - "@kbn/ecs-data-quality-dashboard/*": ["x-pack/packages/kbn-ecs-data-quality-dashboard/*"], + "@kbn/ecs-data-quality-dashboard": ["x-pack/packages/security-solution/ecs_data_quality_dashboard"], + "@kbn/ecs-data-quality-dashboard/*": ["x-pack/packages/security-solution/ecs_data_quality_dashboard/*"], "@kbn/ecs-data-quality-dashboard-plugin": ["x-pack/plugins/ecs_data_quality_dashboard"], "@kbn/ecs-data-quality-dashboard-plugin/*": ["x-pack/plugins/ecs_data_quality_dashboard/*"], "@kbn/elasticsearch-client-plugin": ["test/plugin_functional/plugins/elasticsearch_client_plugin"], @@ -1092,16 +1092,16 @@ "@kbn/security-solution-fixtures-plugin/*": ["x-pack/test/cases_api_integration/common/plugins/security_solution/*"], "@kbn/security-solution-plugin": ["x-pack/plugins/security_solution"], "@kbn/security-solution-plugin/*": ["x-pack/plugins/security_solution/*"], - "@kbn/security-solution-side-nav": ["packages/security-solution/side_nav"], - "@kbn/security-solution-side-nav/*": ["packages/security-solution/side_nav/*"], - "@kbn/security-solution-storybook-config": ["packages/security-solution/storybook/config"], - "@kbn/security-solution-storybook-config/*": ["packages/security-solution/storybook/config/*"], + "@kbn/security-solution-side-nav": ["x-pack/packages/security-solution/side_nav"], + "@kbn/security-solution-side-nav/*": ["x-pack/packages/security-solution/side_nav/*"], + "@kbn/security-solution-storybook-config": ["x-pack/packages/security-solution/storybook/config"], + "@kbn/security-solution-storybook-config/*": ["x-pack/packages/security-solution/storybook/config/*"], "@kbn/security-test-endpoints-plugin": ["x-pack/test/security_functional/plugins/test_endpoints"], "@kbn/security-test-endpoints-plugin/*": ["x-pack/test/security_functional/plugins/test_endpoints/*"], "@kbn/securitysolution-autocomplete": ["packages/kbn-securitysolution-autocomplete"], "@kbn/securitysolution-autocomplete/*": ["packages/kbn-securitysolution-autocomplete/*"], - "@kbn/securitysolution-data-table": ["x-pack/packages/kbn-securitysolution-data-table"], - "@kbn/securitysolution-data-table/*": ["x-pack/packages/kbn-securitysolution-data-table/*"], + "@kbn/securitysolution-data-table": ["x-pack/packages/security-solution/data_table"], + "@kbn/securitysolution-data-table/*": ["x-pack/packages/security-solution/data_table/*"], "@kbn/securitysolution-ecs": ["packages/kbn-securitysolution-ecs"], "@kbn/securitysolution-ecs/*": ["packages/kbn-securitysolution-ecs/*"], "@kbn/securitysolution-es-utils": ["packages/kbn-securitysolution-es-utils"], diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/body/data_quality_details/translations.ts b/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/body/data_quality_details/translations.ts deleted file mode 100644 index 6b8ffed70f8c9..0000000000000 --- a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/body/data_quality_details/translations.ts +++ /dev/null @@ -1,22 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { i18n } from '@kbn/i18n'; - -export const INDICES_TAB_TITLE = i18n.translate( - 'ecsDataQualityDashboard.body.tabs.indicesTabTitle', - { - defaultMessage: 'Indices', - } -); - -export const STORAGE_TAB_TITLE = i18n.translate( - 'ecsDataQualityDashboard.body.tabs.storageTabTitle', - { - defaultMessage: 'Storage', - } -); diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/data_quality_summary/errors_viewer/translations.ts b/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/data_quality_summary/errors_viewer/translations.ts deleted file mode 100644 index b8c9bff644803..0000000000000 --- a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/data_quality_summary/errors_viewer/translations.ts +++ /dev/null @@ -1,20 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { i18n } from '@kbn/i18n'; - -export const ERROR = i18n.translate('ecsDataQualityDashboard.errorsViewerTable.errorColumn', { - defaultMessage: 'Error', -}); - -export const INDEX = i18n.translate('ecsDataQualityDashboard.errorsViewerTable.indexColumn', { - defaultMessage: 'Index', -}); - -export const PATTERN = i18n.translate('ecsDataQualityDashboard.errorsViewerTable.patternColumn', { - defaultMessage: 'Pattern', -}); diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/index_properties/translations.ts b/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/index_properties/translations.ts deleted file mode 100644 index 91f06b70a42ff..0000000000000 --- a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/index_properties/translations.ts +++ /dev/null @@ -1,406 +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 { i18n } from '@kbn/i18n'; - -export const ADD_TO_NEW_CASE = i18n.translate( - 'ecsDataQualityDashboard.indexProperties.addToNewCaseButton', - { - defaultMessage: 'Add to new case', - } -); - -export const ALL_FIELDS = i18n.translate('ecsDataQualityDashboard.indexProperties.allFieldsLabel', { - defaultMessage: 'All fields', -}); - -export const ALL_CALLOUT = (version: string) => - i18n.translate('ecsDataQualityDashboard.indexProperties.allCallout', { - values: { version }, - defaultMessage: - "All mappings for the fields in this index, including fields that comply with the Elastic Common Schema (ECS), version {version}, and fields that don't", - }); - -export const ALL_CALLOUT_TITLE = (fieldCount: number) => - i18n.translate('ecsDataQualityDashboard.indexProperties.allCalloutTitle', { - values: { fieldCount }, - defaultMessage: - 'All {fieldCount} {fieldCount, plural, =1 {field mapping} other {field mappings}}', - }); - -export const ALL_EMPTY = i18n.translate( - 'ecsDataQualityDashboard.indexProperties.allCalloutEmptyContent', - { - defaultMessage: 'This index does not contain any mappings', - } -); - -export const ALL_EMPTY_TITLE = i18n.translate( - 'ecsDataQualityDashboard.indexProperties.allCalloutEmptyTitle', - { - defaultMessage: 'No mappings', - } -); - -export const ALL_FIELDS_TABLE_TITLE = (indexName: string) => - i18n.translate('ecsDataQualityDashboard.allTab.allFieldsTableTitle', { - values: { indexName }, - defaultMessage: 'All fields - {indexName}', - }); - -export const SUMMARY_MARKDOWN_TITLE = i18n.translate( - 'ecsDataQualityDashboard.indexProperties.summaryMarkdownTitle', - { - defaultMessage: 'Data quality', - } -); - -export const SUMMARY_MARKDOWN_DESCRIPTION = ({ - ecsFieldReferenceUrl, - ecsReferenceUrl, - indexName, - mappingUrl, - version, -}: { - ecsFieldReferenceUrl: string; - ecsReferenceUrl: string; - indexName: string; - mappingUrl: string; - version: string; -}) => - i18n.translate('ecsDataQualityDashboard.indexProperties.summaryMarkdownDescription', { - values: { ecsFieldReferenceUrl, ecsReferenceUrl, indexName, mappingUrl, version }, - defaultMessage: - 'The `{indexName}` index has [mappings]({mappingUrl}) or field values that are different than the [Elastic Common Schema]({ecsReferenceUrl}) (ECS), version `{version}` [definitions]({ecsFieldReferenceUrl}).', - }); - -export const COPY_TO_CLIPBOARD = i18n.translate( - 'ecsDataQualityDashboard.indexProperties.copyToClipboardButton', - { - defaultMessage: 'Copy to clipboard', - } -); - -export const CUSTOM_FIELDS_TABLE_TITLE = (indexName: string) => - i18n.translate('ecsDataQualityDashboard.customTab.customFieldsTableTitle', { - values: { indexName }, - defaultMessage: 'Custom fields - {indexName}', - }); - -export const CUSTOM_DETECTION_ENGINE_RULES_WORK = i18n.translate( - 'ecsDataQualityDashboard.indexProperties.custonDetectionEngineRulesWorkMessage', - { - defaultMessage: '✅ Custom detection engine rules work', - } -); - -export const DOCS = i18n.translate('ecsDataQualityDashboard.indexProperties.docsLabel', { - defaultMessage: 'Docs', -}); - -export const ECS_COMPLIANT_FIELDS = i18n.translate( - 'ecsDataQualityDashboard.indexProperties.ecsCompliantFieldsLabel', - { - defaultMessage: 'ECS compliant fields', - } -); - -export const ECS_COMPLIANT_CALLOUT = ({ - fieldCount, - version, -}: { - fieldCount: number; - version: string; -}) => - i18n.translate('ecsDataQualityDashboard.indexProperties.ecsCompliantCallout', { - values: { fieldCount, version }, - defaultMessage: - 'The {fieldCount, plural, =1 {index mapping type and document values for this field comply} other {index mapping types and document values of these fields comply}} with the Elastic Common Schema (ECS), version {version}', - }); - -export const ECS_COMPLIANT_CALLOUT_TITLE = (fieldCount: number) => - i18n.translate('ecsDataQualityDashboard.indexProperties.ecsCompliantCalloutTitle', { - values: { fieldCount }, - defaultMessage: '{fieldCount} ECS compliant {fieldCount, plural, =1 {field} other {fields}}', - }); - -export const ECS_COMPLIANT_EMPTY = i18n.translate( - 'ecsDataQualityDashboard.indexProperties.ecsCompliantEmptyContent', - { - defaultMessage: - 'None of the field mappings in this index comply with the Elastic Common Schema (ECS). The index must (at least) contain an @timestamp date field.', - } -); - -export const ECS_VERSION_MARKDOWN_COMMENT = i18n.translate( - 'ecsDataQualityDashboard.indexProperties.ecsVersionMarkdownComment', - { - defaultMessage: 'Elastic Common Schema (ECS) version', - } -); - -export const INDEX = i18n.translate('ecsDataQualityDashboard.indexProperties.indexMarkdown', { - defaultMessage: 'Index', -}); - -export const ECS_COMPLIANT_EMPTY_TITLE = i18n.translate( - 'ecsDataQualityDashboard.indexProperties.ecsCompliantEmptyTitle', - { - defaultMessage: 'No ECS compliant Mappings', - } -); - -export const ECS_COMPLIANT_MAPPINGS_ARE_FULLY_SUPPORTED = i18n.translate( - 'ecsDataQualityDashboard.indexProperties.ecsCompliantMappingsAreFullySupportedMessage', - { - defaultMessage: '✅ ECS compliant mappings and field values are fully supported', - } -); - -export const ERROR_LOADING_MAPPINGS_TITLE = i18n.translate( - 'ecsDataQualityDashboard.emptyErrorPrompt.errorLoadingMappingsTitle', - { - defaultMessage: 'Unable to load index mappings', - } -); - -export const ERROR_LOADING_MAPPINGS_BODY = (error: string) => - i18n.translate('ecsDataQualityDashboard.emptyErrorPrompt.errorLoadingMappingsBody', { - values: { error }, - defaultMessage: 'There was a problem loading mappings: {error}', - }); - -export const ERROR_LOADING_UNALLOWED_VALUES_BODY = (error: string) => - i18n.translate('ecsDataQualityDashboard.emptyErrorPrompt.errorLoadingUnallowedValuesBody', { - values: { error }, - defaultMessage: 'There was a problem loading unallowed values: {error}', - }); - -export const ERROR_LOADING_UNALLOWED_VALUES_TITLE = i18n.translate( - 'ecsDataQualityDashboard.emptyErrorPrompt.errorLoadingUnallowedValuesTitle', - { - defaultMessage: 'Unable to load unallowed values', - } -); - -export const ECS_COMPLIANT_FIELDS_TABLE_TITLE = (indexName: string) => - i18n.translate('ecsDataQualityDashboard.customTab.ecsComplaintFieldsTableTitle', { - values: { indexName }, - defaultMessage: 'ECS complaint fields - {indexName}', - }); - -export const LOADING_MAPPINGS = i18n.translate( - 'ecsDataQualityDashboard.emptyLoadingPrompt.loadingMappingsPrompt', - { - defaultMessage: 'Loading mappings', - } -); - -export const LOADING_UNALLOWED_VALUES = i18n.translate( - 'ecsDataQualityDashboard.emptyLoadingPrompt.loadingUnallowedValuesPrompt', - { - defaultMessage: 'Loading unallowed values', - } -); - -export const SUMMARY = i18n.translate('ecsDataQualityDashboard.indexProperties.summaryTab', { - defaultMessage: 'Summary', -}); - -export const MISSING_TIMESTAMP_CALLOUT = i18n.translate( - 'ecsDataQualityDashboard.indexProperties.missingTimestampCallout', - { - defaultMessage: - 'Consider adding an @timestamp (date) field mapping to this index, as required by the Elastic Common Schema (ECS), because:', - } -); - -export const MISSING_TIMESTAMP_CALLOUT_TITLE = i18n.translate( - 'ecsDataQualityDashboard.indexProperties.missingTimestampCalloutTitle', - { - defaultMessage: 'Missing an @timestamp (date) field mapping for this index', - } -); - -export const CUSTOM_FIELDS = i18n.translate( - 'ecsDataQualityDashboard.indexProperties.customFieldsLabel', - { - defaultMessage: 'Custom fields', - } -); - -export const CUSTOM_CALLOUT = ({ fieldCount, version }: { fieldCount: number; version: string }) => - i18n.translate('ecsDataQualityDashboard.indexProperties.customCallout', { - values: { fieldCount, version }, - defaultMessage: - '{fieldCount, plural, =1 {This field is not} other {These fields are not}} defined by the Elastic Common Schema (ECS), version {version}.', - }); - -export const CUSTOM_CALLOUT_TITLE = (fieldCount: number) => - i18n.translate('ecsDataQualityDashboard.indexProperties.customCalloutTitle', { - values: { fieldCount }, - defaultMessage: - '{fieldCount} Custom {fieldCount, plural, =1 {field mapping} other {field mappings}}', - }); - -export const CUSTOM_EMPTY = i18n.translate( - 'ecsDataQualityDashboard.indexProperties.customEmptyContent', - { - defaultMessage: 'All the field mappings in this index are defined by the Elastic Common Schema', - } -); - -export const CUSTOM_EMPTY_TITLE = i18n.translate( - 'ecsDataQualityDashboard.indexProperties.customEmptyTitle', - { - defaultMessage: 'All field mappings defined by ECS', - } -); - -export const INCOMPATIBLE_FIELDS = i18n.translate( - 'ecsDataQualityDashboard.indexProperties.incompatibleFieldsTab', - { - defaultMessage: 'Incompatible fields', - } -); - -export const INCOMPATIBLE_CALLOUT = ({ - fieldCount, - version, -}: { - fieldCount: number; - version: string; -}) => - i18n.translate('ecsDataQualityDashboard.indexProperties.incompatibleCallout', { - values: { version }, - defaultMessage: - "Fields are incompatible with ECS when index mappings, or the values of the fields in the index, don't conform to the Elastic Common Schema (ECS), version {version}.", - }); - -export const INCOMPATIBLE_FIELDS_WITH = i18n.translate( - 'ecsDataQualityDashboard.indexProperties.incompatibleCallout.incompatibleFieldsWithLabel', - { - defaultMessage: - 'Incompatible fields with mappings in the same family have exactly the same search behavior but may have different space usage or performance characteristics.', - } -); - -export const WHEN_AN_INCOMPATIBLE_FIELD = i18n.translate( - 'ecsDataQualityDashboard.indexProperties.incompatibleCallout.whenAnIncompatibleFieldLabel', - { - defaultMessage: 'When an incompatible field is not in the same family:', - } -); - -export const INCOMPATIBLE_CALLOUT_TITLE = ({ - fieldCount, - fieldsInSameFamily, -}: { - fieldCount: number; - fieldsInSameFamily: number; -}) => - i18n.translate('ecsDataQualityDashboard.indexProperties.incompatibleCalloutTitle', { - values: { fieldCount, fieldsInSameFamily }, - defaultMessage: - '{fieldCount} incompatible {fieldCount, plural, =1 {field} other {fields}}, {fieldsInSameFamily} {fieldsInSameFamily, plural, =1 {field} other {fields}} with {fieldsInSameFamily, plural, =1 {a mapping} other {mappings}} in the same family', - }); - -export const INCOMPATIBLE_EMPTY = i18n.translate( - 'ecsDataQualityDashboard.indexProperties.incompatibleEmptyContent', - { - defaultMessage: - 'All of the field mappings and document values in this index are compliant with the Elastic Common Schema (ECS).', - } -); - -export const INCOMPATIBLE_EMPTY_TITLE = i18n.translate( - 'ecsDataQualityDashboard.indexProperties.incompatibleEmptyTitle', - { - defaultMessage: 'All field mappings and values are ECS compliant', - } -); - -export const DETECTION_ENGINE_RULES_WILL_WORK = i18n.translate( - 'ecsDataQualityDashboard.indexProperties.detectionEngineRulesWillWorkMessage', - { - defaultMessage: '✅ Detection engine rules will work for these fields', - } -); - -export const DETECTION_ENGINE_RULES_MAY_NOT_MATCH = i18n.translate( - 'ecsDataQualityDashboard.indexProperties.detectionEngineRulesWontWorkMessage', - { - defaultMessage: - '❌ Detection engine rules referencing these fields may not match them correctly', - } -); - -export const OTHER_APP_CAPABILITIES_WORK_PROPERLY = i18n.translate( - 'ecsDataQualityDashboard.indexProperties.otherAppCapabilitiesWorkProperlyMessage', - { - defaultMessage: '✅ Other app capabilities work properly', - } -); - -export const PAGES_DISPLAY_EVENTS = i18n.translate( - 'ecsDataQualityDashboard.indexProperties.pagesDisplayEventsMessage', - { - defaultMessage: '✅ Pages display events and fields correctly', - } -); - -export const PAGES_MAY_NOT_DISPLAY_FIELDS = i18n.translate( - 'ecsDataQualityDashboard.indexProperties.pagesMayNotDisplayFieldsMessage', - { - defaultMessage: '🌕 Some pages and features may not display these fields', - } -); - -export const PAGES_MAY_NOT_DISPLAY_EVENTS = i18n.translate( - 'ecsDataQualityDashboard.indexProperties.pagesMayNotDisplayEventsMessage', - { - defaultMessage: - '❌ Pages may not display some events or fields due to unexpected field mappings or values', - } -); - -export const PRE_BUILT_DETECTION_ENGINE_RULES_WORK = i18n.translate( - 'ecsDataQualityDashboard.indexProperties.preBuiltDetectionEngineRulesWorkMessage', - { - defaultMessage: '✅ Pre-built detection engine rules work', - } -); - -export const ECS_IS_A_PERMISSIVE_SCHEMA = i18n.translate( - 'ecsDataQualityDashboard.indexProperties.ecsIsAPermissiveSchemaMessage', - { - defaultMessage: - 'ECS is a permissive schema. If your events have additional data that cannot be mapped to ECS, you can simply add them to your events, using custom field names.', - } -); - -export const SOMETIMES_INDICES_CREATED_BY_OLDER = i18n.translate( - 'ecsDataQualityDashboard.indexProperties.sometimesIndicesCreatedByOlderDescription', - { - defaultMessage: - 'Sometimes, indices created by older integrations will have mappings or values that were, but are no longer compliant.', - } -); - -export const MAPPINGS_THAT_CONFLICT_WITH_ECS = i18n.translate( - 'ecsDataQualityDashboard.indexProperties.mappingThatConflictWithEcsMessage', - { - defaultMessage: "❌ Mappings or field values that don't comply with ECS are not supported", - } -); - -export const UNKNOWN = i18n.translate( - 'ecsDataQualityDashboard.indexProperties.unknownCategoryLabel', - { - defaultMessage: 'Unknown', - } -); diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/pattern/pattern_summary/translations.ts b/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/pattern/pattern_summary/translations.ts deleted file mode 100644 index 54098d624e0f9..0000000000000 --- a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/pattern/pattern_summary/translations.ts +++ /dev/null @@ -1,29 +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 { i18n } from '@kbn/i18n'; - -export const DOCS = i18n.translate('ecsDataQualityDashboard.patternSummary.docsLabel', { - defaultMessage: 'Docs', -}); - -export const INDICES = i18n.translate('ecsDataQualityDashboard.patternSummary.indicesLabel', { - defaultMessage: 'Indices', -}); - -export const PATTERN_OR_INDEX_TOOLTIP = i18n.translate( - 'ecsDataQualityDashboard.patternSummary.patternOrIndexTooltip', - { - defaultMessage: 'A pattern or specific index', - } -); - -export const PATTERN_DOCS_COUNT_TOOLTIP = (pattern: string) => - i18n.translate('ecsDataQualityDashboard.patternDocsCountTooltip', { - values: { pattern }, - defaultMessage: 'The total count from all indices matching: {pattern}', - }); diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/pattern/translations.ts b/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/pattern/translations.ts deleted file mode 100644 index 79375b1956169..0000000000000 --- a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/pattern/translations.ts +++ /dev/null @@ -1,34 +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 { i18n } from '@kbn/i18n'; - -export const ERROR_LOADING_METADATA_TITLE = (pattern: string) => - i18n.translate('ecsDataQualityDashboard.emptyErrorPrompt.errorLoadingMetadataTitle', { - values: { pattern }, - defaultMessage: "Indices matching the {pattern} pattern won't be checked", - }); - -export const ERROR_LOADING_METADATA_BODY = ({ - error, - pattern, -}: { - error: string; - pattern: string; -}) => - i18n.translate('ecsDataQualityDashboard.emptyErrorPrompt.errorLoadingMetadataBody', { - values: { error, pattern }, - defaultMessage: - "Indices matching the {pattern} pattern won't be checked, because an error occurred: {error}", - }); - -export const LOADING_STATS = i18n.translate( - 'ecsDataQualityDashboard.emptyLoadingPrompt.loadingStatsPrompt', - { - defaultMessage: 'Loading stats', - } -); diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/stat_label/translations.ts b/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/stat_label/translations.ts deleted file mode 100644 index 1c52f29858ac4..0000000000000 --- a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/stat_label/translations.ts +++ /dev/null @@ -1,132 +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 { i18n } from '@kbn/i18n'; - -export const CHECKED = i18n.translate('ecsDataQualityDashboard.statLabels.checkedLabel', { - defaultMessage: 'checked', -}); - -export const CUSTOM = i18n.translate('ecsDataQualityDashboard.statLabels.customLabel', { - defaultMessage: 'Custom', -}); - -export const CUSTOM_INDEX_TOOL_TIP = (indexName: string) => - i18n.translate('ecsDataQualityDashboard.statLabels.customIndexToolTip', { - values: { indexName }, - defaultMessage: 'A count of the custom field mappings in the {indexName} index', - }); - -export const CUSTOM_PATTERN_TOOL_TIP = (pattern: string) => - i18n.translate('ecsDataQualityDashboard.statLabels.customPatternToolTip', { - values: { pattern }, - defaultMessage: - 'The total count of custom field mappings, in indices matching the {pattern} pattern', - }); - -export const DOCS = i18n.translate('ecsDataQualityDashboard.statLabels.docsLabel', { - defaultMessage: 'Docs', -}); - -export const FIELDS = i18n.translate('ecsDataQualityDashboard.statLabels.fieldsLabel', { - defaultMessage: 'fields', -}); - -export const INCOMPATIBLE = i18n.translate('ecsDataQualityDashboard.statLabels.incompatibleLabel', { - defaultMessage: 'Incompatible', -}); - -export const INCOMPATIBLE_INDEX_TOOL_TIP = (indexName: string) => - i18n.translate('ecsDataQualityDashboard.statLabels.incompatibleIndexToolTip', { - values: { indexName }, - defaultMessage: 'Mappings and values incompatible with ECS, in the {indexName} index', - }); - -export const INCOMPATIBLE_PATTERN_TOOL_TIP = (pattern: string) => - i18n.translate('ecsDataQualityDashboard.statLabels.incompatiblePatternToolTip', { - values: { pattern }, - defaultMessage: - 'The total count of fields incompatible with ECS, in indices matching the {pattern} pattern', - }); - -export const INDEX_DOCS_COUNT_TOOL_TIP = (indexName: string) => - i18n.translate('ecsDataQualityDashboard.statLabels.indexDocsCountToolTip', { - values: { indexName }, - defaultMessage: 'A count of the docs in the {indexName} index', - }); - -export const INDEX_DOCS_PATTERN_TOOL_TIP = (pattern: string) => - i18n.translate('ecsDataQualityDashboard.statLabels.indexDocsPatternToolTip', { - values: { pattern }, - defaultMessage: 'The total count of docs, in indices matching the {pattern} pattern', - }); - -export const INDICES = i18n.translate('ecsDataQualityDashboard.statLabels.indicesLabel', { - defaultMessage: 'Indices', -}); - -export const SIZE = i18n.translate('ecsDataQualityDashboard.statLabels.sizeLabel', { - defaultMessage: 'Size', -}); - -export const INDICES_SIZE_PATTERN_TOOL_TIP = (pattern: string) => - i18n.translate('ecsDataQualityDashboard.statLabels.indicesSizePatternToolTip', { - values: { pattern }, - defaultMessage: - 'The total size of the primary indices matching the {pattern} pattern (does not include replicas)', - }); - -export const TOTAL_COUNT_OF_INDICES_CHECKED_MATCHING_PATTERN_TOOL_TIP = (pattern: string) => - i18n.translate( - 'ecsDataQualityDashboard.statLabels.totalCountOfIndicesCheckedMatchingPatternToolTip', - { - values: { pattern }, - defaultMessage: 'The total count of indices checked that match the {pattern} pattern', - } - ); - -export const TOTAL_COUNT_OF_INDICES_MATCHING_PATTERN_TOOL_TIP = (pattern: string) => - i18n.translate('ecsDataQualityDashboard.statLabels.totalCountOfIndicesMatchingPatternToolTip', { - values: { pattern }, - defaultMessage: 'The total count of indices matching the {pattern} pattern', - }); - -export const TOTAL_DOCS_TOOL_TIP = i18n.translate( - 'ecsDataQualityDashboard.statLabels.totalDocsToolTip', - { - defaultMessage: 'The total count of docs, in all indices', - } -); - -export const TOTAL_INCOMPATIBLE_TOOL_TIP = i18n.translate( - 'ecsDataQualityDashboard.statLabels.totalIncompatibleToolTip', - { - defaultMessage: - 'The total count of fields incompatible with ECS, in all indices that were checked', - } -); - -export const TOTAL_INDICES_CHECKED_TOOL_TIP = i18n.translate( - 'ecsDataQualityDashboard.statLabels.totalIndicesCheckedToolTip', - { - defaultMessage: 'The total count of all indices checked', - } -); - -export const TOTAL_INDICES_TOOL_TIP = i18n.translate( - 'ecsDataQualityDashboard.statLabels.totalIndicesToolTip', - { - defaultMessage: 'The total count of all indices', - } -); - -export const TOTAL_SIZE_TOOL_TIP = i18n.translate( - 'ecsDataQualityDashboard.statLabels.totalSizeToolTip', - { - defaultMessage: 'The total size of all primary indices (does not include replicas)', - } -); diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/storage_treemap/translations.ts b/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/storage_treemap/translations.ts deleted file mode 100644 index f60cb2366cf36..0000000000000 --- a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/storage_treemap/translations.ts +++ /dev/null @@ -1,20 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { i18n } from '@kbn/i18n'; - -export const NO_DATA_LABEL = i18n.translate('ecsDataQualityDashboard.storageTreemap.noDataLabel', { - defaultMessage: 'No data to display', -}); - -export const NO_DATA_REASON_LABEL = (stackByField1: string) => - i18n.translate('ecsDataQualityDashboard.storageTreemap.noDataReasonLabel', { - values: { - stackByField1, - }, - defaultMessage: 'The {stackByField1} field was not present in any groups', - }); diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/summary_table/translations.ts b/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/summary_table/translations.ts deleted file mode 100644 index 7f438ce9eab0f..0000000000000 --- a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/summary_table/translations.ts +++ /dev/null @@ -1,86 +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 { i18n } from '@kbn/i18n'; - -export const COLLAPSE = i18n.translate('ecsDataQualityDashboard.summaryTable.collapseLabel', { - defaultMessage: 'Collapse', -}); - -export const DOCS = i18n.translate('ecsDataQualityDashboard.summaryTable.docsColumn', { - defaultMessage: 'Docs', -}); - -export const EXPAND = i18n.translate('ecsDataQualityDashboard.summaryTable.expandLabel', { - defaultMessage: 'Expand', -}); - -export const EXPAND_ROWS = i18n.translate('ecsDataQualityDashboard.summaryTable.expandRowsColumn', { - defaultMessage: 'Expand rows', -}); - -export const FAILED = i18n.translate('ecsDataQualityDashboard.summaryTable.failedTooltip', { - defaultMessage: 'Failed', -}); - -export const ILM_PHASE = i18n.translate('ecsDataQualityDashboard.summaryTable.ilmPhaseColumn', { - defaultMessage: 'ILM Phase', -}); - -export const INCOMPATIBLE_FIELDS = i18n.translate( - 'ecsDataQualityDashboard.summaryTable.incompatibleFieldsColumn', - { - defaultMessage: 'Incompatible fields', - } -); - -export const INDICES = i18n.translate('ecsDataQualityDashboard.summaryTable.indicesColumn', { - defaultMessage: 'Indices', -}); - -export const INDICES_CHECKED = i18n.translate( - 'ecsDataQualityDashboard.summaryTable.indicesCheckedColumn', - { - defaultMessage: 'Indices checked', - } -); - -export const INDEX = i18n.translate('ecsDataQualityDashboard.summaryTable.indexColumn', { - defaultMessage: 'Index', -}); - -export const INDEX_NAME_LABEL = i18n.translate( - 'ecsDataQualityDashboard.summaryTable.indexesNameLabel', - { - defaultMessage: 'Index name', - } -); - -export const INDEX_TOOL_TIP = (pattern: string) => - i18n.translate('ecsDataQualityDashboard.summaryTable.indexToolTip', { - values: { pattern }, - defaultMessage: 'This index matches the pattern or index name: {pattern}', - }); - -export const PASSED = i18n.translate('ecsDataQualityDashboard.summaryTable.passedTooltip', { - defaultMessage: 'Passed', -}); - -export const RESULT = i18n.translate('ecsDataQualityDashboard.summaryTable.resultColumn', { - defaultMessage: 'Result', -}); - -export const SIZE = i18n.translate('ecsDataQualityDashboard.summaryTable.sizeColumn', { - defaultMessage: 'Size', -}); - -export const THIS_INDEX_HAS_NOT_BEEN_CHECKED = i18n.translate( - 'ecsDataQualityDashboard.summaryTable.thisIndexHasNotBeenCheckedTooltip', - { - defaultMessage: 'This index has not been checked', - } -); diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/tabs/incompatible_tab/translations.ts b/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/tabs/incompatible_tab/translations.ts deleted file mode 100644 index 829aa228e06b4..0000000000000 --- a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/tabs/incompatible_tab/translations.ts +++ /dev/null @@ -1,20 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { i18n } from '@kbn/i18n'; - -export const INCOMPATIBLE_FIELD_MAPPINGS_TABLE_TITLE = (indexName: string) => - i18n.translate('ecsDataQualityDashboard.incompatibleTab.incompatibleFieldMappingsTableTitle', { - values: { indexName }, - defaultMessage: 'Incompatible field mappings - {indexName}', - }); - -export const INCOMPATIBLE_FIELD_VALUES_TABLE_TITLE = (indexName: string) => - i18n.translate('ecsDataQualityDashboard.incompatibleTab.incompatibleFieldValuesTableTitle', { - values: { indexName }, - defaultMessage: 'Incompatible field values - {indexName}', - }); diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/ilm_phases_empty_prompt/translations.ts b/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/ilm_phases_empty_prompt/translations.ts deleted file mode 100644 index 080669c92c39a..0000000000000 --- a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/ilm_phases_empty_prompt/translations.ts +++ /dev/null @@ -1,62 +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 { i18n } from '@kbn/i18n'; - -export const BODY = i18n.translate('ecsDataQualityDashboard.ilmPhasesEmptyPromptBody', { - defaultMessage: - 'Indices with these Index Lifecycle Management (ILM) phases will be checked for data quality', -}); - -export const COLD = i18n.translate('ecsDataQualityDashboard.ilmPhasesEmptyPromptColdLabel', { - defaultMessage: 'cold', -}); - -export const FROZEN = i18n.translate('ecsDataQualityDashboard.ilmPhasesEmptyPromptFrozenLabel', { - defaultMessage: 'frozen', -}); - -export const HOT = i18n.translate('ecsDataQualityDashboard.ilmPhasesEmptyPromptHotLabel', { - defaultMessage: 'hot', -}); - -export const ILM_PHASES_THAT_CAN_BE_CHECKED = i18n.translate( - 'ecsDataQualityDashboard.ilmPhasesEmptyPromptIlmPhasesThatCanBeCheckedSubtitle', - { - defaultMessage: 'ILM phases that can be checked for data quality', - } -); - -export const ILM_PHASES_THAT_CANNOT_BE_CHECKED = i18n.translate( - 'ecsDataQualityDashboard.ilmPhasesEmptyPromptIlmPhasesThatCannotBeCheckedSubtitle', - { - defaultMessage: 'ILM phases that cannot be checked', - } -); - -export const THE_FOLLOWING_ILM_PHASES = i18n.translate( - 'ecsDataQualityDashboard.ilmPhasesEmptyPromptITheFollowingIlmPhasesLabel', - { - defaultMessage: - 'The following ILM phases cannot be checked for data quality because they are slower to access', - } -); - -export const UNMANAGED = i18n.translate( - 'ecsDataQualityDashboard.ilmPhasesEmptyPromptUnmanagedLabel', - { - defaultMessage: 'unmanaged', - } -); - -export const WARM = i18n.translate('ecsDataQualityDashboard.ilmPhasesEmptyPromptWarmLabel', { - defaultMessage: 'warm', -}); - -export const TITLE = i18n.translate('ecsDataQualityDashboard.ilmPhasesEmptyPromptTitle', { - defaultMessage: 'Select one or more ILM phases', -}); diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/translations.ts b/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/translations.ts deleted file mode 100644 index 53bedadd9361d..0000000000000 --- a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/translations.ts +++ /dev/null @@ -1,240 +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 { i18n } from '@kbn/i18n'; - -export const ADD_TO_NEW_CASE = i18n.translate('ecsDataQualityDashboard.addToNewCaseButton', { - defaultMessage: 'Add to new case', -}); - -export const CANCEL = i18n.translate('ecsDataQualityDashboard.cancelButton', { - defaultMessage: 'Cancel', -}); - -export const CHECK_ALL = i18n.translate('ecsDataQualityDashboard.checkAllButton', { - defaultMessage: 'Check all', -}); - -export const CHECKING = (index: string) => - i18n.translate('ecsDataQualityDashboard.checkingLabel', { - values: { index }, - defaultMessage: 'Checking {index}', - }); - -export const COLD_DESCRIPTION = i18n.translate('ecsDataQualityDashboard.coldDescription', { - defaultMessage: - 'The index is no longer being updated and is queried infrequently. The information still needs to be searchable, but it’s okay if those queries are slower.', -}); - -export const COLD_PATTERN_TOOLTIP = ({ indices, pattern }: { indices: number; pattern: string }) => - i18n.translate('ecsDataQualityDashboard.coldPatternTooltip', { - values: { indices, pattern }, - defaultMessage: - '{indices} {indices, plural, =1 {index} other {indices}} matching the {pattern} pattern {indices, plural, =1 {is} other {are}} cold. Cold indices are no longer being updated and are queried infrequently. The information still needs to be searchable, but it’s okay if those queries are slower.', - }); - -export const COPIED_RESULTS_TOAST_TITLE = i18n.translate( - 'ecsDataQualityDashboard.toasts.copiedResultsToastTitle', - { - defaultMessage: 'Copied results to the clipboard', - } -); - -export const COPY_TO_CLIPBOARD = i18n.translate('ecsDataQualityDashboard.copyToClipboardButton', { - defaultMessage: 'Copy to clipboard', -}); - -/** The subtitle displayed on the Data Quality dashboard */ -export const DATA_QUALITY_SUBTITLE: string = i18n.translate( - 'ecsDataQualityDashboard.ecsDataQualityDashboardSubtitle', - { - defaultMessage: 'Check index mappings and values for compatibility with the', - } -); - -export const DATA_QUALITY_TITLE = i18n.translate( - 'ecsDataQualityDashboard.ecsDataQualityDashboardTitle', - { - defaultMessage: 'Data quality', - } -); - -export const DEFAULT_PANEL_TITLE = i18n.translate('ecsDataQualityDashboard.defaultPanelTitle', { - defaultMessage: 'Check index mappings', -}); - -export const ECS_VERSION = i18n.translate('ecsDataQualityDashboard.ecsVersionStat', { - defaultMessage: 'ECS version', -}); - -export const ERROR_LOADING_ECS_METADATA_TITLE = i18n.translate( - 'ecsDataQualityDashboard.emptyErrorPrompt.errorLoadingEcsMetadataTitle', - { - defaultMessage: 'Unable to load ECS metadata', - } -); - -export const ERROR_LOADING_ECS_VERSION_TITLE = i18n.translate( - 'ecsDataQualityDashboard.emptyErrorPrompt.errorLoadingEcsVersionTitle', - { - defaultMessage: 'Unable to load ECS version', - } -); - -export const ERROR_LOADING_ILM_EXPLAIN = (details: string) => - i18n.translate('ecsDataQualityDashboard.errorLoadingIlmExplainLabel', { - values: { details }, - defaultMessage: 'Error loading ILM Explain: {details}', - }); - -export const ERROR_LOADING_MAPPINGS = ({ - details, - patternOrIndexName, -}: { - details: string; - patternOrIndexName: string; -}) => - i18n.translate('ecsDataQualityDashboard.errorLoadingMappingsLabel', { - values: { details, patternOrIndexName }, - defaultMessage: 'Error loading mappings for {patternOrIndexName}: {details}', - }); - -export const ERROR_LOADING_STATS = (details: string) => - i18n.translate('ecsDataQualityDashboard.errorLoadingStatsLabel', { - values: { details }, - defaultMessage: 'Error loading stats: {details}', - }); - -export const ERROR_LOADING_UNALLOWED_VALUES = ({ - details, - indexName, -}: { - details: string; - indexName: string; -}) => - i18n.translate('ecsDataQualityDashboard.errorLoadingUnallowedValuesLabel', { - values: { details, indexName }, - defaultMessage: 'Error loading unallowed values for index {indexName}: {details}', - }); - -export const FIELDS = i18n.translate('ecsDataQualityDashboard.fieldsLabel', { - defaultMessage: 'Fields', -}); - -export const FROZEN_DESCRIPTION = i18n.translate('ecsDataQualityDashboard.frozenDescription', { - defaultMessage: `The index is no longer being updated and is queried rarely. The information still needs to be searchable, but it's okay if those queries are extremely slow.`, -}); - -export const FROZEN_PATTERN_TOOLTIP = ({ - indices, - pattern, -}: { - indices: number; - pattern: string; -}) => - i18n.translate('ecsDataQualityDashboard.frozenPatternTooltip', { - values: { indices, pattern }, - defaultMessage: `{indices} {indices, plural, =1 {index} other {indices}} matching the {pattern} pattern {indices, plural, =1 {is} other {are}} frozen. Frozen indices are no longer being updated and are queried rarely. The information still needs to be searchable, but it's okay if those queries are extremely slow.`, - }); - -export const HOT_DESCRIPTION = i18n.translate('ecsDataQualityDashboard.hotDescription', { - defaultMessage: 'The index is actively being updated and queried', -}); - -export const HOT_PATTERN_TOOLTIP = ({ indices, pattern }: { indices: number; pattern: string }) => - i18n.translate('ecsDataQualityDashboard.hotPatternTooltip', { - values: { indices, pattern }, - defaultMessage: - '{indices} {indices, plural, =1 {index} other {indices}} matching the {pattern} pattern {indices, plural, =1 {is} other {are}} hot. Hot indices are actively being updated and queried.', - }); - -/** The tooltip for the `ILM phase` combo box on the Data Quality Dashboard */ -export const INDEX_LIFECYCLE_MANAGEMENT_PHASES: string = i18n.translate( - 'ecsDataQualityDashboard.indexLifecycleManagementPhasesTooltip', - { - defaultMessage: - 'Indices with these Index Lifecycle Management (ILM) phases will be checked for data quality', - } -); - -export const INDEX_NAME = i18n.translate('ecsDataQualityDashboard.indexNameLabel', { - defaultMessage: 'Index name', -}); - -/** The label displayed for the `ILM phase` combo box on the Data Quality dashboard */ -export const ILM_PHASE: string = i18n.translate('ecsDataQualityDashboard.ilmPhaseLabel', { - defaultMessage: 'ILM phase', -}); - -export const LAST_CHECKED = i18n.translate('ecsDataQualityDashboard.lastCheckedLabel', { - defaultMessage: 'Last checked', -}); - -export const LOADING_ECS_METADATA = i18n.translate( - 'ecsDataQualityDashboard.emptyLoadingPrompt.loadingEcsMetadataPrompt', - { - defaultMessage: 'Loading ECS metadata', - } -); - -export const SELECT_AN_INDEX = i18n.translate('ecsDataQualityDashboard.selectAnIndexPrompt', { - defaultMessage: 'Select an index to compare it against ECS version', -}); - -/** The placeholder for the `ILM phase` combo box on the Data Quality Dashboard */ -export const SELECT_ONE_OR_MORE_ILM_PHASES: string = i18n.translate( - 'ecsDataQualityDashboard.selectOneOrMorPhasesPlaceholder', - { - defaultMessage: 'Select one or more ILM phases', - } -); - -export const INDEX_SIZE_TOOLTIP = i18n.translate('ecsDataQualityDashboard.indexSizeTooltip', { - defaultMessage: 'The size of the primary index (does not include replicas)', -}); - -export const TECHNICAL_PREVIEW = i18n.translate('ecsDataQualityDashboard.technicalPreviewBadge', { - defaultMessage: 'Technical preview', -}); - -export const TIMESTAMP_DESCRIPTION = i18n.translate( - 'ecsDataQualityDashboard.timestampDescriptionLabel', - { - defaultMessage: - 'Date/time when the event originated. This is the date/time extracted from the event, typically representing when the event was generated by the source. If the event source has no original timestamp, this value is typically populated by the first time the event was received by the pipeline. Required field for all events.', - } -); - -export const UNMANAGED_DESCRIPTION = i18n.translate( - 'ecsDataQualityDashboard.unmanagedDescription', - { - defaultMessage: `The index isn't managed by Index Lifecycle Management (ILM)`, - } -); - -export const UNMANAGED_PATTERN_TOOLTIP = ({ - indices, - pattern, -}: { - indices: number; - pattern: string; -}) => - i18n.translate('ecsDataQualityDashboard.unmanagedPatternTooltip', { - values: { indices, pattern }, - defaultMessage: `{indices} {indices, plural, =1 {index} other {indices}} matching the {pattern} pattern {indices, plural, =1 {is} other {are}} unmanaged by Index Lifecycle Management (ILM)`, - }); - -export const WARM_DESCRIPTION = i18n.translate('ecsDataQualityDashboard.warmDescription', { - defaultMessage: 'The index is no longer being updated but is still being queried', -}); - -export const WARM_PATTERN_TOOLTIP = ({ indices, pattern }: { indices: number; pattern: string }) => - i18n.translate('ecsDataQualityDashboard.warmPatternTooltip', { - values: { indices, pattern }, - defaultMessage: - '{indices} {indices, plural, =1 {index} other {indices}} matching the {pattern} pattern {indices, plural, =1 {is} other {are}} warm. Warm indices are no longer being updated but are still being queried.', - }); diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/use_add_to_new_case/translations.ts b/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/use_add_to_new_case/translations.ts deleted file mode 100644 index aadb58c56996b..0000000000000 --- a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/use_add_to_new_case/translations.ts +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { i18n } from '@kbn/i18n'; - -export const ADD_TO_CASE_SUCCESS = i18n.translate('ecsDataQualityDashboard.addToCaseSuccessToast', { - defaultMessage: 'Successfully added data quality results to the case', -}); - -export const CREATE_A_DATA_QUALITY_CASE = i18n.translate( - 'ecsDataQualityDashboard.createADataQualityCaseHeaderText', - { - defaultMessage: 'Create a data quality case', - } -); - -export const CREATE_A_DATA_QUALITY_CASE_FOR_INDEX = (indexName: string) => - i18n.translate('ecsDataQualityDashboard.createADataQualityCaseForIndexHeaderText', { - values: { indexName }, - defaultMessage: 'Create a data quality case for index {indexName}', - }); diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/jest.config.js b/x-pack/packages/kbn-ecs-data-quality-dashboard/jest.config.js deleted file mode 100644 index 38ee7689712b5..0000000000000 --- a/x-pack/packages/kbn-ecs-data-quality-dashboard/jest.config.js +++ /dev/null @@ -1,24 +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. - */ - -module.exports = { - coverageDirectory: - '/target/kibana-coverage/jest/x-pack/packages/kbn-ecs-data-quality-dashboard/impl', - coverageReporters: ['text', 'html'], - collectCoverageFrom: [ - '/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/**/*.{ts,tsx}', - '!/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/{__test__,__snapshots__,__examples__,*mock*,tests,test_helpers,integration_tests,types}/**/*', - '!/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/*mock*.{ts,tsx}', - '!/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/*.test.{ts,tsx}', - '!/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/*.d.ts', - '!/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/*.config.ts', - ], - preset: '@kbn/test', - rootDir: '../../..', - roots: ['/x-pack/packages/kbn-ecs-data-quality-dashboard'], - setupFilesAfterEnv: ['/x-pack/packages/kbn-ecs-data-quality-dashboard/setup_tests.ts'], -}; diff --git a/packages/security-solution/README.mdx b/x-pack/packages/security-solution/README.mdx similarity index 100% rename from packages/security-solution/README.mdx rename to x-pack/packages/security-solution/README.mdx diff --git a/x-pack/packages/kbn-securitysolution-data-table/README.md b/x-pack/packages/security-solution/data_table/README.md similarity index 100% rename from x-pack/packages/kbn-securitysolution-data-table/README.md rename to x-pack/packages/security-solution/data_table/README.md diff --git a/x-pack/packages/kbn-securitysolution-data-table/common/constants.ts b/x-pack/packages/security-solution/data_table/common/constants.ts similarity index 100% rename from x-pack/packages/kbn-securitysolution-data-table/common/constants.ts rename to x-pack/packages/security-solution/data_table/common/constants.ts diff --git a/x-pack/packages/kbn-securitysolution-data-table/common/types/data_table/index.ts b/x-pack/packages/security-solution/data_table/common/types/data_table/index.ts similarity index 100% rename from x-pack/packages/kbn-securitysolution-data-table/common/types/data_table/index.ts rename to x-pack/packages/security-solution/data_table/common/types/data_table/index.ts diff --git a/x-pack/packages/kbn-securitysolution-data-table/common/types/detail_panel.ts b/x-pack/packages/security-solution/data_table/common/types/detail_panel.ts similarity index 100% rename from x-pack/packages/kbn-securitysolution-data-table/common/types/detail_panel.ts rename to x-pack/packages/security-solution/data_table/common/types/detail_panel.ts diff --git a/x-pack/packages/kbn-securitysolution-data-table/common/types/header_actions/index.ts b/x-pack/packages/security-solution/data_table/common/types/header_actions/index.ts similarity index 100% rename from x-pack/packages/kbn-securitysolution-data-table/common/types/header_actions/index.ts rename to x-pack/packages/security-solution/data_table/common/types/header_actions/index.ts diff --git a/x-pack/packages/kbn-securitysolution-data-table/common/types/index.ts b/x-pack/packages/security-solution/data_table/common/types/index.ts similarity index 100% rename from x-pack/packages/kbn-securitysolution-data-table/common/types/index.ts rename to x-pack/packages/security-solution/data_table/common/types/index.ts diff --git a/x-pack/packages/kbn-securitysolution-data-table/common/types/risk_scores.ts b/x-pack/packages/security-solution/data_table/common/types/risk_scores.ts similarity index 100% rename from x-pack/packages/kbn-securitysolution-data-table/common/types/risk_scores.ts rename to x-pack/packages/security-solution/data_table/common/types/risk_scores.ts diff --git a/x-pack/packages/kbn-securitysolution-data-table/common/types/session_view/index.ts b/x-pack/packages/security-solution/data_table/common/types/session_view/index.ts similarity index 100% rename from x-pack/packages/kbn-securitysolution-data-table/common/types/session_view/index.ts rename to x-pack/packages/security-solution/data_table/common/types/session_view/index.ts diff --git a/x-pack/packages/kbn-securitysolution-data-table/components/data_table/column_headers/default_headers.ts b/x-pack/packages/security-solution/data_table/components/data_table/column_headers/default_headers.ts similarity index 100% rename from x-pack/packages/kbn-securitysolution-data-table/components/data_table/column_headers/default_headers.ts rename to x-pack/packages/security-solution/data_table/components/data_table/column_headers/default_headers.ts diff --git a/x-pack/packages/kbn-securitysolution-data-table/components/data_table/column_headers/helpers.test.tsx b/x-pack/packages/security-solution/data_table/components/data_table/column_headers/helpers.test.tsx similarity index 100% rename from x-pack/packages/kbn-securitysolution-data-table/components/data_table/column_headers/helpers.test.tsx rename to x-pack/packages/security-solution/data_table/components/data_table/column_headers/helpers.test.tsx diff --git a/x-pack/packages/kbn-securitysolution-data-table/components/data_table/column_headers/helpers.tsx b/x-pack/packages/security-solution/data_table/components/data_table/column_headers/helpers.tsx similarity index 95% rename from x-pack/packages/kbn-securitysolution-data-table/components/data_table/column_headers/helpers.tsx rename to x-pack/packages/security-solution/data_table/components/data_table/column_headers/helpers.tsx index 95adca844984c..e55e3378c92d9 100644 --- a/x-pack/packages/kbn-securitysolution-data-table/components/data_table/column_headers/helpers.tsx +++ b/x-pack/packages/security-solution/data_table/components/data_table/column_headers/helpers.tsx @@ -154,7 +154,7 @@ const eventRenderedViewColumns: ColumnHeaderOptions[] = [ columnHeaderType: defaultColumnHeaderType, id: '@timestamp', displayAsText: i18n.translate( - 'securitySolutionDataTable.EventRenderedView.timestampTitle.column', + 'securitySolutionPackages.dataTable.eventRenderedView.timestampTitle.column', { defaultMessage: 'Timestamp', } @@ -166,9 +166,12 @@ const eventRenderedViewColumns: ColumnHeaderOptions[] = [ }, { columnHeaderType: defaultColumnHeaderType, - displayAsText: i18n.translate('securitySolutionDataTable.EventRenderedView.ruleTitle.column', { - defaultMessage: 'Rule', - }), + displayAsText: i18n.translate( + 'securitySolutionPackages.dataTable.eventRenderedView.ruleTitle.column', + { + defaultMessage: 'Rule', + } + ), id: 'kibana.alert.rule.name', initialWidth: DEFAULT_TABLE_COLUMN_MIN_WIDTH + 50, linkField: 'kibana.alert.rule.uuid', @@ -180,7 +183,7 @@ const eventRenderedViewColumns: ColumnHeaderOptions[] = [ columnHeaderType: defaultColumnHeaderType, id: 'eventSummary', displayAsText: i18n.translate( - 'securitySolutionDataTable.EventRenderedView.eventSummary.column', + 'securitySolutionPackages.dataTable.eventRenderedView.eventSummary.column', { defaultMessage: 'Event Summary', } diff --git a/x-pack/packages/kbn-securitysolution-data-table/components/data_table/column_headers/translations.ts b/x-pack/packages/security-solution/data_table/components/data_table/column_headers/translations.ts similarity index 80% rename from x-pack/packages/kbn-securitysolution-data-table/components/data_table/column_headers/translations.ts rename to x-pack/packages/security-solution/data_table/components/data_table/column_headers/translations.ts index 48aeb0d0f0d9e..a12699cfde096 100644 --- a/x-pack/packages/kbn-securitysolution-data-table/components/data_table/column_headers/translations.ts +++ b/x-pack/packages/security-solution/data_table/components/data_table/column_headers/translations.ts @@ -8,7 +8,7 @@ import { i18n } from '@kbn/i18n'; export const REMOVE_COLUMN = i18n.translate( - 'securitySolutionDataTable.columnHeaders.flyout.pane.removeColumnButtonLabel', + 'securitySolutionPackages.dataTable.columnHeaders.flyout.pane.removeColumnButtonLabel', { defaultMessage: 'Remove column', } diff --git a/x-pack/packages/kbn-securitysolution-data-table/components/data_table/constants.ts b/x-pack/packages/security-solution/data_table/components/data_table/constants.ts similarity index 100% rename from x-pack/packages/kbn-securitysolution-data-table/components/data_table/constants.ts rename to x-pack/packages/security-solution/data_table/components/data_table/constants.ts diff --git a/x-pack/packages/kbn-securitysolution-data-table/components/data_table/data_table.stories.tsx b/x-pack/packages/security-solution/data_table/components/data_table/data_table.stories.tsx similarity index 93% rename from x-pack/packages/kbn-securitysolution-data-table/components/data_table/data_table.stories.tsx rename to x-pack/packages/security-solution/data_table/components/data_table/data_table.stories.tsx index 29b50a59af86a..970ca22d90c04 100644 --- a/x-pack/packages/kbn-securitysolution-data-table/components/data_table/data_table.stories.tsx +++ b/x-pack/packages/security-solution/data_table/components/data_table/data_table.stories.tsx @@ -15,7 +15,6 @@ import { DragDropContext, DropResult, ResponderProvided } from 'react-beautiful- import { ThemeProvider } from 'styled-components'; import { Provider as ReduxStoreProvider } from 'react-redux'; import { euiDarkVars } from '@kbn/ui-theme'; -import { Store } from 'redux'; import { createStore as createReduxStore } from 'redux'; import type { Action } from '@kbn/ui-actions-plugin/public'; import { EuiButtonEmpty } from '@elastic/eui'; @@ -26,15 +25,14 @@ import { mockTimelineData } from '../../mock/mock_timeline_data'; import { DataTableComponent } from '.'; export default { - component: DataTableComponent, - title: 'DataTableComponent', + title: 'DataTable', + description: 'Table component for displaying events data in a grid view', }; -const createStore = (state: any) => createReduxStore(() => state, state); +const createStore = (state: unknown) => createReduxStore(() => state, state); interface Props { children?: React.ReactNode; - store?: Store; onDragEnd?: (result: DropResult, provided: ResponderProvided) => void; cellActions?: Action[]; } @@ -76,12 +74,12 @@ const MockFieldBrowser = () => { size="xs" onClick={() => window.alert('Not implemented')} > - Field Browser + {'Field Browser'} ); }; -export function Example() { +export const DataTable = () => { return ( ); -} +}; diff --git a/x-pack/packages/kbn-securitysolution-data-table/components/data_table/helpers.test.tsx b/x-pack/packages/security-solution/data_table/components/data_table/helpers.test.tsx similarity index 100% rename from x-pack/packages/kbn-securitysolution-data-table/components/data_table/helpers.test.tsx rename to x-pack/packages/security-solution/data_table/components/data_table/helpers.test.tsx diff --git a/x-pack/packages/kbn-securitysolution-data-table/components/data_table/helpers.tsx b/x-pack/packages/security-solution/data_table/components/data_table/helpers.tsx similarity index 100% rename from x-pack/packages/kbn-securitysolution-data-table/components/data_table/helpers.tsx rename to x-pack/packages/security-solution/data_table/components/data_table/helpers.tsx diff --git a/x-pack/packages/kbn-securitysolution-data-table/components/data_table/index.test.tsx b/x-pack/packages/security-solution/data_table/components/data_table/index.test.tsx similarity index 100% rename from x-pack/packages/kbn-securitysolution-data-table/components/data_table/index.test.tsx rename to x-pack/packages/security-solution/data_table/components/data_table/index.test.tsx diff --git a/x-pack/packages/kbn-securitysolution-data-table/components/data_table/index.tsx b/x-pack/packages/security-solution/data_table/components/data_table/index.tsx similarity index 99% rename from x-pack/packages/kbn-securitysolution-data-table/components/data_table/index.tsx rename to x-pack/packages/security-solution/data_table/components/data_table/index.tsx index 0f1e34099997f..4242778ff0cb8 100644 --- a/x-pack/packages/kbn-securitysolution-data-table/components/data_table/index.tsx +++ b/x-pack/packages/security-solution/data_table/components/data_table/index.tsx @@ -52,7 +52,7 @@ import { UnitCount } from '../toolbar/unit'; import { useShallowEqualSelector } from '../../hooks/use_selector'; import { tableDefaults } from '../../store/data_table/defaults'; -const DATA_TABLE_ARIA_LABEL = i18n.translate('securitySolutionDataTable.dataTable.ariaLabel', { +const DATA_TABLE_ARIA_LABEL = i18n.translate('securitySolutionPackages.dataTable.ariaLabel', { defaultMessage: 'Alerts', }); diff --git a/x-pack/packages/kbn-securitysolution-data-table/components/data_table/pagination.ts b/x-pack/packages/security-solution/data_table/components/data_table/pagination.ts similarity index 100% rename from x-pack/packages/kbn-securitysolution-data-table/components/data_table/pagination.ts rename to x-pack/packages/security-solution/data_table/components/data_table/pagination.ts diff --git a/x-pack/packages/kbn-securitysolution-data-table/components/data_table/types.ts b/x-pack/packages/security-solution/data_table/components/data_table/types.ts similarity index 100% rename from x-pack/packages/kbn-securitysolution-data-table/components/data_table/types.ts rename to x-pack/packages/security-solution/data_table/components/data_table/types.ts diff --git a/x-pack/packages/kbn-securitysolution-data-table/components/data_table/utils.ts b/x-pack/packages/security-solution/data_table/components/data_table/utils.ts similarity index 100% rename from x-pack/packages/kbn-securitysolution-data-table/components/data_table/utils.ts rename to x-pack/packages/security-solution/data_table/components/data_table/utils.ts diff --git a/x-pack/packages/kbn-securitysolution-data-table/components/toolbar/bulk_actions/types.ts b/x-pack/packages/security-solution/data_table/components/toolbar/bulk_actions/types.ts similarity index 100% rename from x-pack/packages/kbn-securitysolution-data-table/components/toolbar/bulk_actions/types.ts rename to x-pack/packages/security-solution/data_table/components/toolbar/bulk_actions/types.ts diff --git a/x-pack/packages/kbn-securitysolution-data-table/components/toolbar/unit/index.ts b/x-pack/packages/security-solution/data_table/components/toolbar/unit/index.ts similarity index 100% rename from x-pack/packages/kbn-securitysolution-data-table/components/toolbar/unit/index.ts rename to x-pack/packages/security-solution/data_table/components/toolbar/unit/index.ts diff --git a/x-pack/packages/kbn-securitysolution-data-table/components/toolbar/unit/styles.tsx b/x-pack/packages/security-solution/data_table/components/toolbar/unit/styles.tsx similarity index 100% rename from x-pack/packages/kbn-securitysolution-data-table/components/toolbar/unit/styles.tsx rename to x-pack/packages/security-solution/data_table/components/toolbar/unit/styles.tsx diff --git a/x-pack/packages/kbn-securitysolution-data-table/components/toolbar/unit/translations.ts b/x-pack/packages/security-solution/data_table/components/toolbar/unit/translations.ts similarity index 86% rename from x-pack/packages/kbn-securitysolution-data-table/components/toolbar/unit/translations.ts rename to x-pack/packages/security-solution/data_table/components/toolbar/unit/translations.ts index 0ec18a9e509cd..6963cac5d3fab 100644 --- a/x-pack/packages/kbn-securitysolution-data-table/components/toolbar/unit/translations.ts +++ b/x-pack/packages/security-solution/data_table/components/toolbar/unit/translations.ts @@ -8,7 +8,7 @@ import { i18n } from '@kbn/i18n'; export const ALERTS_UNIT = (totalCount: number) => - i18n.translate('securitySolutionDataTable.eventsTab.unit', { + i18n.translate('securitySolutionPackages.dataTable.eventsTab.unit', { values: { totalCount }, defaultMessage: `{totalCount, plural, =1 {alert} other {alerts}}`, }); diff --git a/x-pack/packages/kbn-securitysolution-data-table/hooks/use_selector.tsx b/x-pack/packages/security-solution/data_table/hooks/use_selector.tsx similarity index 100% rename from x-pack/packages/kbn-securitysolution-data-table/hooks/use_selector.tsx rename to x-pack/packages/security-solution/data_table/hooks/use_selector.tsx diff --git a/x-pack/packages/kbn-securitysolution-data-table/index.ts b/x-pack/packages/security-solution/data_table/index.ts similarity index 100% rename from x-pack/packages/kbn-securitysolution-data-table/index.ts rename to x-pack/packages/security-solution/data_table/index.ts diff --git a/x-pack/packages/kbn-securitysolution-data-table/jest.config.js b/x-pack/packages/security-solution/data_table/jest.config.js similarity index 75% rename from x-pack/packages/kbn-securitysolution-data-table/jest.config.js rename to x-pack/packages/security-solution/data_table/jest.config.js index 439c6c690765b..38f769e20277c 100644 --- a/x-pack/packages/kbn-securitysolution-data-table/jest.config.js +++ b/x-pack/packages/security-solution/data_table/jest.config.js @@ -7,6 +7,6 @@ module.exports = { preset: '@kbn/test', - roots: ['/x-pack/packages/kbn-securitysolution-data-table'], - rootDir: '../../..', + roots: ['/x-pack/packages/security-solution/data_table'], + rootDir: '../../../..', }; diff --git a/x-pack/packages/kbn-securitysolution-data-table/kibana.jsonc b/x-pack/packages/security-solution/data_table/kibana.jsonc similarity index 100% rename from x-pack/packages/kbn-securitysolution-data-table/kibana.jsonc rename to x-pack/packages/security-solution/data_table/kibana.jsonc diff --git a/x-pack/packages/kbn-securitysolution-data-table/mock/demo_data/endpoint/library_load_event.ts b/x-pack/packages/security-solution/data_table/mock/demo_data/endpoint/library_load_event.ts similarity index 100% rename from x-pack/packages/kbn-securitysolution-data-table/mock/demo_data/endpoint/library_load_event.ts rename to x-pack/packages/security-solution/data_table/mock/demo_data/endpoint/library_load_event.ts diff --git a/x-pack/packages/kbn-securitysolution-data-table/mock/demo_data/endpoint/process_execution_malware_prevention_alert.ts b/x-pack/packages/security-solution/data_table/mock/demo_data/endpoint/process_execution_malware_prevention_alert.ts similarity index 100% rename from x-pack/packages/kbn-securitysolution-data-table/mock/demo_data/endpoint/process_execution_malware_prevention_alert.ts rename to x-pack/packages/security-solution/data_table/mock/demo_data/endpoint/process_execution_malware_prevention_alert.ts diff --git a/x-pack/packages/kbn-securitysolution-data-table/mock/demo_data/endpoint/registry_modification_event.ts b/x-pack/packages/security-solution/data_table/mock/demo_data/endpoint/registry_modification_event.ts similarity index 100% rename from x-pack/packages/kbn-securitysolution-data-table/mock/demo_data/endpoint/registry_modification_event.ts rename to x-pack/packages/security-solution/data_table/mock/demo_data/endpoint/registry_modification_event.ts diff --git a/x-pack/packages/kbn-securitysolution-data-table/mock/demo_data/timeline.ts b/x-pack/packages/security-solution/data_table/mock/demo_data/timeline.ts similarity index 100% rename from x-pack/packages/kbn-securitysolution-data-table/mock/demo_data/timeline.ts rename to x-pack/packages/security-solution/data_table/mock/demo_data/timeline.ts diff --git a/x-pack/packages/kbn-securitysolution-data-table/mock/global_state.ts b/x-pack/packages/security-solution/data_table/mock/global_state.ts similarity index 97% rename from x-pack/packages/kbn-securitysolution-data-table/mock/global_state.ts rename to x-pack/packages/security-solution/data_table/mock/global_state.ts index dda9d8b6c6d8d..557c2c0dd0aca 100644 --- a/x-pack/packages/kbn-securitysolution-data-table/mock/global_state.ts +++ b/x-pack/packages/security-solution/data_table/mock/global_state.ts @@ -9,7 +9,7 @@ import { TableId } from '../common/types'; import { defaultHeaders } from './header'; // FIXME add strong typings -export const mockGlobalState: any = { +export const mockGlobalState = { app: { notesById: {}, errors: [ diff --git a/x-pack/packages/kbn-securitysolution-data-table/mock/header.ts b/x-pack/packages/security-solution/data_table/mock/header.ts similarity index 100% rename from x-pack/packages/kbn-securitysolution-data-table/mock/header.ts rename to x-pack/packages/security-solution/data_table/mock/header.ts diff --git a/x-pack/packages/kbn-securitysolution-data-table/mock/mock_local_storage.ts b/x-pack/packages/security-solution/data_table/mock/mock_local_storage.ts similarity index 100% rename from x-pack/packages/kbn-securitysolution-data-table/mock/mock_local_storage.ts rename to x-pack/packages/security-solution/data_table/mock/mock_local_storage.ts diff --git a/x-pack/packages/kbn-securitysolution-data-table/mock/mock_source.ts b/x-pack/packages/security-solution/data_table/mock/mock_source.ts similarity index 100% rename from x-pack/packages/kbn-securitysolution-data-table/mock/mock_source.ts rename to x-pack/packages/security-solution/data_table/mock/mock_source.ts diff --git a/x-pack/packages/kbn-securitysolution-data-table/mock/mock_timeline_data.ts b/x-pack/packages/security-solution/data_table/mock/mock_timeline_data.ts similarity index 100% rename from x-pack/packages/kbn-securitysolution-data-table/mock/mock_timeline_data.ts rename to x-pack/packages/security-solution/data_table/mock/mock_timeline_data.ts diff --git a/x-pack/packages/kbn-securitysolution-data-table/mock/test_providers.tsx b/x-pack/packages/security-solution/data_table/mock/test_providers.tsx similarity index 97% rename from x-pack/packages/kbn-securitysolution-data-table/mock/test_providers.tsx rename to x-pack/packages/security-solution/data_table/mock/test_providers.tsx index 33c86527f7505..b55d01e2e4a00 100644 --- a/x-pack/packages/kbn-securitysolution-data-table/mock/test_providers.tsx +++ b/x-pack/packages/security-solution/data_table/mock/test_providers.tsx @@ -36,6 +36,7 @@ Object.defineProperty(window, 'localStorage', { }); window.scrollTo = jest.fn(); +// eslint-disable-next-line @typescript-eslint/no-explicit-any const createStore = (state: any) => createReduxStore(() => {}, state); /** A utility for wrapping children in the providers required to run most tests */ diff --git a/x-pack/packages/kbn-securitysolution-data-table/package.json b/x-pack/packages/security-solution/data_table/package.json similarity index 100% rename from x-pack/packages/kbn-securitysolution-data-table/package.json rename to x-pack/packages/security-solution/data_table/package.json diff --git a/x-pack/packages/kbn-securitysolution-data-table/store/data_table/actions.ts b/x-pack/packages/security-solution/data_table/store/data_table/actions.ts similarity index 100% rename from x-pack/packages/kbn-securitysolution-data-table/store/data_table/actions.ts rename to x-pack/packages/security-solution/data_table/store/data_table/actions.ts diff --git a/x-pack/packages/kbn-securitysolution-data-table/store/data_table/defaults.ts b/x-pack/packages/security-solution/data_table/store/data_table/defaults.ts similarity index 100% rename from x-pack/packages/kbn-securitysolution-data-table/store/data_table/defaults.ts rename to x-pack/packages/security-solution/data_table/store/data_table/defaults.ts diff --git a/x-pack/packages/kbn-securitysolution-data-table/store/data_table/helpers.test.tsx b/x-pack/packages/security-solution/data_table/store/data_table/helpers.test.tsx similarity index 95% rename from x-pack/packages/kbn-securitysolution-data-table/store/data_table/helpers.test.tsx rename to x-pack/packages/security-solution/data_table/store/data_table/helpers.test.tsx index d414003743c66..965e6387e5520 100644 --- a/x-pack/packages/kbn-securitysolution-data-table/store/data_table/helpers.test.tsx +++ b/x-pack/packages/security-solution/data_table/store/data_table/helpers.test.tsx @@ -14,11 +14,12 @@ import { import { mockGlobalState } from '../../mock/global_state'; import { SortColumnTable, TableId } from '../../common/types'; import type { DataTableModelSettings } from './model'; +import type { TableById } from './types'; const id = 'foo'; const defaultTableById = { ...mockGlobalState.dataTable.tableById, -}; +} as unknown as TableById; describe('setInitializeDataTableSettings', () => { test('it returns the expected sort when dataTableSettingsProps has an override', () => { @@ -68,7 +69,7 @@ describe('setInitializeDataTableSettings', () => { describe('updateDataTableColumnOrder', () => { test('it returns the columns in the new expected order', () => { - const originalIdOrder = defaultTableById[TableId.test].columns.map((x: any) => x.id); // ['@timestamp', 'event.severity', 'event.category', '...'] + const originalIdOrder = defaultTableById[TableId.test].columns.map((x) => x.id); // ['@timestamp', 'event.severity', 'event.category', '...'] // the new order swaps the positions of the first and second columns: const newIdOrder = [originalIdOrder[1], originalIdOrder[0], ...originalIdOrder.slice(2)]; // ['event.severity', '@timestamp', 'event.category', '...'] @@ -93,7 +94,7 @@ describe('updateDataTableColumnOrder', () => { }); test('it omits unknown column IDs when re-ordering columns', () => { - const originalIdOrder = defaultTableById[TableId.test].columns.map((x: any) => x.id); // ['@timestamp', 'event.severity', 'event.category', '...'] + const originalIdOrder = defaultTableById[TableId.test].columns.map((x) => x.id); // ['@timestamp', 'event.severity', 'event.category', '...'] const unknownColumId = 'does.not.exist'; const newIdOrder = [originalIdOrder[0], unknownColumId, ...originalIdOrder.slice(1)]; // ['@timestamp', 'does.not.exist', 'event.severity', 'event.category', '...'] diff --git a/x-pack/packages/kbn-securitysolution-data-table/store/data_table/helpers.ts b/x-pack/packages/security-solution/data_table/store/data_table/helpers.ts similarity index 100% rename from x-pack/packages/kbn-securitysolution-data-table/store/data_table/helpers.ts rename to x-pack/packages/security-solution/data_table/store/data_table/helpers.ts diff --git a/x-pack/packages/kbn-securitysolution-data-table/store/data_table/index.ts b/x-pack/packages/security-solution/data_table/store/data_table/index.ts similarity index 100% rename from x-pack/packages/kbn-securitysolution-data-table/store/data_table/index.ts rename to x-pack/packages/security-solution/data_table/store/data_table/index.ts diff --git a/x-pack/packages/kbn-securitysolution-data-table/store/data_table/inputs.ts b/x-pack/packages/security-solution/data_table/store/data_table/inputs.ts similarity index 100% rename from x-pack/packages/kbn-securitysolution-data-table/store/data_table/inputs.ts rename to x-pack/packages/security-solution/data_table/store/data_table/inputs.ts diff --git a/x-pack/packages/kbn-securitysolution-data-table/store/data_table/model.ts b/x-pack/packages/security-solution/data_table/store/data_table/model.ts similarity index 95% rename from x-pack/packages/kbn-securitysolution-data-table/store/data_table/model.ts rename to x-pack/packages/security-solution/data_table/store/data_table/model.ts index b4aad728ff8ce..f8d0d22d5ea88 100644 --- a/x-pack/packages/kbn-securitysolution-data-table/store/data_table/model.ts +++ b/x-pack/packages/security-solution/data_table/store/data_table/model.ts @@ -9,8 +9,12 @@ import type { EuiDataGridColumn } from '@elastic/eui'; import type { Filter } from '@kbn/es-query'; import { TimelineNonEcsData } from '@kbn/timelines-plugin/common'; import { ExpandedDetail } from '../../common/types/detail_panel'; -import { ColumnHeaderOptions, SessionViewConfig, SortColumnTable } from '../../common/types'; -import { ViewSelection } from '../../common/types'; +import type { + ColumnHeaderOptions, + SessionViewConfig, + SortColumnTable, + ViewSelection, +} from '../../common/types'; export interface DataTableModelSettings { defaultColumns: Array< diff --git a/x-pack/packages/kbn-securitysolution-data-table/store/data_table/reducer.ts b/x-pack/packages/security-solution/data_table/store/data_table/reducer.ts similarity index 100% rename from x-pack/packages/kbn-securitysolution-data-table/store/data_table/reducer.ts rename to x-pack/packages/security-solution/data_table/store/data_table/reducer.ts diff --git a/x-pack/packages/kbn-securitysolution-data-table/store/data_table/selectors.ts b/x-pack/packages/security-solution/data_table/store/data_table/selectors.ts similarity index 100% rename from x-pack/packages/kbn-securitysolution-data-table/store/data_table/selectors.ts rename to x-pack/packages/security-solution/data_table/store/data_table/selectors.ts diff --git a/x-pack/packages/kbn-securitysolution-data-table/store/data_table/translations.ts b/x-pack/packages/security-solution/data_table/store/data_table/translations.ts similarity index 80% rename from x-pack/packages/kbn-securitysolution-data-table/store/data_table/translations.ts rename to x-pack/packages/security-solution/data_table/store/data_table/translations.ts index 17c735bac5a23..082ca23d99343 100644 --- a/x-pack/packages/kbn-securitysolution-data-table/store/data_table/translations.ts +++ b/x-pack/packages/security-solution/data_table/store/data_table/translations.ts @@ -8,14 +8,14 @@ import { i18n } from '@kbn/i18n'; export const LOADING_EVENTS = i18n.translate( - 'securitySolutionDataTable.dataTable.loadingEventsDataLabel', + 'securitySolutionPackages.dataTable.loadingEventsDataLabel', { defaultMessage: 'Loading Events', } ); export const UNIT = (totalCount: number) => - i18n.translate('securitySolutionDataTable.dataTable.unit', { + i18n.translate('securitySolutionPackages.dataTable.unit', { values: { totalCount }, defaultMessage: `{totalCount, plural, =1 {alert} other {alerts}}`, }); diff --git a/x-pack/packages/kbn-securitysolution-data-table/store/data_table/types.ts b/x-pack/packages/security-solution/data_table/store/data_table/types.ts similarity index 100% rename from x-pack/packages/kbn-securitysolution-data-table/store/data_table/types.ts rename to x-pack/packages/security-solution/data_table/store/data_table/types.ts diff --git a/x-pack/packages/kbn-securitysolution-data-table/tsconfig.json b/x-pack/packages/security-solution/data_table/tsconfig.json similarity index 92% rename from x-pack/packages/kbn-securitysolution-data-table/tsconfig.json rename to x-pack/packages/security-solution/data_table/tsconfig.json index 64d26b9c80257..da4167b70ade2 100644 --- a/x-pack/packages/kbn-securitysolution-data-table/tsconfig.json +++ b/x-pack/packages/security-solution/data_table/tsconfig.json @@ -1,5 +1,5 @@ { - "extends": "../../../tsconfig.base.json", + "extends": "../../../../tsconfig.base.json", "compilerOptions": { "outDir": "target/types", "types": ["jest", "node", "react"] diff --git a/x-pack/packages/kbn-securitysolution-data-table/utils/use_mount_appended.ts b/x-pack/packages/security-solution/data_table/utils/use_mount_appended.ts similarity index 87% rename from x-pack/packages/kbn-securitysolution-data-table/utils/use_mount_appended.ts rename to x-pack/packages/security-solution/data_table/utils/use_mount_appended.ts index d43b0455f47da..a416c66e9b9a2 100644 --- a/x-pack/packages/kbn-securitysolution-data-table/utils/use_mount_appended.ts +++ b/x-pack/packages/security-solution/data_table/utils/use_mount_appended.ts @@ -8,7 +8,9 @@ // eslint-disable-next-line import/no-extraneous-dependencies import { mount } from 'enzyme'; -type WrapperOf any> = (...args: Parameters) => ReturnType; +type WrapperOf) => ReturnType> = ( + ...args: Parameters +) => ReturnType; export type MountAppended = WrapperOf; export const useMountAppended = () => { diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/README.md b/x-pack/packages/security-solution/ecs_data_quality_dashboard/README.md similarity index 100% rename from x-pack/packages/kbn-ecs-data-quality-dashboard/README.md rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/README.md diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/compare_fields_table/ecs_allowed_values/index.test.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/compare_fields_table/ecs_allowed_values/index.test.tsx similarity index 100% rename from x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/compare_fields_table/ecs_allowed_values/index.test.tsx rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/compare_fields_table/ecs_allowed_values/index.test.tsx diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/compare_fields_table/ecs_allowed_values/index.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/compare_fields_table/ecs_allowed_values/index.tsx similarity index 100% rename from x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/compare_fields_table/ecs_allowed_values/index.tsx rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/compare_fields_table/ecs_allowed_values/index.tsx diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/compare_fields_table/get_common_table_columns/index.test.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/compare_fields_table/get_common_table_columns/index.test.tsx similarity index 100% rename from x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/compare_fields_table/get_common_table_columns/index.test.tsx rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/compare_fields_table/get_common_table_columns/index.test.tsx diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/compare_fields_table/get_common_table_columns/index.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/compare_fields_table/get_common_table_columns/index.tsx similarity index 100% rename from x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/compare_fields_table/get_common_table_columns/index.tsx rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/compare_fields_table/get_common_table_columns/index.tsx diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/compare_fields_table/get_incompatible_mappings_table_columns/index.test.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/compare_fields_table/get_incompatible_mappings_table_columns/index.test.tsx similarity index 100% rename from x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/compare_fields_table/get_incompatible_mappings_table_columns/index.test.tsx rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/compare_fields_table/get_incompatible_mappings_table_columns/index.test.tsx diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/compare_fields_table/get_incompatible_mappings_table_columns/index.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/compare_fields_table/get_incompatible_mappings_table_columns/index.tsx similarity index 100% rename from x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/compare_fields_table/get_incompatible_mappings_table_columns/index.tsx rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/compare_fields_table/get_incompatible_mappings_table_columns/index.tsx diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/compare_fields_table/helpers.test.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/compare_fields_table/helpers.test.tsx similarity index 100% rename from x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/compare_fields_table/helpers.test.tsx rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/compare_fields_table/helpers.test.tsx diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/compare_fields_table/helpers.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/compare_fields_table/helpers.tsx similarity index 100% rename from x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/compare_fields_table/helpers.tsx rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/compare_fields_table/helpers.tsx diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/compare_fields_table/index.test.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/compare_fields_table/index.test.tsx similarity index 100% rename from x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/compare_fields_table/index.test.tsx rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/compare_fields_table/index.test.tsx diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/compare_fields_table/index.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/compare_fields_table/index.tsx similarity index 100% rename from x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/compare_fields_table/index.tsx rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/compare_fields_table/index.tsx diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/compare_fields_table/index_invalid_values/index.test.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/compare_fields_table/index_invalid_values/index.test.tsx similarity index 100% rename from x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/compare_fields_table/index_invalid_values/index.test.tsx rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/compare_fields_table/index_invalid_values/index.test.tsx diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/compare_fields_table/index_invalid_values/index.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/compare_fields_table/index_invalid_values/index.tsx similarity index 100% rename from x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/compare_fields_table/index_invalid_values/index.tsx rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/compare_fields_table/index_invalid_values/index.tsx diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/compare_fields_table/translations.ts b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/compare_fields_table/translations.ts similarity index 54% rename from x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/compare_fields_table/translations.ts rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/compare_fields_table/translations.ts index 98d3e72676cdf..95b968f0cd34e 100644 --- a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/compare_fields_table/translations.ts +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/compare_fields_table/translations.ts @@ -8,67 +8,70 @@ import { i18n } from '@kbn/i18n'; export const ECS_VALUES = i18n.translate( - 'ecsDataQualityDashboard.compareFieldsTable.ecsValuesColumn', + 'securitySolutionPackages.ecsDataQualityDashboard.compareFieldsTable.ecsValuesColumn', { defaultMessage: 'ECS values', } ); export const ECS_VALUES_EXPECTED = i18n.translate( - 'ecsDataQualityDashboard.compareFieldsTable.ecsValuesExpectedColumn', + 'securitySolutionPackages.ecsDataQualityDashboard.compareFieldsTable.ecsValuesExpectedColumn', { defaultMessage: 'ECS values (expected)', } ); export const ECS_DESCRIPTION = i18n.translate( - 'ecsDataQualityDashboard.compareFieldsTable.ecsDescriptionColumn', + 'securitySolutionPackages.ecsDataQualityDashboard.compareFieldsTable.ecsDescriptionColumn', { defaultMessage: 'ECS description', } ); export const ECS_MAPPING_TYPE = i18n.translate( - 'ecsDataQualityDashboard.compareFieldsTable.ecsMappingTypeColumn', + 'securitySolutionPackages.ecsDataQualityDashboard.compareFieldsTable.ecsMappingTypeColumn', { defaultMessage: 'ECS mapping type', } ); export const ECS_MAPPING_TYPE_EXPECTED = i18n.translate( - 'ecsDataQualityDashboard.compareFieldsTable.ecsMappingTypeExpectedColumn', + 'securitySolutionPackages.ecsDataQualityDashboard.compareFieldsTable.ecsMappingTypeExpectedColumn', { defaultMessage: 'ECS mapping type (expected)', } ); export const DOCUMENT_VALUES_ACTUAL = i18n.translate( - 'ecsDataQualityDashboard.compareFieldsTable.documentValuesActualColumn', + 'securitySolutionPackages.ecsDataQualityDashboard.compareFieldsTable.documentValuesActualColumn', { defaultMessage: 'Document values (actual)', } ); export const INDEX_MAPPING_TYPE = i18n.translate( - 'ecsDataQualityDashboard.compareFieldsTable.indexMappingTypeColumn', + 'securitySolutionPackages.ecsDataQualityDashboard.compareFieldsTable.indexMappingTypeColumn', { defaultMessage: 'Index mapping type', } ); export const INDEX_MAPPING_TYPE_ACTUAL = i18n.translate( - 'ecsDataQualityDashboard.compareFieldsTable.indexMappingTypeActualColumn', + 'securitySolutionPackages.ecsDataQualityDashboard.compareFieldsTable.indexMappingTypeActualColumn', { defaultMessage: 'Index mapping type (actual)', } ); -export const FIELD = i18n.translate('ecsDataQualityDashboard.compareFieldsTable.fieldColumn', { - defaultMessage: 'Field', -}); +export const FIELD = i18n.translate( + 'securitySolutionPackages.ecsDataQualityDashboard.compareFieldsTable.fieldColumn', + { + defaultMessage: 'Field', + } +); export const SEARCH_FIELDS = i18n.translate( - 'ecsDataQualityDashboard.compareFieldsTable.searchFieldsPlaceholder', + 'securitySolutionPackages.ecsDataQualityDashboard.compareFieldsTable.searchFieldsPlaceholder', { defaultMessage: 'Search fields', } diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/allowed_values/helpers.test.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/allowed_values/helpers.test.tsx similarity index 100% rename from x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/allowed_values/helpers.test.tsx rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/allowed_values/helpers.test.tsx diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/allowed_values/helpers.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/allowed_values/helpers.tsx similarity index 100% rename from x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/allowed_values/helpers.tsx rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/allowed_values/helpers.tsx diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/body/data_quality_details/index.test.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/body/data_quality_details/index.test.tsx similarity index 100% rename from x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/body/data_quality_details/index.test.tsx rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/body/data_quality_details/index.test.tsx diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/body/data_quality_details/index.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/body/data_quality_details/index.tsx similarity index 100% rename from x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/body/data_quality_details/index.tsx rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/body/data_quality_details/index.tsx diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/body/data_quality_details/indices_details/index.test.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/body/data_quality_details/indices_details/index.test.tsx similarity index 100% rename from x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/body/data_quality_details/indices_details/index.test.tsx rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/body/data_quality_details/indices_details/index.test.tsx diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/body/data_quality_details/indices_details/index.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/body/data_quality_details/indices_details/index.tsx similarity index 100% rename from x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/body/data_quality_details/indices_details/index.tsx rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/body/data_quality_details/indices_details/index.tsx diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/body/data_quality_details/storage_details/helpers.test.ts b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/body/data_quality_details/storage_details/helpers.test.ts similarity index 100% rename from x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/body/data_quality_details/storage_details/helpers.test.ts rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/body/data_quality_details/storage_details/helpers.test.ts diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/body/data_quality_details/storage_details/helpers.ts b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/body/data_quality_details/storage_details/helpers.ts similarity index 100% rename from x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/body/data_quality_details/storage_details/helpers.ts rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/body/data_quality_details/storage_details/helpers.ts diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/body/data_quality_details/storage_details/index.test.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/body/data_quality_details/storage_details/index.test.tsx similarity index 100% rename from x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/body/data_quality_details/storage_details/index.test.tsx rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/body/data_quality_details/storage_details/index.test.tsx diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/body/data_quality_details/storage_details/index.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/body/data_quality_details/storage_details/index.tsx similarity index 100% rename from x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/body/data_quality_details/storage_details/index.tsx rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/body/data_quality_details/storage_details/index.tsx diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/body/index.test.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/body/index.test.tsx similarity index 100% rename from x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/body/index.test.tsx rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/body/index.test.tsx diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/body/index.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/body/index.tsx similarity index 100% rename from x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/body/index.tsx rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/body/index.tsx diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/data_quality_summary/check_status/index.test.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/data_quality_summary/check_status/index.test.tsx similarity index 100% rename from x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/data_quality_summary/check_status/index.test.tsx rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/data_quality_summary/check_status/index.test.tsx diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/data_quality_summary/check_status/index.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/data_quality_summary/check_status/index.tsx similarity index 100% rename from x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/data_quality_summary/check_status/index.tsx rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/data_quality_summary/check_status/index.tsx diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/data_quality_summary/errors_popover/index.test.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/data_quality_summary/errors_popover/index.test.tsx similarity index 100% rename from x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/data_quality_summary/errors_popover/index.test.tsx rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/data_quality_summary/errors_popover/index.test.tsx diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/data_quality_summary/errors_popover/index.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/data_quality_summary/errors_popover/index.tsx similarity index 100% rename from x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/data_quality_summary/errors_popover/index.tsx rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/data_quality_summary/errors_popover/index.tsx diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/data_quality_summary/errors_popover/translations.ts b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/data_quality_summary/errors_popover/translations.ts similarity index 50% rename from x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/data_quality_summary/errors_popover/translations.ts rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/data_quality_summary/errors_popover/translations.ts index 304ce658a0704..310f69ab5d584 100644 --- a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/data_quality_summary/errors_popover/translations.ts +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/data_quality_summary/errors_popover/translations.ts @@ -8,70 +8,79 @@ import { i18n } from '@kbn/i18n'; export const COPIED_ERRORS_TOAST_TITLE = i18n.translate( - 'ecsDataQualityDashboard.toasts.copiedErrorsToastTitle', + 'securitySolutionPackages.ecsDataQualityDashboard.toasts.copiedErrorsToastTitle', { defaultMessage: 'Copied errors to the clipboard', } ); export const COPY_TO_CLIPBOARD = i18n.translate( - 'ecsDataQualityDashboard.errorsPopover.copyToClipboardButton', + 'securitySolutionPackages.ecsDataQualityDashboard.errorsPopover.copyToClipboardButton', { defaultMessage: 'Copy to clipboard', } ); -export const ERRORS = i18n.translate('ecsDataQualityDashboard.errorsPopover.errorsTitle', { - defaultMessage: 'Errors', -}); +export const ERRORS = i18n.translate( + 'securitySolutionPackages.ecsDataQualityDashboard.errorsPopover.errorsTitle', + { + defaultMessage: 'Errors', + } +); export const ERRORS_CALLOUT_SUMMARY = i18n.translate( - 'ecsDataQualityDashboard.errorsPopover.errorsCalloutSummary', + 'securitySolutionPackages.ecsDataQualityDashboard.errorsPopover.errorsCalloutSummary', { defaultMessage: 'Some indices were not checked for Data Quality', } ); export const ERRORS_MAY_OCCUR = i18n.translate( - 'ecsDataQualityDashboard.errors.errorMayOccurLabel', + 'securitySolutionPackages.ecsDataQualityDashboard.errors.errorMayOccurLabel', { defaultMessage: "Errors may occur when pattern or index metadata is temporarily unavailable, or because you don't have the privileges required for access", } ); -export const MANAGE = i18n.translate('ecsDataQualityDashboard.errors.manage', { - defaultMessage: 'manage', -}); +export const MANAGE = i18n.translate( + 'securitySolutionPackages.ecsDataQualityDashboard.errors.manage', + { + defaultMessage: 'manage', + } +); -export const MONITOR = i18n.translate('ecsDataQualityDashboard.errors.monitor', { - defaultMessage: 'monitor', -}); +export const MONITOR = i18n.translate( + 'securitySolutionPackages.ecsDataQualityDashboard.errors.monitor', + { + defaultMessage: 'monitor', + } +); -export const OR = i18n.translate('ecsDataQualityDashboard.errors.or', { +export const OR = i18n.translate('securitySolutionPackages.ecsDataQualityDashboard.errors.or', { defaultMessage: 'or', }); -export const READ = i18n.translate('ecsDataQualityDashboard.errors.read', { +export const READ = i18n.translate('securitySolutionPackages.ecsDataQualityDashboard.errors.read', { defaultMessage: 'read', }); export const THE_FOLLOWING_PRIVILEGES_ARE_REQUIRED = i18n.translate( - 'ecsDataQualityDashboard.errors.theFollowingPrivilegesLabel', + 'securitySolutionPackages.ecsDataQualityDashboard.errors.theFollowingPrivilegesLabel', { defaultMessage: 'The following privileges are required to check an index:', } ); export const VIEW_ERRORS = i18n.translate( - 'ecsDataQualityDashboard.errorsPopover.viewErrorsButton', + 'securitySolutionPackages.ecsDataQualityDashboard.errorsPopover.viewErrorsButton', { defaultMessage: 'View errors', } ); export const VIEW_INDEX_METADATA = i18n.translate( - 'ecsDataQualityDashboard.errors.viewIndexMetadata', + 'securitySolutionPackages.ecsDataQualityDashboard.errors.viewIndexMetadata', { defaultMessage: 'view_index_metadata', } diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/data_quality_summary/errors_viewer/helpers.test.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/data_quality_summary/errors_viewer/helpers.test.tsx similarity index 100% rename from x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/data_quality_summary/errors_viewer/helpers.test.tsx rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/data_quality_summary/errors_viewer/helpers.test.tsx diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/data_quality_summary/errors_viewer/helpers.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/data_quality_summary/errors_viewer/helpers.tsx similarity index 100% rename from x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/data_quality_summary/errors_viewer/helpers.tsx rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/data_quality_summary/errors_viewer/helpers.tsx diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/data_quality_summary/errors_viewer/index.test.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/data_quality_summary/errors_viewer/index.test.tsx similarity index 100% rename from x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/data_quality_summary/errors_viewer/index.test.tsx rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/data_quality_summary/errors_viewer/index.test.tsx diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/data_quality_summary/errors_viewer/index.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/data_quality_summary/errors_viewer/index.tsx similarity index 100% rename from x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/data_quality_summary/errors_viewer/index.tsx rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/data_quality_summary/errors_viewer/index.tsx diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/data_quality_summary/errors_viewer/translations.ts b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/data_quality_summary/errors_viewer/translations.ts new file mode 100644 index 0000000000000..a044922748cdf --- /dev/null +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/data_quality_summary/errors_viewer/translations.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 { i18n } from '@kbn/i18n'; + +export const ERROR = i18n.translate( + 'securitySolutionPackages.ecsDataQualityDashboard.errorsViewerTable.errorColumn', + { + defaultMessage: 'Error', + } +); + +export const INDEX = i18n.translate( + 'securitySolutionPackages.ecsDataQualityDashboard.errorsViewerTable.indexColumn', + { + defaultMessage: 'Index', + } +); + +export const PATTERN = i18n.translate( + 'securitySolutionPackages.ecsDataQualityDashboard.errorsViewerTable.patternColumn', + { + defaultMessage: 'Pattern', + } +); diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/data_quality_summary/index.test.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/data_quality_summary/index.test.tsx similarity index 100% rename from x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/data_quality_summary/index.test.tsx rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/data_quality_summary/index.test.tsx diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/data_quality_summary/index.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/data_quality_summary/index.tsx similarity index 100% rename from x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/data_quality_summary/index.tsx rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/data_quality_summary/index.tsx diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/data_quality_summary/summary_actions/actions/index.test.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/data_quality_summary/summary_actions/actions/index.test.tsx similarity index 100% rename from x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/data_quality_summary/summary_actions/actions/index.test.tsx rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/data_quality_summary/summary_actions/actions/index.test.tsx diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/data_quality_summary/summary_actions/actions/index.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/data_quality_summary/summary_actions/actions/index.tsx similarity index 100% rename from x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/data_quality_summary/summary_actions/actions/index.tsx rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/data_quality_summary/summary_actions/actions/index.tsx diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/data_quality_summary/summary_actions/check_all/check_index.test.ts b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/data_quality_summary/summary_actions/check_all/check_index.test.ts similarity index 100% rename from x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/data_quality_summary/summary_actions/check_all/check_index.test.ts rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/data_quality_summary/summary_actions/check_all/check_index.test.ts diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/data_quality_summary/summary_actions/check_all/check_index.ts b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/data_quality_summary/summary_actions/check_all/check_index.ts similarity index 100% rename from x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/data_quality_summary/summary_actions/check_all/check_index.ts rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/data_quality_summary/summary_actions/check_all/check_index.ts diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/data_quality_summary/summary_actions/check_all/helpers.test.ts b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/data_quality_summary/summary_actions/check_all/helpers.test.ts similarity index 100% rename from x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/data_quality_summary/summary_actions/check_all/helpers.test.ts rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/data_quality_summary/summary_actions/check_all/helpers.test.ts diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/data_quality_summary/summary_actions/check_all/helpers.ts b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/data_quality_summary/summary_actions/check_all/helpers.ts similarity index 100% rename from x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/data_quality_summary/summary_actions/check_all/helpers.ts rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/data_quality_summary/summary_actions/check_all/helpers.ts diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/data_quality_summary/summary_actions/check_all/index.test.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/data_quality_summary/summary_actions/check_all/index.test.tsx similarity index 100% rename from x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/data_quality_summary/summary_actions/check_all/index.test.tsx rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/data_quality_summary/summary_actions/check_all/index.test.tsx diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/data_quality_summary/summary_actions/check_all/index.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/data_quality_summary/summary_actions/check_all/index.tsx similarity index 100% rename from x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/data_quality_summary/summary_actions/check_all/index.tsx rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/data_quality_summary/summary_actions/check_all/index.tsx diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/data_quality_summary/summary_actions/check_all/translations.ts b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/data_quality_summary/summary_actions/check_all/translations.ts similarity index 61% rename from x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/data_quality_summary/summary_actions/check_all/translations.ts rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/data_quality_summary/summary_actions/check_all/translations.ts index d63acdb418a17..219d1039ac290 100644 --- a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/data_quality_summary/summary_actions/check_all/translations.ts +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/data_quality_summary/summary_actions/check_all/translations.ts @@ -8,7 +8,10 @@ import { i18n } from '@kbn/i18n'; export const AN_ERROR_OCCURRED_CHECKING_INDEX = (indexName: string) => - i18n.translate('ecsDataQualityDashboard.checkAllErrorCheckingIndexMessage', { - values: { indexName }, - defaultMessage: 'An error occurred checking index {indexName}', - }); + i18n.translate( + 'securitySolutionPackages.ecsDataQualityDashboard.checkAllErrorCheckingIndexMessage', + { + values: { indexName }, + defaultMessage: 'An error occurred checking index {indexName}', + } + ); diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/data_quality_summary/summary_actions/index.test.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/data_quality_summary/summary_actions/index.test.tsx similarity index 100% rename from x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/data_quality_summary/summary_actions/index.test.tsx rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/data_quality_summary/summary_actions/index.test.tsx diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/data_quality_summary/summary_actions/index.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/data_quality_summary/summary_actions/index.tsx similarity index 100% rename from x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/data_quality_summary/summary_actions/index.tsx rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/data_quality_summary/summary_actions/index.tsx diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/error_empty_prompt/index.test.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/error_empty_prompt/index.test.tsx similarity index 100% rename from x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/error_empty_prompt/index.test.tsx rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/error_empty_prompt/index.test.tsx diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/error_empty_prompt/index.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/error_empty_prompt/index.tsx similarity index 100% rename from x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/error_empty_prompt/index.tsx rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/error_empty_prompt/index.tsx diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/ilm_phase_counts/index.test.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/ilm_phase_counts/index.test.tsx similarity index 100% rename from x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/ilm_phase_counts/index.test.tsx rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/ilm_phase_counts/index.test.tsx diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/ilm_phase_counts/index.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/ilm_phase_counts/index.tsx similarity index 100% rename from x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/ilm_phase_counts/index.tsx rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/ilm_phase_counts/index.tsx diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/index_properties/empty_prompt_body.test.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/index_properties/empty_prompt_body.test.tsx similarity index 100% rename from x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/index_properties/empty_prompt_body.test.tsx rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/index_properties/empty_prompt_body.test.tsx diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/index_properties/empty_prompt_body.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/index_properties/empty_prompt_body.tsx similarity index 100% rename from x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/index_properties/empty_prompt_body.tsx rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/index_properties/empty_prompt_body.tsx diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/index_properties/empty_prompt_title.test.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/index_properties/empty_prompt_title.test.tsx similarity index 100% rename from x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/index_properties/empty_prompt_title.test.tsx rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/index_properties/empty_prompt_title.test.tsx diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/index_properties/empty_prompt_title.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/index_properties/empty_prompt_title.tsx similarity index 100% rename from x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/index_properties/empty_prompt_title.tsx rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/index_properties/empty_prompt_title.tsx diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/index_properties/helpers.test.ts b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/index_properties/helpers.test.ts similarity index 100% rename from x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/index_properties/helpers.test.ts rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/index_properties/helpers.test.ts diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/index_properties/helpers.ts b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/index_properties/helpers.ts similarity index 100% rename from x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/index_properties/helpers.ts rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/index_properties/helpers.ts diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/index_properties/index.test.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/index_properties/index.test.tsx similarity index 100% rename from x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/index_properties/index.test.tsx rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/index_properties/index.test.tsx diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/index_properties/index.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/index_properties/index.tsx similarity index 100% rename from x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/index_properties/index.tsx rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/index_properties/index.tsx diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/index_properties/markdown/helpers.test.ts b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/index_properties/markdown/helpers.test.ts similarity index 100% rename from x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/index_properties/markdown/helpers.test.ts rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/index_properties/markdown/helpers.test.ts diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/index_properties/markdown/helpers.ts b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/index_properties/markdown/helpers.ts similarity index 100% rename from x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/index_properties/markdown/helpers.ts rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/index_properties/markdown/helpers.ts diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/index_properties/translations.ts b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/index_properties/translations.ts new file mode 100644 index 0000000000000..4e2378ce3d317 --- /dev/null +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/index_properties/translations.ts @@ -0,0 +1,451 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license 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 ADD_TO_NEW_CASE = i18n.translate( + 'securitySolutionPackages.ecsDataQualityDashboard.indexProperties.addToNewCaseButton', + { + defaultMessage: 'Add to new case', + } +); + +export const ALL_FIELDS = i18n.translate( + 'securitySolutionPackages.ecsDataQualityDashboard.indexProperties.allFieldsLabel', + { + defaultMessage: 'All fields', + } +); + +export const ALL_CALLOUT = (version: string) => + i18n.translate('securitySolutionPackages.ecsDataQualityDashboard.indexProperties.allCallout', { + values: { version }, + defaultMessage: + "All mappings for the fields in this index, including fields that comply with the Elastic Common Schema (ECS), version {version}, and fields that don't", + }); + +export const ALL_CALLOUT_TITLE = (fieldCount: number) => + i18n.translate( + 'securitySolutionPackages.ecsDataQualityDashboard.indexProperties.allCalloutTitle', + { + values: { fieldCount }, + defaultMessage: + 'All {fieldCount} {fieldCount, plural, =1 {field mapping} other {field mappings}}', + } + ); + +export const ALL_EMPTY = i18n.translate( + 'securitySolutionPackages.ecsDataQualityDashboard.indexProperties.allCalloutEmptyContent', + { + defaultMessage: 'This index does not contain any mappings', + } +); + +export const ALL_EMPTY_TITLE = i18n.translate( + 'securitySolutionPackages.ecsDataQualityDashboard.indexProperties.allCalloutEmptyTitle', + { + defaultMessage: 'No mappings', + } +); + +export const ALL_FIELDS_TABLE_TITLE = (indexName: string) => + i18n.translate('securitySolutionPackages.ecsDataQualityDashboard.allTab.allFieldsTableTitle', { + values: { indexName }, + defaultMessage: 'All fields - {indexName}', + }); + +export const SUMMARY_MARKDOWN_TITLE = i18n.translate( + 'securitySolutionPackages.ecsDataQualityDashboard.indexProperties.summaryMarkdownTitle', + { + defaultMessage: 'Data quality', + } +); + +export const SUMMARY_MARKDOWN_DESCRIPTION = ({ + ecsFieldReferenceUrl, + ecsReferenceUrl, + indexName, + mappingUrl, + version, +}: { + ecsFieldReferenceUrl: string; + ecsReferenceUrl: string; + indexName: string; + mappingUrl: string; + version: string; +}) => + i18n.translate( + 'securitySolutionPackages.ecsDataQualityDashboard.indexProperties.summaryMarkdownDescription', + { + values: { ecsFieldReferenceUrl, ecsReferenceUrl, indexName, mappingUrl, version }, + defaultMessage: + 'The `{indexName}` index has [mappings]({mappingUrl}) or field values that are different than the [Elastic Common Schema]({ecsReferenceUrl}) (ECS), version `{version}` [definitions]({ecsFieldReferenceUrl}).', + } + ); + +export const COPY_TO_CLIPBOARD = i18n.translate( + 'securitySolutionPackages.ecsDataQualityDashboard.indexProperties.copyToClipboardButton', + { + defaultMessage: 'Copy to clipboard', + } +); + +export const CUSTOM_FIELDS_TABLE_TITLE = (indexName: string) => + i18n.translate( + 'securitySolutionPackages.ecsDataQualityDashboard.customTab.customFieldsTableTitle', + { + values: { indexName }, + defaultMessage: 'Custom fields - {indexName}', + } + ); + +export const CUSTOM_DETECTION_ENGINE_RULES_WORK = i18n.translate( + 'securitySolutionPackages.ecsDataQualityDashboard.indexProperties.custonDetectionEngineRulesWorkMessage', + { + defaultMessage: '✅ Custom detection engine rules work', + } +); + +export const DOCS = i18n.translate( + 'securitySolutionPackages.ecsDataQualityDashboard.indexProperties.docsLabel', + { + defaultMessage: 'Docs', + } +); + +export const ECS_COMPLIANT_FIELDS = i18n.translate( + 'securitySolutionPackages.ecsDataQualityDashboard.indexProperties.ecsCompliantFieldsLabel', + { + defaultMessage: 'ECS compliant fields', + } +); + +export const ECS_COMPLIANT_CALLOUT = ({ + fieldCount, + version, +}: { + fieldCount: number; + version: string; +}) => + i18n.translate( + 'securitySolutionPackages.ecsDataQualityDashboard.indexProperties.ecsCompliantCallout', + { + values: { fieldCount, version }, + defaultMessage: + 'The {fieldCount, plural, =1 {index mapping type and document values for this field comply} other {index mapping types and document values of these fields comply}} with the Elastic Common Schema (ECS), version {version}', + } + ); + +export const ECS_COMPLIANT_CALLOUT_TITLE = (fieldCount: number) => + i18n.translate( + 'securitySolutionPackages.ecsDataQualityDashboard.indexProperties.ecsCompliantCalloutTitle', + { + values: { fieldCount }, + defaultMessage: '{fieldCount} ECS compliant {fieldCount, plural, =1 {field} other {fields}}', + } + ); + +export const ECS_COMPLIANT_EMPTY = i18n.translate( + 'securitySolutionPackages.ecsDataQualityDashboard.indexProperties.ecsCompliantEmptyContent', + { + defaultMessage: + 'None of the field mappings in this index comply with the Elastic Common Schema (ECS). The index must (at least) contain an @timestamp date field.', + } +); + +export const ECS_VERSION_MARKDOWN_COMMENT = i18n.translate( + 'securitySolutionPackages.ecsDataQualityDashboard.indexProperties.ecsVersionMarkdownComment', + { + defaultMessage: 'Elastic Common Schema (ECS) version', + } +); + +export const INDEX = i18n.translate( + 'securitySolutionPackages.ecsDataQualityDashboard.indexProperties.indexMarkdown', + { + defaultMessage: 'Index', + } +); + +export const ECS_COMPLIANT_EMPTY_TITLE = i18n.translate( + 'securitySolutionPackages.ecsDataQualityDashboard.indexProperties.ecsCompliantEmptyTitle', + { + defaultMessage: 'No ECS compliant Mappings', + } +); + +export const ECS_COMPLIANT_MAPPINGS_ARE_FULLY_SUPPORTED = i18n.translate( + 'securitySolutionPackages.ecsDataQualityDashboard.indexProperties.ecsCompliantMappingsAreFullySupportedMessage', + { + defaultMessage: '✅ ECS compliant mappings and field values are fully supported', + } +); + +export const ERROR_LOADING_MAPPINGS_TITLE = i18n.translate( + 'securitySolutionPackages.ecsDataQualityDashboard.emptyErrorPrompt.errorLoadingMappingsTitle', + { + defaultMessage: 'Unable to load index mappings', + } +); + +export const ERROR_LOADING_MAPPINGS_BODY = (error: string) => + i18n.translate( + 'securitySolutionPackages.ecsDataQualityDashboard.emptyErrorPrompt.errorLoadingMappingsBody', + { + values: { error }, + defaultMessage: 'There was a problem loading mappings: {error}', + } + ); + +export const ERROR_LOADING_UNALLOWED_VALUES_BODY = (error: string) => + i18n.translate( + 'securitySolutionPackages.ecsDataQualityDashboard.emptyErrorPrompt.errorLoadingUnallowedValuesBody', + { + values: { error }, + defaultMessage: 'There was a problem loading unallowed values: {error}', + } + ); + +export const ERROR_LOADING_UNALLOWED_VALUES_TITLE = i18n.translate( + 'securitySolutionPackages.ecsDataQualityDashboard.emptyErrorPrompt.errorLoadingUnallowedValuesTitle', + { + defaultMessage: 'Unable to load unallowed values', + } +); + +export const ECS_COMPLIANT_FIELDS_TABLE_TITLE = (indexName: string) => + i18n.translate( + 'securitySolutionPackages.ecsDataQualityDashboard.customTab.ecsComplaintFieldsTableTitle', + { + values: { indexName }, + defaultMessage: 'ECS complaint fields - {indexName}', + } + ); + +export const LOADING_MAPPINGS = i18n.translate( + 'securitySolutionPackages.ecsDataQualityDashboard.emptyLoadingPrompt.loadingMappingsPrompt', + { + defaultMessage: 'Loading mappings', + } +); + +export const LOADING_UNALLOWED_VALUES = i18n.translate( + 'securitySolutionPackages.ecsDataQualityDashboard.emptyLoadingPrompt.loadingUnallowedValuesPrompt', + { + defaultMessage: 'Loading unallowed values', + } +); + +export const SUMMARY = i18n.translate( + 'securitySolutionPackages.ecsDataQualityDashboard.indexProperties.summaryTab', + { + defaultMessage: 'Summary', + } +); + +export const MISSING_TIMESTAMP_CALLOUT = i18n.translate( + 'securitySolutionPackages.ecsDataQualityDashboard.indexProperties.missingTimestampCallout', + { + defaultMessage: + 'Consider adding an @timestamp (date) field mapping to this index, as required by the Elastic Common Schema (ECS), because:', + } +); + +export const MISSING_TIMESTAMP_CALLOUT_TITLE = i18n.translate( + 'securitySolutionPackages.ecsDataQualityDashboard.indexProperties.missingTimestampCalloutTitle', + { + defaultMessage: 'Missing an @timestamp (date) field mapping for this index', + } +); + +export const CUSTOM_FIELDS = i18n.translate( + 'securitySolutionPackages.ecsDataQualityDashboard.indexProperties.customFieldsLabel', + { + defaultMessage: 'Custom fields', + } +); + +export const CUSTOM_CALLOUT = ({ fieldCount, version }: { fieldCount: number; version: string }) => + i18n.translate('securitySolutionPackages.ecsDataQualityDashboard.indexProperties.customCallout', { + values: { fieldCount, version }, + defaultMessage: + '{fieldCount, plural, =1 {This field is not} other {These fields are not}} defined by the Elastic Common Schema (ECS), version {version}.', + }); + +export const CUSTOM_CALLOUT_TITLE = (fieldCount: number) => + i18n.translate( + 'securitySolutionPackages.ecsDataQualityDashboard.indexProperties.customCalloutTitle', + { + values: { fieldCount }, + defaultMessage: + '{fieldCount} Custom {fieldCount, plural, =1 {field mapping} other {field mappings}}', + } + ); + +export const CUSTOM_EMPTY = i18n.translate( + 'securitySolutionPackages.ecsDataQualityDashboard.indexProperties.customEmptyContent', + { + defaultMessage: 'All the field mappings in this index are defined by the Elastic Common Schema', + } +); + +export const CUSTOM_EMPTY_TITLE = i18n.translate( + 'securitySolutionPackages.ecsDataQualityDashboard.indexProperties.customEmptyTitle', + { + defaultMessage: 'All field mappings defined by ECS', + } +); + +export const INCOMPATIBLE_FIELDS = i18n.translate( + 'securitySolutionPackages.ecsDataQualityDashboard.indexProperties.incompatibleFieldsTab', + { + defaultMessage: 'Incompatible fields', + } +); + +export const INCOMPATIBLE_CALLOUT = ({ + fieldCount, + version, +}: { + fieldCount: number; + version: string; +}) => + i18n.translate( + 'securitySolutionPackages.ecsDataQualityDashboard.indexProperties.incompatibleCallout', + { + values: { version }, + defaultMessage: + "Fields are incompatible with ECS when index mappings, or the values of the fields in the index, don't conform to the Elastic Common Schema (ECS), version {version}.", + } + ); + +export const INCOMPATIBLE_FIELDS_WITH = i18n.translate( + 'securitySolutionPackages.ecsDataQualityDashboard.indexProperties.incompatibleCallout.incompatibleFieldsWithLabel', + { + defaultMessage: + 'Incompatible fields with mappings in the same family have exactly the same search behavior but may have different space usage or performance characteristics.', + } +); + +export const WHEN_AN_INCOMPATIBLE_FIELD = i18n.translate( + 'securitySolutionPackages.ecsDataQualityDashboard.indexProperties.incompatibleCallout.whenAnIncompatibleFieldLabel', + { + defaultMessage: 'When an incompatible field is not in the same family:', + } +); + +export const INCOMPATIBLE_CALLOUT_TITLE = ({ + fieldCount, + fieldsInSameFamily, +}: { + fieldCount: number; + fieldsInSameFamily: number; +}) => + i18n.translate( + 'securitySolutionPackages.ecsDataQualityDashboard.indexProperties.incompatibleCalloutTitle', + { + values: { fieldCount, fieldsInSameFamily }, + defaultMessage: + '{fieldCount} incompatible {fieldCount, plural, =1 {field} other {fields}}, {fieldsInSameFamily} {fieldsInSameFamily, plural, =1 {field} other {fields}} with {fieldsInSameFamily, plural, =1 {a mapping} other {mappings}} in the same family', + } + ); + +export const INCOMPATIBLE_EMPTY = i18n.translate( + 'securitySolutionPackages.ecsDataQualityDashboard.indexProperties.incompatibleEmptyContent', + { + defaultMessage: + 'All of the field mappings and document values in this index are compliant with the Elastic Common Schema (ECS).', + } +); + +export const INCOMPATIBLE_EMPTY_TITLE = i18n.translate( + 'securitySolutionPackages.ecsDataQualityDashboard.indexProperties.incompatibleEmptyTitle', + { + defaultMessage: 'All field mappings and values are ECS compliant', + } +); + +export const DETECTION_ENGINE_RULES_WILL_WORK = i18n.translate( + 'securitySolutionPackages.ecsDataQualityDashboard.indexProperties.detectionEngineRulesWillWorkMessage', + { + defaultMessage: '✅ Detection engine rules will work for these fields', + } +); + +export const DETECTION_ENGINE_RULES_MAY_NOT_MATCH = i18n.translate( + 'securitySolutionPackages.ecsDataQualityDashboard.indexProperties.detectionEngineRulesWontWorkMessage', + { + defaultMessage: + '❌ Detection engine rules referencing these fields may not match them correctly', + } +); + +export const OTHER_APP_CAPABILITIES_WORK_PROPERLY = i18n.translate( + 'securitySolutionPackages.ecsDataQualityDashboard.indexProperties.otherAppCapabilitiesWorkProperlyMessage', + { + defaultMessage: '✅ Other app capabilities work properly', + } +); + +export const PAGES_DISPLAY_EVENTS = i18n.translate( + 'securitySolutionPackages.ecsDataQualityDashboard.indexProperties.pagesDisplayEventsMessage', + { + defaultMessage: '✅ Pages display events and fields correctly', + } +); + +export const PAGES_MAY_NOT_DISPLAY_FIELDS = i18n.translate( + 'securitySolutionPackages.ecsDataQualityDashboard.indexProperties.pagesMayNotDisplayFieldsMessage', + { + defaultMessage: '🌕 Some pages and features may not display these fields', + } +); + +export const PAGES_MAY_NOT_DISPLAY_EVENTS = i18n.translate( + 'securitySolutionPackages.ecsDataQualityDashboard.indexProperties.pagesMayNotDisplayEventsMessage', + { + defaultMessage: + '❌ Pages may not display some events or fields due to unexpected field mappings or values', + } +); + +export const PRE_BUILT_DETECTION_ENGINE_RULES_WORK = i18n.translate( + 'securitySolutionPackages.ecsDataQualityDashboard.indexProperties.preBuiltDetectionEngineRulesWorkMessage', + { + defaultMessage: '✅ Pre-built detection engine rules work', + } +); + +export const ECS_IS_A_PERMISSIVE_SCHEMA = i18n.translate( + 'securitySolutionPackages.ecsDataQualityDashboard.indexProperties.ecsIsAPermissiveSchemaMessage', + { + defaultMessage: + 'ECS is a permissive schema. If your events have additional data that cannot be mapped to ECS, you can simply add them to your events, using custom field names.', + } +); + +export const SOMETIMES_INDICES_CREATED_BY_OLDER = i18n.translate( + 'securitySolutionPackages.ecsDataQualityDashboard.indexProperties.sometimesIndicesCreatedByOlderDescription', + { + defaultMessage: + 'Sometimes, indices created by older integrations will have mappings or values that were, but are no longer compliant.', + } +); + +export const MAPPINGS_THAT_CONFLICT_WITH_ECS = i18n.translate( + 'securitySolutionPackages.ecsDataQualityDashboard.indexProperties.mappingThatConflictWithEcsMessage', + { + defaultMessage: "❌ Mappings or field values that don't comply with ECS are not supported", + } +); + +export const UNKNOWN = i18n.translate( + 'securitySolutionPackages.ecsDataQualityDashboard.indexProperties.unknownCategoryLabel', + { + defaultMessage: 'Unknown', + } +); diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/loading_empty_prompt/index.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/loading_empty_prompt/index.tsx similarity index 100% rename from x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/loading_empty_prompt/index.tsx rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/loading_empty_prompt/index.tsx diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/pattern/helpers.test.ts b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/pattern/helpers.test.ts similarity index 100% rename from x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/pattern/helpers.test.ts rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/pattern/helpers.test.ts diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/pattern/helpers.ts b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/pattern/helpers.ts similarity index 100% rename from x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/pattern/helpers.ts rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/pattern/helpers.ts diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/pattern/index.test.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/pattern/index.test.tsx similarity index 100% rename from x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/pattern/index.test.tsx rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/pattern/index.test.tsx diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/pattern/index.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/pattern/index.tsx similarity index 100% rename from x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/pattern/index.tsx rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/pattern/index.tsx diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/pattern/pattern_summary/index.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/pattern/pattern_summary/index.tsx similarity index 100% rename from x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/pattern/pattern_summary/index.tsx rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/pattern/pattern_summary/index.tsx diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/pattern/pattern_summary/pattern_label/helpers.test.ts b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/pattern/pattern_summary/pattern_label/helpers.test.ts similarity index 100% rename from x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/pattern/pattern_summary/pattern_label/helpers.test.ts rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/pattern/pattern_summary/pattern_label/helpers.test.ts diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/pattern/pattern_summary/pattern_label/helpers.ts b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/pattern/pattern_summary/pattern_label/helpers.ts similarity index 100% rename from x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/pattern/pattern_summary/pattern_label/helpers.ts rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/pattern/pattern_summary/pattern_label/helpers.ts diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/pattern/pattern_summary/pattern_label/index.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/pattern/pattern_summary/pattern_label/index.tsx similarity index 100% rename from x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/pattern/pattern_summary/pattern_label/index.tsx rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/pattern/pattern_summary/pattern_label/index.tsx diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/pattern/pattern_summary/pattern_label/translations.ts b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/pattern/pattern_summary/pattern_label/translations.ts similarity index 59% rename from x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/pattern/pattern_summary/pattern_label/translations.ts rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/pattern/pattern_summary/pattern_label/translations.ts index cdd9c897ed326..165dc45b3f1c0 100644 --- a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/pattern/pattern_summary/pattern_label/translations.ts +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/pattern/pattern_summary/pattern_label/translations.ts @@ -7,19 +7,22 @@ import { i18n } from '@kbn/i18n'; -export const ALL_PASSED = i18n.translate('ecsDataQualityDashboard.patternLabel.allPassedTooltip', { - defaultMessage: 'All indices matching this pattern passed the data quality checks', -}); +export const ALL_PASSED = i18n.translate( + 'securitySolutionPackages.ecsDataQualityDashboard.patternLabel.allPassedTooltip', + { + defaultMessage: 'All indices matching this pattern passed the data quality checks', + } +); export const SOME_FAILED = i18n.translate( - 'ecsDataQualityDashboard.patternLabel.someFailedTooltip', + 'securitySolutionPackages.ecsDataQualityDashboard.patternLabel.someFailedTooltip', { defaultMessage: 'Some indices matching this pattern failed the data quality checks', } ); export const SOME_UNCHECKED = i18n.translate( - 'ecsDataQualityDashboard.patternLabel.someUncheckedTooltip', + 'securitySolutionPackages.ecsDataQualityDashboard.patternLabel.someUncheckedTooltip', { defaultMessage: 'Some indices matching this pattern have not been checked for data quality', } diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/pattern/pattern_summary/stats_rollup/index.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/pattern/pattern_summary/stats_rollup/index.tsx similarity index 100% rename from x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/pattern/pattern_summary/stats_rollup/index.tsx rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/pattern/pattern_summary/stats_rollup/index.tsx diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/pattern/pattern_summary/translations.ts b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/pattern/pattern_summary/translations.ts new file mode 100644 index 0000000000000..244fc257d2797 --- /dev/null +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/pattern/pattern_summary/translations.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 { i18n } from '@kbn/i18n'; + +export const DOCS = i18n.translate( + 'securitySolutionPackages.ecsDataQualityDashboard.patternSummary.docsLabel', + { + defaultMessage: 'Docs', + } +); + +export const INDICES = i18n.translate( + 'securitySolutionPackages.ecsDataQualityDashboard.patternSummary.indicesLabel', + { + defaultMessage: 'Indices', + } +); + +export const PATTERN_OR_INDEX_TOOLTIP = i18n.translate( + 'securitySolutionPackages.ecsDataQualityDashboard.patternSummary.patternOrIndexTooltip', + { + defaultMessage: 'A pattern or specific index', + } +); diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/pattern/translations.ts b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/pattern/translations.ts new file mode 100644 index 0000000000000..aaf11b1ad405c --- /dev/null +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/pattern/translations.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 { i18n } from '@kbn/i18n'; + +export const ERROR_LOADING_METADATA_TITLE = (pattern: string) => + i18n.translate( + 'securitySolutionPackages.ecsDataQualityDashboard.emptyErrorPrompt.errorLoadingMetadataTitle', + { + values: { pattern }, + defaultMessage: "Indices matching the {pattern} pattern won't be checked", + } + ); + +export const LOADING_STATS = i18n.translate( + 'securitySolutionPackages.ecsDataQualityDashboard.emptyLoadingPrompt.loadingStatsPrompt', + { + defaultMessage: 'Loading stats', + } +); diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/remote_clusters_callout/index.test.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/remote_clusters_callout/index.test.tsx similarity index 100% rename from x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/remote_clusters_callout/index.test.tsx rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/remote_clusters_callout/index.test.tsx diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/remote_clusters_callout/index.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/remote_clusters_callout/index.tsx similarity index 100% rename from x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/remote_clusters_callout/index.tsx rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/remote_clusters_callout/index.tsx diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/remote_clusters_callout/translations.ts b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/remote_clusters_callout/translations.ts similarity index 62% rename from x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/remote_clusters_callout/translations.ts rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/remote_clusters_callout/translations.ts index be355c6e5a71c..6c252e66dfb4d 100644 --- a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/remote_clusters_callout/translations.ts +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/remote_clusters_callout/translations.ts @@ -7,12 +7,15 @@ import { i18n } from '@kbn/i18n'; -export const TITLE = i18n.translate('ecsDataQualityDashboard.remoteClustersCallout.title', { - defaultMessage: "Remote clusters won't be checked", -}); +export const TITLE = i18n.translate( + 'securitySolutionPackages.ecsDataQualityDashboard.remoteClustersCallout.title', + { + defaultMessage: "Remote clusters won't be checked", + } +); export const TO_CHECK_INDICES_ON_REMOTE_CLUSTERS = i18n.translate( - 'ecsDataQualityDashboard.remoteClustersCallout.toCheckIndicesOnRemoteClustersLabel', + 'securitySolutionPackages.ecsDataQualityDashboard.remoteClustersCallout.toCheckIndicesOnRemoteClustersLabel', { defaultMessage: "To check indices on remote clusters supporting cross-cluster search, log in to the remote cluster's Kibana", diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/same_family/index.test.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/same_family/index.test.tsx similarity index 100% rename from x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/same_family/index.test.tsx rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/same_family/index.test.tsx diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/same_family/index.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/same_family/index.tsx similarity index 100% rename from x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/same_family/index.tsx rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/same_family/index.tsx diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/same_family/translations.ts b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/same_family/translations.ts similarity index 63% rename from x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/same_family/translations.ts rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/same_family/translations.ts index 1b4e05f01d635..b26a7386e8e4b 100644 --- a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/same_family/translations.ts +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/same_family/translations.ts @@ -7,6 +7,9 @@ import { i18n } from '@kbn/i18n'; -export const SAME_FAMILY = i18n.translate('ecsDataQualityDashboard.sameFamilyBadgeLabel', { - defaultMessage: 'same family', -}); +export const SAME_FAMILY = i18n.translate( + 'securitySolutionPackages.ecsDataQualityDashboard.sameFamilyBadgeLabel', + { + defaultMessage: 'same family', + } +); diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/stat_label/index.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/stat_label/index.tsx similarity index 100% rename from x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/stat_label/index.tsx rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/stat_label/index.tsx diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/stat_label/translations.ts b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/stat_label/translations.ts new file mode 100644 index 0000000000000..df91b4ed562e9 --- /dev/null +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/stat_label/translations.ts @@ -0,0 +1,174 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license 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 CHECKED = i18n.translate( + 'securitySolutionPackages.ecsDataQualityDashboard.statLabels.checkedLabel', + { + defaultMessage: 'checked', + } +); + +export const CUSTOM = i18n.translate( + 'securitySolutionPackages.ecsDataQualityDashboard.statLabels.customLabel', + { + defaultMessage: 'Custom', + } +); + +export const CUSTOM_INDEX_TOOL_TIP = (indexName: string) => + i18n.translate('securitySolutionPackages.ecsDataQualityDashboard.statLabels.customIndexToolTip', { + values: { indexName }, + defaultMessage: 'A count of the custom field mappings in the {indexName} index', + }); + +export const CUSTOM_PATTERN_TOOL_TIP = (pattern: string) => + i18n.translate( + 'securitySolutionPackages.ecsDataQualityDashboard.statLabels.customPatternToolTip', + { + values: { pattern }, + defaultMessage: + 'The total count of custom field mappings, in indices matching the {pattern} pattern', + } + ); + +export const DOCS = i18n.translate( + 'securitySolutionPackages.ecsDataQualityDashboard.statLabels.docsLabel', + { + defaultMessage: 'Docs', + } +); + +export const FIELDS = i18n.translate( + 'securitySolutionPackages.ecsDataQualityDashboard.statLabels.fieldsLabel', + { + defaultMessage: 'fields', + } +); + +export const INCOMPATIBLE = i18n.translate( + 'securitySolutionPackages.ecsDataQualityDashboard.statLabels.incompatibleLabel', + { + defaultMessage: 'Incompatible', + } +); + +export const INCOMPATIBLE_INDEX_TOOL_TIP = (indexName: string) => + i18n.translate( + 'securitySolutionPackages.ecsDataQualityDashboard.statLabels.incompatibleIndexToolTip', + { + values: { indexName }, + defaultMessage: 'Mappings and values incompatible with ECS, in the {indexName} index', + } + ); + +export const INCOMPATIBLE_PATTERN_TOOL_TIP = (pattern: string) => + i18n.translate( + 'securitySolutionPackages.ecsDataQualityDashboard.statLabels.incompatiblePatternToolTip', + { + values: { pattern }, + defaultMessage: + 'The total count of fields incompatible with ECS, in indices matching the {pattern} pattern', + } + ); + +export const INDEX_DOCS_COUNT_TOOL_TIP = (indexName: string) => + i18n.translate( + 'securitySolutionPackages.ecsDataQualityDashboard.statLabels.indexDocsCountToolTip', + { + values: { indexName }, + defaultMessage: 'A count of the docs in the {indexName} index', + } + ); + +export const INDEX_DOCS_PATTERN_TOOL_TIP = (pattern: string) => + i18n.translate( + 'securitySolutionPackages.ecsDataQualityDashboard.statLabels.indexDocsPatternToolTip', + { + values: { pattern }, + defaultMessage: 'The total count of docs, in indices matching the {pattern} pattern', + } + ); + +export const INDICES = i18n.translate( + 'securitySolutionPackages.ecsDataQualityDashboard.statLabels.indicesLabel', + { + defaultMessage: 'Indices', + } +); + +export const SIZE = i18n.translate( + 'securitySolutionPackages.ecsDataQualityDashboard.statLabels.sizeLabel', + { + defaultMessage: 'Size', + } +); + +export const INDICES_SIZE_PATTERN_TOOL_TIP = (pattern: string) => + i18n.translate( + 'securitySolutionPackages.ecsDataQualityDashboard.statLabels.indicesSizePatternToolTip', + { + values: { pattern }, + defaultMessage: + 'The total size of the primary indices matching the {pattern} pattern (does not include replicas)', + } + ); + +export const TOTAL_COUNT_OF_INDICES_CHECKED_MATCHING_PATTERN_TOOL_TIP = (pattern: string) => + i18n.translate( + 'securitySolutionPackages.ecsDataQualityDashboard.statLabels.totalCountOfIndicesCheckedMatchingPatternToolTip', + { + values: { pattern }, + defaultMessage: 'The total count of indices checked that match the {pattern} pattern', + } + ); + +export const TOTAL_COUNT_OF_INDICES_MATCHING_PATTERN_TOOL_TIP = (pattern: string) => + i18n.translate( + 'securitySolutionPackages.ecsDataQualityDashboard.statLabels.totalCountOfIndicesMatchingPatternToolTip', + { + values: { pattern }, + defaultMessage: 'The total count of indices matching the {pattern} pattern', + } + ); + +export const TOTAL_DOCS_TOOL_TIP = i18n.translate( + 'securitySolutionPackages.ecsDataQualityDashboard.statLabels.totalDocsToolTip', + { + defaultMessage: 'The total count of docs, in all indices', + } +); + +export const TOTAL_INCOMPATIBLE_TOOL_TIP = i18n.translate( + 'securitySolutionPackages.ecsDataQualityDashboard.statLabels.totalIncompatibleToolTip', + { + defaultMessage: + 'The total count of fields incompatible with ECS, in all indices that were checked', + } +); + +export const TOTAL_INDICES_CHECKED_TOOL_TIP = i18n.translate( + 'securitySolutionPackages.ecsDataQualityDashboard.statLabels.totalIndicesCheckedToolTip', + { + defaultMessage: 'The total count of all indices checked', + } +); + +export const TOTAL_INDICES_TOOL_TIP = i18n.translate( + 'securitySolutionPackages.ecsDataQualityDashboard.statLabels.totalIndicesToolTip', + { + defaultMessage: 'The total count of all indices', + } +); + +export const TOTAL_SIZE_TOOL_TIP = i18n.translate( + 'securitySolutionPackages.ecsDataQualityDashboard.statLabels.totalSizeToolTip', + { + defaultMessage: 'The total size of all primary indices (does not include replicas)', + } +); diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/storage_treemap/index.test.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/storage_treemap/index.test.tsx similarity index 100% rename from x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/storage_treemap/index.test.tsx rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/storage_treemap/index.test.tsx diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/storage_treemap/index.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/storage_treemap/index.tsx similarity index 100% rename from x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/storage_treemap/index.tsx rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/storage_treemap/index.tsx diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/storage_treemap/no_data/index.test.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/storage_treemap/no_data/index.test.tsx similarity index 100% rename from x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/storage_treemap/no_data/index.test.tsx rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/storage_treemap/no_data/index.test.tsx diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/storage_treemap/no_data/index.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/storage_treemap/no_data/index.tsx similarity index 100% rename from x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/storage_treemap/no_data/index.tsx rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/storage_treemap/no_data/index.tsx diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/storage_treemap/translations.ts b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/storage_treemap/translations.ts new file mode 100644 index 0000000000000..eb5aec996cb01 --- /dev/null +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/storage_treemap/translations.ts @@ -0,0 +1,26 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { i18n } from '@kbn/i18n'; + +export const NO_DATA_LABEL = i18n.translate( + 'securitySolutionPackages.ecsDataQualityDashboard.storageTreemap.noDataLabel', + { + defaultMessage: 'No data to display', + } +); + +export const NO_DATA_REASON_LABEL = (stackByField1: string) => + i18n.translate( + 'securitySolutionPackages.ecsDataQualityDashboard.storageTreemap.noDataReasonLabel', + { + values: { + stackByField1, + }, + defaultMessage: 'The {stackByField1} field was not present in any groups', + } + ); diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/summary_table/helpers.test.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/summary_table/helpers.test.tsx similarity index 100% rename from x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/summary_table/helpers.test.tsx rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/summary_table/helpers.test.tsx diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/summary_table/helpers.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/summary_table/helpers.tsx similarity index 100% rename from x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/summary_table/helpers.tsx rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/summary_table/helpers.tsx diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/summary_table/index.test.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/summary_table/index.test.tsx similarity index 100% rename from x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/summary_table/index.test.tsx rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/summary_table/index.test.tsx diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/summary_table/index.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/summary_table/index.tsx similarity index 100% rename from x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/summary_table/index.tsx rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/summary_table/index.tsx diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/summary_table/translations.ts b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/summary_table/translations.ts new file mode 100644 index 0000000000000..0101708db9f9d --- /dev/null +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/summary_table/translations.ts @@ -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 { i18n } from '@kbn/i18n'; + +export const COLLAPSE = i18n.translate( + 'securitySolutionPackages.ecsDataQualityDashboard.summaryTable.collapseLabel', + { + defaultMessage: 'Collapse', + } +); + +export const DOCS = i18n.translate( + 'securitySolutionPackages.ecsDataQualityDashboard.summaryTable.docsColumn', + { + defaultMessage: 'Docs', + } +); + +export const EXPAND = i18n.translate( + 'securitySolutionPackages.ecsDataQualityDashboard.summaryTable.expandLabel', + { + defaultMessage: 'Expand', + } +); + +export const EXPAND_ROWS = i18n.translate( + 'securitySolutionPackages.ecsDataQualityDashboard.summaryTable.expandRowsColumn', + { + defaultMessage: 'Expand rows', + } +); + +export const FAILED = i18n.translate( + 'securitySolutionPackages.ecsDataQualityDashboard.summaryTable.failedTooltip', + { + defaultMessage: 'Failed', + } +); + +export const ILM_PHASE = i18n.translate( + 'securitySolutionPackages.ecsDataQualityDashboard.summaryTable.ilmPhaseColumn', + { + defaultMessage: 'ILM Phase', + } +); + +export const INCOMPATIBLE_FIELDS = i18n.translate( + 'securitySolutionPackages.ecsDataQualityDashboard.summaryTable.incompatibleFieldsColumn', + { + defaultMessage: 'Incompatible fields', + } +); + +export const INDICES = i18n.translate( + 'securitySolutionPackages.ecsDataQualityDashboard.summaryTable.indicesColumn', + { + defaultMessage: 'Indices', + } +); + +export const INDICES_CHECKED = i18n.translate( + 'securitySolutionPackages.ecsDataQualityDashboard.summaryTable.indicesCheckedColumn', + { + defaultMessage: 'Indices checked', + } +); + +export const INDEX = i18n.translate( + 'securitySolutionPackages.ecsDataQualityDashboard.summaryTable.indexColumn', + { + defaultMessage: 'Index', + } +); + +export const INDEX_NAME_LABEL = i18n.translate( + 'securitySolutionPackages.ecsDataQualityDashboard.summaryTable.indexesNameLabel', + { + defaultMessage: 'Index name', + } +); + +export const INDEX_TOOL_TIP = (pattern: string) => + i18n.translate('securitySolutionPackages.ecsDataQualityDashboard.summaryTable.indexToolTip', { + values: { pattern }, + defaultMessage: 'This index matches the pattern or index name: {pattern}', + }); + +export const PASSED = i18n.translate( + 'securitySolutionPackages.ecsDataQualityDashboard.summaryTable.passedTooltip', + { + defaultMessage: 'Passed', + } +); + +export const RESULT = i18n.translate( + 'securitySolutionPackages.ecsDataQualityDashboard.summaryTable.resultColumn', + { + defaultMessage: 'Result', + } +); + +export const SIZE = i18n.translate( + 'securitySolutionPackages.ecsDataQualityDashboard.summaryTable.sizeColumn', + { + defaultMessage: 'Size', + } +); + +export const THIS_INDEX_HAS_NOT_BEEN_CHECKED = i18n.translate( + 'securitySolutionPackages.ecsDataQualityDashboard.summaryTable.thisIndexHasNotBeenCheckedTooltip', + { + defaultMessage: 'This index has not been checked', + } +); diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/tabs/all_tab/index.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/tabs/all_tab/index.tsx similarity index 100% rename from x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/tabs/all_tab/index.tsx rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/tabs/all_tab/index.tsx diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/tabs/callouts/custom_callout/index.test.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/tabs/callouts/custom_callout/index.test.tsx similarity index 100% rename from x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/tabs/callouts/custom_callout/index.test.tsx rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/tabs/callouts/custom_callout/index.test.tsx diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/tabs/callouts/custom_callout/index.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/tabs/callouts/custom_callout/index.tsx similarity index 100% rename from x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/tabs/callouts/custom_callout/index.tsx rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/tabs/callouts/custom_callout/index.tsx diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/tabs/callouts/incompatible_callout/helpers.test.ts b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/tabs/callouts/incompatible_callout/helpers.test.ts similarity index 100% rename from x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/tabs/callouts/incompatible_callout/helpers.test.ts rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/tabs/callouts/incompatible_callout/helpers.test.ts diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/tabs/callouts/incompatible_callout/helpers.ts b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/tabs/callouts/incompatible_callout/helpers.ts similarity index 100% rename from x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/tabs/callouts/incompatible_callout/helpers.ts rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/tabs/callouts/incompatible_callout/helpers.ts diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/tabs/callouts/incompatible_callout/index.test.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/tabs/callouts/incompatible_callout/index.test.tsx similarity index 100% rename from x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/tabs/callouts/incompatible_callout/index.test.tsx rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/tabs/callouts/incompatible_callout/index.test.tsx diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/tabs/callouts/incompatible_callout/index.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/tabs/callouts/incompatible_callout/index.tsx similarity index 100% rename from x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/tabs/callouts/incompatible_callout/index.tsx rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/tabs/callouts/incompatible_callout/index.tsx diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/tabs/callouts/missing_timestamp_callout/index.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/tabs/callouts/missing_timestamp_callout/index.tsx similarity index 100% rename from x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/tabs/callouts/missing_timestamp_callout/index.tsx rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/tabs/callouts/missing_timestamp_callout/index.tsx diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/tabs/custom_tab/helpers.test.ts b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/tabs/custom_tab/helpers.test.ts similarity index 100% rename from x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/tabs/custom_tab/helpers.test.ts rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/tabs/custom_tab/helpers.test.ts diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/tabs/custom_tab/helpers.ts b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/tabs/custom_tab/helpers.ts similarity index 100% rename from x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/tabs/custom_tab/helpers.ts rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/tabs/custom_tab/helpers.ts diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/tabs/custom_tab/index.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/tabs/custom_tab/index.tsx similarity index 100% rename from x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/tabs/custom_tab/index.tsx rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/tabs/custom_tab/index.tsx diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/tabs/ecs_compliant_tab/index.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/tabs/ecs_compliant_tab/index.tsx similarity index 100% rename from x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/tabs/ecs_compliant_tab/index.tsx rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/tabs/ecs_compliant_tab/index.tsx diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/tabs/helpers.test.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/tabs/helpers.test.tsx similarity index 100% rename from x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/tabs/helpers.test.tsx rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/tabs/helpers.test.tsx diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/tabs/helpers.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/tabs/helpers.tsx similarity index 100% rename from x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/tabs/helpers.tsx rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/tabs/helpers.tsx diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/tabs/incompatible_tab/helpers.test.ts b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/tabs/incompatible_tab/helpers.test.ts similarity index 100% rename from x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/tabs/incompatible_tab/helpers.test.ts rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/tabs/incompatible_tab/helpers.test.ts diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/tabs/incompatible_tab/helpers.ts b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/tabs/incompatible_tab/helpers.ts similarity index 100% rename from x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/tabs/incompatible_tab/helpers.ts rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/tabs/incompatible_tab/helpers.ts diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/tabs/incompatible_tab/index.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/tabs/incompatible_tab/index.tsx similarity index 100% rename from x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/tabs/incompatible_tab/index.tsx rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/tabs/incompatible_tab/index.tsx diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/tabs/incompatible_tab/translations.ts b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/tabs/incompatible_tab/translations.ts new file mode 100644 index 0000000000000..3be9a55333ad2 --- /dev/null +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/tabs/incompatible_tab/translations.ts @@ -0,0 +1,26 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { i18n } from '@kbn/i18n'; + +export const INCOMPATIBLE_FIELD_MAPPINGS_TABLE_TITLE = (indexName: string) => + i18n.translate( + 'securitySolutionPackages.ecsDataQualityDashboard.incompatibleTab.incompatibleFieldMappingsTableTitle', + { + values: { indexName }, + defaultMessage: 'Incompatible field mappings - {indexName}', + } + ); + +export const INCOMPATIBLE_FIELD_VALUES_TABLE_TITLE = (indexName: string) => + i18n.translate( + 'securitySolutionPackages.ecsDataQualityDashboard.incompatibleTab.incompatibleFieldValuesTableTitle', + { + values: { indexName }, + defaultMessage: 'Incompatible field values - {indexName}', + } + ); diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/tabs/styles.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/tabs/styles.tsx similarity index 100% rename from x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/tabs/styles.tsx rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/tabs/styles.tsx diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/tabs/summary_tab/callout_summary/index.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/tabs/summary_tab/callout_summary/index.tsx similarity index 100% rename from x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/tabs/summary_tab/callout_summary/index.tsx rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/tabs/summary_tab/callout_summary/index.tsx diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/tabs/summary_tab/helpers.test.ts b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/tabs/summary_tab/helpers.test.ts similarity index 100% rename from x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/tabs/summary_tab/helpers.test.ts rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/tabs/summary_tab/helpers.test.ts diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/tabs/summary_tab/helpers.ts b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/tabs/summary_tab/helpers.ts similarity index 100% rename from x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/tabs/summary_tab/helpers.ts rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/tabs/summary_tab/helpers.ts diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/tabs/summary_tab/index.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/tabs/summary_tab/index.tsx similarity index 100% rename from x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/tabs/summary_tab/index.tsx rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/tabs/summary_tab/index.tsx diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/ecs_summary_donut_chart/chart_legend/chart_legend_item.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/ecs_summary_donut_chart/chart_legend/chart_legend_item.tsx similarity index 100% rename from x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/ecs_summary_donut_chart/chart_legend/chart_legend_item.tsx rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/ecs_summary_donut_chart/chart_legend/chart_legend_item.tsx diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/ecs_summary_donut_chart/chart_legend/index.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/ecs_summary_donut_chart/chart_legend/index.tsx similarity index 100% rename from x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/ecs_summary_donut_chart/chart_legend/index.tsx rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/ecs_summary_donut_chart/chart_legend/index.tsx diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/ecs_summary_donut_chart/helpers.test.ts b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/ecs_summary_donut_chart/helpers.test.ts similarity index 100% rename from x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/ecs_summary_donut_chart/helpers.test.ts rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/ecs_summary_donut_chart/helpers.test.ts diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/ecs_summary_donut_chart/helpers.ts b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/ecs_summary_donut_chart/helpers.ts similarity index 100% rename from x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/ecs_summary_donut_chart/helpers.ts rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/ecs_summary_donut_chart/helpers.ts diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/ecs_summary_donut_chart/index.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/ecs_summary_donut_chart/index.tsx similarity index 100% rename from x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/ecs_summary_donut_chart/index.tsx rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/ecs_summary_donut_chart/index.tsx diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/ecs_summary_donut_chart/translations.ts b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/ecs_summary_donut_chart/translations.ts similarity index 60% rename from x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/ecs_summary_donut_chart/translations.ts rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/ecs_summary_donut_chart/translations.ts index 414a6f127eeda..4df2f41cfdbd3 100644 --- a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/ecs_summary_donut_chart/translations.ts +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/ecs_summary_donut_chart/translations.ts @@ -8,12 +8,15 @@ import { i18n } from '@kbn/i18n'; export const CHART_TITLE = i18n.translate( - 'ecsDataQualityDashboard.ecsSummaryDonutChart.chartTitle', + 'securitySolutionPackages.ecsDataQualityDashboard.ecsSummaryDonutChart.chartTitle', { defaultMessage: 'Field mappings', } ); -export const FIELDS = i18n.translate('ecsDataQualityDashboard.ecsSummaryDonutChart.fieldsLabel', { - defaultMessage: 'Fields', -}); +export const FIELDS = i18n.translate( + 'securitySolutionPackages.ecsDataQualityDashboard.ecsSummaryDonutChart.fieldsLabel', + { + defaultMessage: 'Fields', + } +); diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/helpers.test.ts b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/helpers.test.ts similarity index 100% rename from x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/helpers.test.ts rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/helpers.test.ts diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/helpers.ts b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/helpers.ts similarity index 100% rename from x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/helpers.ts rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/helpers.ts diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/ilm_phases_empty_prompt/index.test.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/ilm_phases_empty_prompt/index.test.tsx similarity index 100% rename from x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/ilm_phases_empty_prompt/index.test.tsx rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/ilm_phases_empty_prompt/index.test.tsx diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/ilm_phases_empty_prompt/index.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/ilm_phases_empty_prompt/index.tsx similarity index 100% rename from x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/ilm_phases_empty_prompt/index.tsx rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/ilm_phases_empty_prompt/index.tsx diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/ilm_phases_empty_prompt/translations.ts b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/ilm_phases_empty_prompt/translations.ts new file mode 100644 index 0000000000000..08a39d5b64d22 --- /dev/null +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/ilm_phases_empty_prompt/translations.ts @@ -0,0 +1,80 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { i18n } from '@kbn/i18n'; + +export const BODY = i18n.translate( + 'securitySolutionPackages.ecsDataQualityDashboard.ilmPhasesEmptyPromptBody', + { + defaultMessage: + 'Indices with these Index Lifecycle Management (ILM) phases will be checked for data quality', + } +); + +export const COLD = i18n.translate( + 'securitySolutionPackages.ecsDataQualityDashboard.ilmPhasesEmptyPromptColdLabel', + { + defaultMessage: 'cold', + } +); + +export const FROZEN = i18n.translate( + 'securitySolutionPackages.ecsDataQualityDashboard.ilmPhasesEmptyPromptFrozenLabel', + { + defaultMessage: 'frozen', + } +); + +export const HOT = i18n.translate( + 'securitySolutionPackages.ecsDataQualityDashboard.ilmPhasesEmptyPromptHotLabel', + { + defaultMessage: 'hot', + } +); + +export const ILM_PHASES_THAT_CAN_BE_CHECKED = i18n.translate( + 'securitySolutionPackages.ecsDataQualityDashboard.ilmPhasesEmptyPromptIlmPhasesThatCanBeCheckedSubtitle', + { + defaultMessage: 'ILM phases that can be checked for data quality', + } +); + +export const ILM_PHASES_THAT_CANNOT_BE_CHECKED = i18n.translate( + 'securitySolutionPackages.ecsDataQualityDashboard.ilmPhasesEmptyPromptIlmPhasesThatCannotBeCheckedSubtitle', + { + defaultMessage: 'ILM phases that cannot be checked', + } +); + +export const THE_FOLLOWING_ILM_PHASES = i18n.translate( + 'securitySolutionPackages.ecsDataQualityDashboard.ilmPhasesEmptyPromptITheFollowingIlmPhasesLabel', + { + defaultMessage: + 'The following ILM phases cannot be checked for data quality because they are slower to access', + } +); + +export const UNMANAGED = i18n.translate( + 'securitySolutionPackages.ecsDataQualityDashboard.ilmPhasesEmptyPromptUnmanagedLabel', + { + defaultMessage: 'unmanaged', + } +); + +export const WARM = i18n.translate( + 'securitySolutionPackages.ecsDataQualityDashboard.ilmPhasesEmptyPromptWarmLabel', + { + defaultMessage: 'warm', + } +); + +export const TITLE = i18n.translate( + 'securitySolutionPackages.ecsDataQualityDashboard.ilmPhasesEmptyPromptTitle', + { + defaultMessage: 'Select one or more ILM phases', + } +); diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/index.test.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/index.test.tsx similarity index 100% rename from x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/index.test.tsx rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/index.test.tsx diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/index.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/index.tsx similarity index 100% rename from x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/index.tsx rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/index.tsx diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/mock/allowed_values/mock_allowed_values.ts b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/mock/allowed_values/mock_allowed_values.ts similarity index 100% rename from x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/mock/allowed_values/mock_allowed_values.ts rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/mock/allowed_values/mock_allowed_values.ts diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/mock/data_quality_check_result/mock_index.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/mock/data_quality_check_result/mock_index.tsx similarity index 100% rename from x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/mock/data_quality_check_result/mock_index.tsx rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/mock/data_quality_check_result/mock_index.tsx diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/mock/enriched_field_metadata/mock_enriched_field_metadata.ts b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/mock/enriched_field_metadata/mock_enriched_field_metadata.ts similarity index 100% rename from x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/mock/enriched_field_metadata/mock_enriched_field_metadata.ts rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/mock/enriched_field_metadata/mock_enriched_field_metadata.ts diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/mock/ilm_explain/mock_ilm_explain.ts b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/mock/ilm_explain/mock_ilm_explain.ts similarity index 100% rename from x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/mock/ilm_explain/mock_ilm_explain.ts rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/mock/ilm_explain/mock_ilm_explain.ts diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/mock/indices_get_mapping_index_mapping_record/mock_indices_get_mapping_index_mapping_record.ts b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/mock/indices_get_mapping_index_mapping_record/mock_indices_get_mapping_index_mapping_record.ts similarity index 100% rename from x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/mock/indices_get_mapping_index_mapping_record/mock_indices_get_mapping_index_mapping_record.ts rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/mock/indices_get_mapping_index_mapping_record/mock_indices_get_mapping_index_mapping_record.ts diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/mock/mappings_properties/mock_mappings_properties.ts b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/mock/mappings_properties/mock_mappings_properties.ts similarity index 100% rename from x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/mock/mappings_properties/mock_mappings_properties.ts rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/mock/mappings_properties/mock_mappings_properties.ts diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/mock/mappings_response/mock_mappings_response.ts b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/mock/mappings_response/mock_mappings_response.ts similarity index 100% rename from x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/mock/mappings_response/mock_mappings_response.ts rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/mock/mappings_response/mock_mappings_response.ts diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/mock/partitioned_field_metadata/mock_partitioned_field_metadata.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/mock/partitioned_field_metadata/mock_partitioned_field_metadata.tsx similarity index 100% rename from x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/mock/partitioned_field_metadata/mock_partitioned_field_metadata.tsx rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/mock/partitioned_field_metadata/mock_partitioned_field_metadata.tsx diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/mock/pattern_rollup/mock_alerts_pattern_rollup.ts b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/mock/pattern_rollup/mock_alerts_pattern_rollup.ts similarity index 100% rename from x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/mock/pattern_rollup/mock_alerts_pattern_rollup.ts rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/mock/pattern_rollup/mock_alerts_pattern_rollup.ts diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/mock/pattern_rollup/mock_auditbeat_pattern_rollup.ts b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/mock/pattern_rollup/mock_auditbeat_pattern_rollup.ts similarity index 100% rename from x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/mock/pattern_rollup/mock_auditbeat_pattern_rollup.ts rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/mock/pattern_rollup/mock_auditbeat_pattern_rollup.ts diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/mock/pattern_rollup/mock_packetbeat_pattern_rollup.ts b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/mock/pattern_rollup/mock_packetbeat_pattern_rollup.ts similarity index 100% rename from x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/mock/pattern_rollup/mock_packetbeat_pattern_rollup.ts rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/mock/pattern_rollup/mock_packetbeat_pattern_rollup.ts diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/mock/stats/mock_stats.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/mock/stats/mock_stats.tsx similarity index 100% rename from x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/mock/stats/mock_stats.tsx rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/mock/stats/mock_stats.tsx diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/mock/stats/mock_stats_green_index.ts b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/mock/stats/mock_stats_green_index.ts similarity index 100% rename from x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/mock/stats/mock_stats_green_index.ts rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/mock/stats/mock_stats_green_index.ts diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/mock/stats/mock_stats_yellow_index.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/mock/stats/mock_stats_yellow_index.tsx similarity index 100% rename from x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/mock/stats/mock_stats_yellow_index.tsx rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/mock/stats/mock_stats_yellow_index.tsx diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/mock/test_providers/test_providers.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/mock/test_providers/test_providers.tsx similarity index 100% rename from x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/mock/test_providers/test_providers.tsx rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/mock/test_providers/test_providers.tsx diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/mock/unallowed_values/mock_unallowed_values.ts b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/mock/unallowed_values/mock_unallowed_values.ts similarity index 100% rename from x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/mock/unallowed_values/mock_unallowed_values.ts rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/mock/unallowed_values/mock_unallowed_values.ts diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/styles.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/styles.tsx similarity index 100% rename from x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/styles.tsx rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/styles.tsx diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/translations.ts b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/translations.ts new file mode 100644 index 0000000000000..18c14d5036b6b --- /dev/null +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/translations.ts @@ -0,0 +1,273 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license 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 ADD_TO_NEW_CASE = i18n.translate( + 'securitySolutionPackages.ecsDataQualityDashboard.addToNewCaseButton', + { + defaultMessage: 'Add to new case', + } +); + +export const CANCEL = i18n.translate( + 'securitySolutionPackages.ecsDataQualityDashboard.cancelButton', + { + defaultMessage: 'Cancel', + } +); + +export const CHECK_ALL = i18n.translate( + 'securitySolutionPackages.ecsDataQualityDashboard.checkAllButton', + { + defaultMessage: 'Check all', + } +); + +export const CHECKING = (index: string) => + i18n.translate('securitySolutionPackages.ecsDataQualityDashboard.checkingLabel', { + values: { index }, + defaultMessage: 'Checking {index}', + }); + +export const COLD_DESCRIPTION = i18n.translate( + 'securitySolutionPackages.ecsDataQualityDashboard.coldDescription', + { + defaultMessage: + 'The index is no longer being updated and is queried infrequently. The information still needs to be searchable, but it’s okay if those queries are slower.', + } +); + +export const COLD_PATTERN_TOOLTIP = ({ indices, pattern }: { indices: number; pattern: string }) => + i18n.translate('securitySolutionPackages.ecsDataQualityDashboard.coldPatternTooltip', { + values: { indices, pattern }, + defaultMessage: + '{indices} {indices, plural, =1 {index} other {indices}} matching the {pattern} pattern {indices, plural, =1 {is} other {are}} cold. Cold indices are no longer being updated and are queried infrequently. The information still needs to be searchable, but it’s okay if those queries are slower.', + }); + +export const COPIED_RESULTS_TOAST_TITLE = i18n.translate( + 'securitySolutionPackages.ecsDataQualityDashboard.toasts.copiedResultsToastTitle', + { + defaultMessage: 'Copied results to the clipboard', + } +); + +export const COPY_TO_CLIPBOARD = i18n.translate( + 'securitySolutionPackages.ecsDataQualityDashboard.copyToClipboardButton', + { + defaultMessage: 'Copy to clipboard', + } +); + +/** The subtitle displayed on the Data Quality dashboard */ +export const DATA_QUALITY_SUBTITLE: string = i18n.translate( + 'securitySolutionPackages.ecsDataQualityDashboard.securitySolutionPackages.ecsDataQualityDashboardSubtitle', + { + defaultMessage: 'Check index mappings and values for compatibility with the', + } +); + +export const DATA_QUALITY_TITLE = i18n.translate( + 'securitySolutionPackages.ecsDataQualityDashboard.securitySolutionPackages.ecsDataQualityDashboardTitle', + { + defaultMessage: 'Data quality', + } +); + +export const DEFAULT_PANEL_TITLE = i18n.translate( + 'securitySolutionPackages.ecsDataQualityDashboard.defaultPanelTitle', + { + defaultMessage: 'Check index mappings', + } +); + +export const ECS_VERSION = i18n.translate( + 'securitySolutionPackages.ecsDataQualityDashboard.ecsVersionStat', + { + defaultMessage: 'ECS version', + } +); + +export const ERROR_LOADING_ILM_EXPLAIN = (details: string) => + i18n.translate('securitySolutionPackages.ecsDataQualityDashboard.errorLoadingIlmExplainLabel', { + values: { details }, + defaultMessage: 'Error loading ILM Explain: {details}', + }); + +export const ERROR_LOADING_MAPPINGS = ({ + details, + patternOrIndexName, +}: { + details: string; + patternOrIndexName: string; +}) => + i18n.translate('securitySolutionPackages.ecsDataQualityDashboard.errorLoadingMappingsLabel', { + values: { details, patternOrIndexName }, + defaultMessage: 'Error loading mappings for {patternOrIndexName}: {details}', + }); + +export const ERROR_LOADING_STATS = (details: string) => + i18n.translate('securitySolutionPackages.ecsDataQualityDashboard.errorLoadingStatsLabel', { + values: { details }, + defaultMessage: 'Error loading stats: {details}', + }); + +export const ERROR_LOADING_UNALLOWED_VALUES = ({ + details, + indexName, +}: { + details: string; + indexName: string; +}) => + i18n.translate( + 'securitySolutionPackages.ecsDataQualityDashboard.errorLoadingUnallowedValuesLabel', + { + values: { details, indexName }, + defaultMessage: 'Error loading unallowed values for index {indexName}: {details}', + } + ); + +export const FIELDS = i18n.translate( + 'securitySolutionPackages.ecsDataQualityDashboard.fieldsLabel', + { + defaultMessage: 'Fields', + } +); + +export const FROZEN_DESCRIPTION = i18n.translate( + 'securitySolutionPackages.ecsDataQualityDashboard.frozenDescription', + { + defaultMessage: `The index is no longer being updated and is queried rarely. The information still needs to be searchable, but it's okay if those queries are extremely slow.`, + } +); + +export const FROZEN_PATTERN_TOOLTIP = ({ + indices, + pattern, +}: { + indices: number; + pattern: string; +}) => + i18n.translate('securitySolutionPackages.ecsDataQualityDashboard.frozenPatternTooltip', { + values: { indices, pattern }, + defaultMessage: `{indices} {indices, plural, =1 {index} other {indices}} matching the {pattern} pattern {indices, plural, =1 {is} other {are}} frozen. Frozen indices are no longer being updated and are queried rarely. The information still needs to be searchable, but it's okay if those queries are extremely slow.`, + }); + +export const HOT_DESCRIPTION = i18n.translate( + 'securitySolutionPackages.ecsDataQualityDashboard.hotDescription', + { + defaultMessage: 'The index is actively being updated and queried', + } +); + +export const HOT_PATTERN_TOOLTIP = ({ indices, pattern }: { indices: number; pattern: string }) => + i18n.translate('securitySolutionPackages.ecsDataQualityDashboard.hotPatternTooltip', { + values: { indices, pattern }, + defaultMessage: + '{indices} {indices, plural, =1 {index} other {indices}} matching the {pattern} pattern {indices, plural, =1 {is} other {are}} hot. Hot indices are actively being updated and queried.', + }); + +/** The tooltip for the `ILM phase` combo box on the Data Quality Dashboard */ +export const INDEX_LIFECYCLE_MANAGEMENT_PHASES: string = i18n.translate( + 'securitySolutionPackages.ecsDataQualityDashboard.indexLifecycleManagementPhasesTooltip', + { + defaultMessage: + 'Indices with these Index Lifecycle Management (ILM) phases will be checked for data quality', + } +); + +export const INDEX_NAME = i18n.translate( + 'securitySolutionPackages.ecsDataQualityDashboard.indexNameLabel', + { + defaultMessage: 'Index name', + } +); + +/** The label displayed for the `ILM phase` combo box on the Data Quality dashboard */ +export const ILM_PHASE: string = i18n.translate( + 'securitySolutionPackages.ecsDataQualityDashboard.ilmPhaseLabel', + { + defaultMessage: 'ILM phase', + } +); + +export const LAST_CHECKED = i18n.translate( + 'securitySolutionPackages.ecsDataQualityDashboard.lastCheckedLabel', + { + defaultMessage: 'Last checked', + } +); + +export const LOADING_ECS_METADATA = i18n.translate( + 'securitySolutionPackages.ecsDataQualityDashboard.emptyLoadingPrompt.loadingEcsMetadataPrompt', + { + defaultMessage: 'Loading ECS metadata', + } +); + +export const SELECT_AN_INDEX = i18n.translate( + 'securitySolutionPackages.ecsDataQualityDashboard.selectAnIndexPrompt', + { + defaultMessage: 'Select an index to compare it against ECS version', + } +); + +/** The placeholder for the `ILM phase` combo box on the Data Quality Dashboard */ +export const SELECT_ONE_OR_MORE_ILM_PHASES: string = i18n.translate( + 'securitySolutionPackages.ecsDataQualityDashboard.selectOneOrMorPhasesPlaceholder', + { + defaultMessage: 'Select one or more ILM phases', + } +); + +export const INDEX_SIZE_TOOLTIP = i18n.translate( + 'securitySolutionPackages.ecsDataQualityDashboard.indexSizeTooltip', + { + defaultMessage: 'The size of the primary index (does not include replicas)', + } +); + +export const TIMESTAMP_DESCRIPTION = i18n.translate( + 'securitySolutionPackages.ecsDataQualityDashboard.timestampDescriptionLabel', + { + defaultMessage: + 'Date/time when the event originated. This is the date/time extracted from the event, typically representing when the event was generated by the source. If the event source has no original timestamp, this value is typically populated by the first time the event was received by the pipeline. Required field for all events.', + } +); + +export const UNMANAGED_DESCRIPTION = i18n.translate( + 'securitySolutionPackages.ecsDataQualityDashboard.unmanagedDescription', + { + defaultMessage: `The index isn't managed by Index Lifecycle Management (ILM)`, + } +); + +export const UNMANAGED_PATTERN_TOOLTIP = ({ + indices, + pattern, +}: { + indices: number; + pattern: string; +}) => + i18n.translate('securitySolutionPackages.ecsDataQualityDashboard.unmanagedPatternTooltip', { + values: { indices, pattern }, + defaultMessage: `{indices} {indices, plural, =1 {index} other {indices}} matching the {pattern} pattern {indices, plural, =1 {is} other {are}} unmanaged by Index Lifecycle Management (ILM)`, + }); + +export const WARM_DESCRIPTION = i18n.translate( + 'securitySolutionPackages.ecsDataQualityDashboard.warmDescription', + { + defaultMessage: 'The index is no longer being updated but is still being queried', + } +); + +export const WARM_PATTERN_TOOLTIP = ({ indices, pattern }: { indices: number; pattern: string }) => + i18n.translate('securitySolutionPackages.ecsDataQualityDashboard.warmPatternTooltip', { + values: { indices, pattern }, + defaultMessage: + '{indices} {indices, plural, =1 {index} other {indices}} matching the {pattern} pattern {indices, plural, =1 {is} other {are}} warm. Warm indices are no longer being updated but are still being queried.', + }); diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/types.ts b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/types.ts similarity index 100% rename from x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/types.ts rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/types.ts diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/use_add_to_new_case/index.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/use_add_to_new_case/index.tsx similarity index 100% rename from x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/use_add_to_new_case/index.tsx rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/use_add_to_new_case/index.tsx diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/use_add_to_new_case/translations.ts b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/use_add_to_new_case/translations.ts new file mode 100644 index 0000000000000..9118d6c51187a --- /dev/null +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/use_add_to_new_case/translations.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 { i18n } from '@kbn/i18n'; + +export const ADD_TO_CASE_SUCCESS = i18n.translate( + 'securitySolutionPackages.ecsDataQualityDashboard.addToCaseSuccessToast', + { + defaultMessage: 'Successfully added data quality results to the case', + } +); + +export const CREATE_A_DATA_QUALITY_CASE = i18n.translate( + 'securitySolutionPackages.ecsDataQualityDashboard.createADataQualityCaseHeaderText', + { + defaultMessage: 'Create a data quality case', + } +); + +export const CREATE_A_DATA_QUALITY_CASE_FOR_INDEX = (indexName: string) => + i18n.translate( + 'securitySolutionPackages.ecsDataQualityDashboard.createADataQualityCaseForIndexHeaderText', + { + values: { indexName }, + defaultMessage: 'Create a data quality case for index {indexName}', + } + ); diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/use_ilm_explain/index.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/use_ilm_explain/index.tsx similarity index 100% rename from x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/use_ilm_explain/index.tsx rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/use_ilm_explain/index.tsx diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/use_mappings/helpers.test.ts b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/use_mappings/helpers.test.ts similarity index 100% rename from x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/use_mappings/helpers.test.ts rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/use_mappings/helpers.test.ts diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/use_mappings/helpers.ts b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/use_mappings/helpers.ts similarity index 100% rename from x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/use_mappings/helpers.ts rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/use_mappings/helpers.ts diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/use_mappings/index.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/use_mappings/index.tsx similarity index 100% rename from x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/use_mappings/index.tsx rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/use_mappings/index.tsx diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/use_results_rollup/helpers.test.ts b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/use_results_rollup/helpers.test.ts similarity index 100% rename from x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/use_results_rollup/helpers.test.ts rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/use_results_rollup/helpers.test.ts diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/use_results_rollup/helpers.ts b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/use_results_rollup/helpers.ts similarity index 100% rename from x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/use_results_rollup/helpers.ts rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/use_results_rollup/helpers.ts diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/use_results_rollup/index.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/use_results_rollup/index.tsx similarity index 100% rename from x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/use_results_rollup/index.tsx rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/use_results_rollup/index.tsx diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/use_stats/index.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/use_stats/index.tsx similarity index 100% rename from x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/use_stats/index.tsx rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/use_stats/index.tsx diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/use_unallowed_values/helpers.test.ts b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/use_unallowed_values/helpers.test.ts similarity index 100% rename from x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/use_unallowed_values/helpers.test.ts rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/use_unallowed_values/helpers.test.ts diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/use_unallowed_values/helpers.ts b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/use_unallowed_values/helpers.ts similarity index 100% rename from x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/use_unallowed_values/helpers.ts rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/use_unallowed_values/helpers.ts diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/use_unallowed_values/index.ts b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/use_unallowed_values/index.ts similarity index 100% rename from x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/use_unallowed_values/index.ts rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/use_unallowed_values/index.ts diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/index.ts b/x-pack/packages/security-solution/ecs_data_quality_dashboard/index.ts similarity index 100% rename from x-pack/packages/kbn-ecs-data-quality-dashboard/index.ts rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/index.ts diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/jest.config.js b/x-pack/packages/security-solution/ecs_data_quality_dashboard/jest.config.js new file mode 100644 index 0000000000000..e017b0ceaf369 --- /dev/null +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/jest.config.js @@ -0,0 +1,26 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +module.exports = { + coverageDirectory: + '/target/kibana-coverage/jest/x-pack/packages/security-solution/ecs_data_quality_dashboard_impl', + coverageReporters: ['text', 'html'], + collectCoverageFrom: [ + '/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/**/*.{ts,tsx}', + '!/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/{__test__,__snapshots__,__examples__,*mock*,tests,test_helpers,integration_tests,types}/**/*', + '!/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/*mock*.{ts,tsx}', + '!/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/*.test.{ts,tsx}', + '!/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/*.d.ts', + '!/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/*.config.ts', + ], + preset: '@kbn/test', + rootDir: '../../../..', + roots: ['/x-pack/packages/security-solution/ecs_data_quality_dashboard'], + setupFilesAfterEnv: [ + '/x-pack/packages/security-solution/ecs_data_quality_dashboard/setup_tests.ts', + ], +}; diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/kibana.jsonc b/x-pack/packages/security-solution/ecs_data_quality_dashboard/kibana.jsonc similarity index 100% rename from x-pack/packages/kbn-ecs-data-quality-dashboard/kibana.jsonc rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/kibana.jsonc diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/package.json b/x-pack/packages/security-solution/ecs_data_quality_dashboard/package.json similarity index 100% rename from x-pack/packages/kbn-ecs-data-quality-dashboard/package.json rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/package.json diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/setup_tests.ts b/x-pack/packages/security-solution/ecs_data_quality_dashboard/setup_tests.ts similarity index 100% rename from x-pack/packages/kbn-ecs-data-quality-dashboard/setup_tests.ts rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/setup_tests.ts diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/tsconfig.json b/x-pack/packages/security-solution/ecs_data_quality_dashboard/tsconfig.json similarity index 87% rename from x-pack/packages/kbn-ecs-data-quality-dashboard/tsconfig.json rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/tsconfig.json index 09b55094f3b23..8cd3ca4ed16ac 100644 --- a/x-pack/packages/kbn-ecs-data-quality-dashboard/tsconfig.json +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/tsconfig.json @@ -1,5 +1,5 @@ { - "extends": "../../../tsconfig.base.json", + "extends": "../../../../tsconfig.base.json", "compilerOptions": { "outDir": "target/types", "types": [ diff --git a/packages/security-solution/side_nav/README.mdx b/x-pack/packages/security-solution/side_nav/README.mdx similarity index 100% rename from packages/security-solution/side_nav/README.mdx rename to x-pack/packages/security-solution/side_nav/README.mdx diff --git a/packages/security-solution/side_nav/index.ts b/x-pack/packages/security-solution/side_nav/index.ts similarity index 63% rename from packages/security-solution/side_nav/index.ts rename to x-pack/packages/security-solution/side_nav/index.ts index d35fa8578831b..c13ed6f81d16c 100644 --- a/packages/security-solution/side_nav/index.ts +++ b/x-pack/packages/security-solution/side_nav/index.ts @@ -1,9 +1,8 @@ /* * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one * or more contributor license agreements. Licensed under the Elastic License - * 2.0 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. + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. */ export { SolutionSideNav, type SolutionSideNavProps } from './src'; diff --git a/x-pack/packages/security-solution/side_nav/jest.config.js b/x-pack/packages/security-solution/side_nav/jest.config.js new file mode 100644 index 0000000000000..89a16e7fff05e --- /dev/null +++ b/x-pack/packages/security-solution/side_nav/jest.config.js @@ -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. + */ + +module.exports = { + preset: '@kbn/test', + rootDir: '../../../..', + roots: ['/x-pack/packages/security-solution/side_nav'], +}; diff --git a/packages/security-solution/side_nav/kibana.jsonc b/x-pack/packages/security-solution/side_nav/kibana.jsonc similarity index 100% rename from packages/security-solution/side_nav/kibana.jsonc rename to x-pack/packages/security-solution/side_nav/kibana.jsonc diff --git a/packages/security-solution/side_nav/package.json b/x-pack/packages/security-solution/side_nav/package.json similarity index 65% rename from packages/security-solution/side_nav/package.json rename to x-pack/packages/security-solution/side_nav/package.json index d34551f751046..c80baa25af3e2 100644 --- a/packages/security-solution/side_nav/package.json +++ b/x-pack/packages/security-solution/side_nav/package.json @@ -2,5 +2,5 @@ "name": "@kbn/security-solution-side-nav", "private": true, "version": "1.0.0", - "license": "SSPL-1.0 OR Elastic License 2.0" + "license": "Elastic License 2.0" } \ No newline at end of file diff --git a/packages/security-solution/side_nav/src/beta_badge.tsx b/x-pack/packages/security-solution/side_nav/src/beta_badge.tsx similarity index 82% rename from packages/security-solution/side_nav/src/beta_badge.tsx rename to x-pack/packages/security-solution/side_nav/src/beta_badge.tsx index 75696cd3bad32..ba838bcb9e340 100644 --- a/packages/security-solution/side_nav/src/beta_badge.tsx +++ b/x-pack/packages/security-solution/side_nav/src/beta_badge.tsx @@ -1,9 +1,8 @@ /* * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one * or more contributor license agreements. Licensed under the Elastic License - * 2.0 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. + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. */ import React from 'react'; diff --git a/packages/security-solution/side_nav/src/index.tsx b/x-pack/packages/security-solution/side_nav/src/index.tsx similarity index 76% rename from packages/security-solution/side_nav/src/index.tsx rename to x-pack/packages/security-solution/side_nav/src/index.tsx index 09d37a0b0cdff..055c45a1a8519 100644 --- a/packages/security-solution/side_nav/src/index.tsx +++ b/x-pack/packages/security-solution/side_nav/src/index.tsx @@ -1,9 +1,8 @@ /* * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one * or more contributor license agreements. Licensed under the Elastic License - * 2.0 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. + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. */ import React, { lazy, Suspense } from 'react'; diff --git a/packages/security-solution/side_nav/src/solution_side_nav.stories.tsx b/x-pack/packages/security-solution/side_nav/src/solution_side_nav.stories.tsx similarity index 96% rename from packages/security-solution/side_nav/src/solution_side_nav.stories.tsx rename to x-pack/packages/security-solution/side_nav/src/solution_side_nav.stories.tsx index e8b82c01a62e8..a2f64afd38ba4 100644 --- a/packages/security-solution/side_nav/src/solution_side_nav.stories.tsx +++ b/x-pack/packages/security-solution/side_nav/src/solution_side_nav.stories.tsx @@ -1,9 +1,8 @@ /* * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one * or more contributor license agreements. Licensed under the Elastic License - * 2.0 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. + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. */ import React from 'react'; @@ -146,6 +145,7 @@ export const SolutionSideNav = (params: Params) => ( icon={'logoSecurity'} isOpenOnDesktop={true} canBeCollapsed={false} + // eslint-disable-next-line react/no-children-prop children={ ; -export const SolutionSideNav: React.FC = React.memo( - ({ items, selectedId, footerItems = [], panelBottomOffset, panelTopOffset, tracker }) => { - const isMobileSize = useIsWithinBreakpoints(['xs', 's']); +export const SolutionSideNav: React.FC = React.memo(function SolutionSideNav({ + items, + selectedId, + footerItems = [], + panelBottomOffset, + panelTopOffset, + tracker, +}) { + const isMobileSize = useIsWithinBreakpoints(['xs', 's']); - const [activePanelNavId, setActivePanelNavId] = useState(null); - const activePanelNavIdRef = useRef(null); + const [activePanelNavId, setActivePanelNavId] = useState(null); + const activePanelNavIdRef = useRef(null); - const openPanelNav = (id: string) => { - activePanelNavIdRef.current = id; - setActivePanelNavId(id); - }; + const openPanelNav = (id: string) => { + activePanelNavIdRef.current = id; + setActivePanelNavId(id); + }; - const onClosePanelNav = useCallback(() => { - activePanelNavIdRef.current = null; - setActivePanelNavId(null); - }, []); + const onClosePanelNav = useCallback(() => { + activePanelNavIdRef.current = null; + setActivePanelNavId(null); + }, []); - const onOutsidePanelClick = useCallback(() => { - const currentPanelNavId = activePanelNavIdRef.current; - setTimeout(() => { - // This event is triggered on outside click. - // Closing the side nav at the end of event loop to make sure it - // closes also if the active panel button has been clicked (toggle), - // but it does not close if any any other panel open button has been clicked. - if (activePanelNavIdRef.current === currentPanelNavId) { - onClosePanelNav(); + const onOutsidePanelClick = useCallback(() => { + const currentPanelNavId = activePanelNavIdRef.current; + setTimeout(() => { + // This event is triggered on outside click. + // Closing the side nav at the end of event loop to make sure it + // closes also if the active panel button has been clicked (toggle), + // but it does not close if any any other panel open button has been clicked. + if (activePanelNavIdRef.current === currentPanelNavId) { + onClosePanelNav(); + } + }); + }, [onClosePanelNav]); + + const navItemsById = useMemo( + () => + [...items, ...footerItems].reduce((acc, navItem) => { + if (navItem.items?.length) { + acc[navItem.id] = { + title: navItem.label, + panelItems: navItem.items, + categories: navItem.categories, + }; } - }); - }, [onClosePanelNav]); + return acc; + }, {}), + [items, footerItems] + ); - const navItemsById = useMemo( - () => - [...items, ...footerItems].reduce((acc, navItem) => { - if (navItem.items?.length) { - acc[navItem.id] = { - title: navItem.label, - panelItems: navItem.items, - categories: navItem.categories, - }; - } - return acc; - }, {}), - [items, footerItems] + const panelNav = useMemo(() => { + if (activePanelNavId == null || !navItemsById[activePanelNavId]) { + return null; + } + const { panelItems, title, categories } = navItemsById[activePanelNavId]; + return ( + ); + }, [ + activePanelNavId, + navItemsById, + onClosePanelNav, + onOutsidePanelClick, + panelBottomOffset, + panelTopOffset, + ]); - const panelNav = useMemo(() => { - if (activePanelNavId == null || !navItemsById[activePanelNavId]) { - return null; - } - const { panelItems, title, categories } = navItemsById[activePanelNavId]; - return ( - - ); - }, [ - activePanelNavId, - navItemsById, - onClosePanelNav, - onOutsidePanelClick, - panelBottomOffset, - panelTopOffset, - ]); - - return ( - - - - - - - - - - - - - - - - - + return ( + + + + + + + + + + + + + + + + + - {panelNav} - - ); - } -); + {panelNav} + + ); +}); const SolutionSideNavItems: React.FC = ({ items, @@ -192,7 +196,7 @@ const SolutionSideNavItems: React.FC = ({ ); const SolutionSideNavItem: React.FC = React.memo( - ({ item, isSelected, isActive, hasPanelNav, onOpenPanelNav }) => { + function SolutionSideNavItem({ item, isSelected, isActive, hasPanelNav, onOpenPanelNav }) { const { euiTheme } = useEuiTheme(); const { tracker } = useTelemetryContext(); diff --git a/packages/security-solution/side_nav/src/solution_side_nav_panel.styles.ts b/x-pack/packages/security-solution/side_nav/src/solution_side_nav_panel.styles.ts similarity index 92% rename from packages/security-solution/side_nav/src/solution_side_nav_panel.styles.ts rename to x-pack/packages/security-solution/side_nav/src/solution_side_nav_panel.styles.ts index 574310923c357..070bbe29b30bd 100644 --- a/packages/security-solution/side_nav/src/solution_side_nav_panel.styles.ts +++ b/x-pack/packages/security-solution/side_nav/src/solution_side_nav_panel.styles.ts @@ -1,9 +1,8 @@ /* * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one * or more contributor license agreements. Licensed under the Elastic License - * 2.0 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. + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. */ import { transparentize, type EuiThemeComputed } from '@elastic/eui'; diff --git a/packages/security-solution/side_nav/src/solution_side_nav_panel.test.tsx b/x-pack/packages/security-solution/side_nav/src/solution_side_nav_panel.test.tsx similarity index 95% rename from packages/security-solution/side_nav/src/solution_side_nav_panel.test.tsx rename to x-pack/packages/security-solution/side_nav/src/solution_side_nav_panel.test.tsx index eb0f887eab96c..4c8a9ea7d8d63 100644 --- a/packages/security-solution/side_nav/src/solution_side_nav_panel.test.tsx +++ b/x-pack/packages/security-solution/side_nav/src/solution_side_nav_panel.test.tsx @@ -1,20 +1,18 @@ /* * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one * or more contributor license agreements. Licensed under the Elastic License - * 2.0 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. + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. */ import React from 'react'; import { render, waitFor } from '@testing-library/react'; import { SolutionSideNavPanel, type SolutionSideNavPanelProps } from './solution_side_nav_panel'; -import type { SolutionSideNavItem } from './types'; import { BETA_LABEL } from './beta_badge'; import { TELEMETRY_EVENT } from './telemetry/const'; import { METRIC_TYPE } from '@kbn/analytics'; import { TelemetryContextProvider } from './telemetry/telemetry_context'; -import type { LinkCategories } from './types'; +import type { SolutionSideNavItem, LinkCategories } from './types'; const mockUseIsWithinMinBreakpoint = jest.fn(() => true); jest.mock('@elastic/eui', () => { diff --git a/packages/security-solution/side_nav/src/solution_side_nav_panel.tsx b/x-pack/packages/security-solution/side_nav/src/solution_side_nav_panel.tsx similarity index 94% rename from packages/security-solution/side_nav/src/solution_side_nav_panel.tsx rename to x-pack/packages/security-solution/side_nav/src/solution_side_nav_panel.tsx index f884bfce5fac7..1a96fafb4d547 100644 --- a/packages/security-solution/side_nav/src/solution_side_nav_panel.tsx +++ b/x-pack/packages/security-solution/side_nav/src/solution_side_nav_panel.tsx @@ -1,9 +1,8 @@ /* * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one * or more contributor license agreements. Licensed under the Elastic License - * 2.0 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. + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. */ import React, { Fragment, useCallback } from 'react'; @@ -60,7 +59,15 @@ export interface SolutionSideNavPanelItemsProps { * Renders the side navigation panel for secondary links */ export const SolutionSideNavPanel: React.FC = React.memo( - ({ onClose, onOutsideClick, title, categories, items, bottomOffset, topOffset }) => { + function SolutionSideNavPanel({ + onClose, + onOutsideClick, + title, + categories, + items, + bottomOffset, + topOffset, + }) { const { euiTheme } = useEuiTheme(); const isLargerBreakpoint = useIsWithinMinBreakpoint('l'); @@ -133,7 +140,7 @@ export const SolutionSideNavPanel: React.FC = React.m ); const SolutionSideNavPanelCategories: React.FC = React.memo( - ({ categories, items, onClose }) => { + function SolutionSideNavPanelCategories({ categories, items, onClose }) { const { euiTheme } = useEuiTheme(); const sideNavTitleStyles = SolutionSideNavTitleStyles(euiTheme); const titleClasses = classNames(sideNavTitleStyles); @@ -170,7 +177,7 @@ const SolutionSideNavPanelCategories: React.FC = React.memo( - ({ items, onClose }) => { + function SolutionSideNavPanelItems({ items, onClose }) { const panelLinkClassNames = classNames('solutionSideNavPanelLink'); const panelLinkItemClassNames = classNames('solutionSideNavPanelLinkItem'); const { tracker } = useTelemetryContext(); diff --git a/packages/security-solution/side_nav/src/telemetry/const.ts b/x-pack/packages/security-solution/side_nav/src/telemetry/const.ts similarity index 63% rename from packages/security-solution/side_nav/src/telemetry/const.ts rename to x-pack/packages/security-solution/side_nav/src/telemetry/const.ts index 84628ee341ba9..c68a610e6e463 100644 --- a/packages/security-solution/side_nav/src/telemetry/const.ts +++ b/x-pack/packages/security-solution/side_nav/src/telemetry/const.ts @@ -1,9 +1,8 @@ /* * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one * or more contributor license agreements. Licensed under the Elastic License - * 2.0 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. + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. */ export enum TELEMETRY_EVENT { diff --git a/packages/security-solution/side_nav/src/telemetry/telemetry_context.tsx b/x-pack/packages/security-solution/side_nav/src/telemetry/telemetry_context.tsx similarity index 81% rename from packages/security-solution/side_nav/src/telemetry/telemetry_context.tsx rename to x-pack/packages/security-solution/side_nav/src/telemetry/telemetry_context.tsx index 53a4b355dcb45..c7e97969ad31c 100644 --- a/packages/security-solution/side_nav/src/telemetry/telemetry_context.tsx +++ b/x-pack/packages/security-solution/side_nav/src/telemetry/telemetry_context.tsx @@ -1,9 +1,8 @@ /* * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one * or more contributor license agreements. Licensed under the Elastic License - * 2.0 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. + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. */ import type { FC } from 'react'; diff --git a/packages/security-solution/side_nav/src/types.ts b/x-pack/packages/security-solution/side_nav/src/types.ts similarity index 84% rename from packages/security-solution/side_nav/src/types.ts rename to x-pack/packages/security-solution/side_nav/src/types.ts index c67647f12fa73..ccc1b2f3f3b86 100644 --- a/packages/security-solution/side_nav/src/types.ts +++ b/x-pack/packages/security-solution/side_nav/src/types.ts @@ -1,9 +1,8 @@ /* * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one * or more contributor license agreements. Licensed under the Elastic License - * 2.0 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. + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. */ import type React from 'react'; diff --git a/packages/security-solution/side_nav/tsconfig.json b/x-pack/packages/security-solution/side_nav/tsconfig.json similarity index 90% rename from packages/security-solution/side_nav/tsconfig.json rename to x-pack/packages/security-solution/side_nav/tsconfig.json index e19419203ab79..8dfe47eb50cfe 100644 --- a/packages/security-solution/side_nav/tsconfig.json +++ b/x-pack/packages/security-solution/side_nav/tsconfig.json @@ -1,5 +1,5 @@ { - "extends": "../../../tsconfig.base.json", + "extends": "../../../../tsconfig.base.json", "compilerOptions": { "outDir": "target/types", "types": [ diff --git a/packages/security-solution/storybook/config/README.mdx b/x-pack/packages/security-solution/storybook/config/README.mdx similarity index 100% rename from packages/security-solution/storybook/config/README.mdx rename to x-pack/packages/security-solution/storybook/config/README.mdx diff --git a/packages/security-solution/storybook/config/constants.ts b/x-pack/packages/security-solution/storybook/config/constants.ts similarity index 69% rename from packages/security-solution/storybook/config/constants.ts rename to x-pack/packages/security-solution/storybook/config/constants.ts index 216e3822a2943..7c078fa165bdd 100644 --- a/packages/security-solution/storybook/config/constants.ts +++ b/x-pack/packages/security-solution/storybook/config/constants.ts @@ -1,9 +1,8 @@ /* * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one * or more contributor license agreements. Licensed under the Elastic License - * 2.0 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. + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. */ /** The title of the Storybook. */ diff --git a/x-pack/packages/kbn-securitysolution-data-table/.storybook/main.js b/x-pack/packages/security-solution/storybook/config/index.ts old mode 100644 new mode 100755 similarity index 81% rename from x-pack/packages/kbn-securitysolution-data-table/.storybook/main.js rename to x-pack/packages/security-solution/storybook/config/index.ts index 86b48c32f103e..c8d2302efe529 --- a/x-pack/packages/kbn-securitysolution-data-table/.storybook/main.js +++ b/x-pack/packages/security-solution/storybook/config/index.ts @@ -5,4 +5,4 @@ * 2.0. */ -module.exports = require('@kbn/storybook').defaultConfig; +export { TITLE, URL } from './constants'; diff --git a/packages/security-solution/storybook/config/kibana.jsonc b/x-pack/packages/security-solution/storybook/config/kibana.jsonc similarity index 100% rename from packages/security-solution/storybook/config/kibana.jsonc rename to x-pack/packages/security-solution/storybook/config/kibana.jsonc diff --git a/packages/security-solution/storybook/config/main.ts b/x-pack/packages/security-solution/storybook/config/main.ts similarity index 64% rename from packages/security-solution/storybook/config/main.ts rename to x-pack/packages/security-solution/storybook/config/main.ts index 47a47a5a802b3..4e7fca030c2f6 100644 --- a/packages/security-solution/storybook/config/main.ts +++ b/x-pack/packages/security-solution/storybook/config/main.ts @@ -1,9 +1,8 @@ /* * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one * or more contributor license agreements. Licensed under the Elastic License - * 2.0 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. + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. */ import { defaultConfig } from '@kbn/storybook'; diff --git a/packages/security-solution/storybook/config/manager.ts b/x-pack/packages/security-solution/storybook/config/manager.ts similarity index 73% rename from packages/security-solution/storybook/config/manager.ts rename to x-pack/packages/security-solution/storybook/config/manager.ts index fb973258b9053..5dff8fa3fcbd6 100644 --- a/packages/security-solution/storybook/config/manager.ts +++ b/x-pack/packages/security-solution/storybook/config/manager.ts @@ -1,9 +1,8 @@ /* * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one * or more contributor license agreements. Licensed under the Elastic License - * 2.0 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. + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. */ import { addons } from '@storybook/addons'; diff --git a/packages/security-solution/storybook/config/package.json b/x-pack/packages/security-solution/storybook/config/package.json similarity index 67% rename from packages/security-solution/storybook/config/package.json rename to x-pack/packages/security-solution/storybook/config/package.json index 27d92c5c0ed1a..61360ea5a22f0 100644 --- a/packages/security-solution/storybook/config/package.json +++ b/x-pack/packages/security-solution/storybook/config/package.json @@ -2,5 +2,5 @@ "name": "@kbn/security-solution-storybook-config", "private": true, "version": "1.0.0", - "license": "SSPL-1.0 OR Elastic License 2.0" + "license": "Elastic License 2.0" } diff --git a/packages/security-solution/storybook/config/preview.ts b/x-pack/packages/security-solution/storybook/config/preview.ts similarity index 75% rename from packages/security-solution/storybook/config/preview.ts rename to x-pack/packages/security-solution/storybook/config/preview.ts index 5a53e48a916d8..8a74ff82a6af4 100644 --- a/packages/security-solution/storybook/config/preview.ts +++ b/x-pack/packages/security-solution/storybook/config/preview.ts @@ -1,9 +1,8 @@ /* * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one * or more contributor license agreements. Licensed under the Elastic License - * 2.0 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. + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. */ /* eslint-disable @typescript-eslint/no-namespace,@typescript-eslint/no-empty-interface */ diff --git a/packages/security-solution/storybook/config/tsconfig.json b/x-pack/packages/security-solution/storybook/config/tsconfig.json similarity index 84% rename from packages/security-solution/storybook/config/tsconfig.json rename to x-pack/packages/security-solution/storybook/config/tsconfig.json index 7b41c512d4ef0..1f8b2275f5191 100644 --- a/packages/security-solution/storybook/config/tsconfig.json +++ b/x-pack/packages/security-solution/storybook/config/tsconfig.json @@ -1,5 +1,5 @@ { - "extends": "../../../../tsconfig.base.json", + "extends": "../../../../../tsconfig.base.json", "compilerOptions": { "outDir": "target/types", "types": [ diff --git a/x-pack/plugins/translations/translations/fr-FR.json b/x-pack/plugins/translations/translations/fr-FR.json index 2feb566730389..285e5b8a1284f 100644 --- a/x-pack/plugins/translations/translations/fr-FR.json +++ b/x-pack/plugins/translations/translations/fr-FR.json @@ -2468,177 +2468,6 @@ "discover.viewAlert.searchSourceErrorTitle": "Erreur lors de la récupération de la source de recherche", "discover.viewModes.document.label": "Documents", "discover.viewModes.fieldStatistics.label": "Statistiques de champ", - "ecsDataQualityDashboard.allTab.allFieldsTableTitle": "Tous les champs – {indexName}", - "ecsDataQualityDashboard.checkAllErrorCheckingIndexMessage": "Une erreur s'est produite lors de la vérification de l'index {indexName}", - "ecsDataQualityDashboard.checkingLabel": "Vérification de {index}", - "ecsDataQualityDashboard.coldPatternTooltip": "{indices} {indices, plural, other {index}} correspondant au modèle {pattern} {indices, plural, =1 {est} other {sont}} de type \"cold\". Les index \"cold\" ne sont plus mis à jour et ne sont pas interrogés fréquemment. Les informations doivent toujours être interrogeables, mais il est acceptable que ces requêtes soient plus lentes.", - "ecsDataQualityDashboard.createADataQualityCaseForIndexHeaderText": "Créer un cas de qualité des données pour l'index {indexName}", - "ecsDataQualityDashboard.customTab.customFieldsTableTitle": "Champs personnalisés – {indexName}", - "ecsDataQualityDashboard.customTab.ecsComplaintFieldsTableTitle": "Champs de plainte ECS – {indexName}", - "ecsDataQualityDashboard.emptyErrorPrompt.errorLoadingMappingsBody": "Un problème est survenu lors du chargement des mappings : {error}", - "ecsDataQualityDashboard.emptyErrorPrompt.errorLoadingMetadataBody": "Les index correspondant au modèle {pattern} ne seront pas vérifiés, car une erreur s'est produite : {error}", - "ecsDataQualityDashboard.emptyErrorPrompt.errorLoadingMetadataTitle": "Les index correspondant au modèle {pattern} ne seront pas vérifiés", - "ecsDataQualityDashboard.emptyErrorPrompt.errorLoadingUnallowedValuesBody": "Un problème est survenu lors du chargement des valeurs non autorisées : {error}", - "ecsDataQualityDashboard.errorLoadingIlmExplainLabel": "Erreur lors du chargement d'ILM Explain : {details}", - "ecsDataQualityDashboard.errorLoadingMappingsLabel": "Erreur lors du chargement des mappings pour {patternOrIndexName} : {details}", - "ecsDataQualityDashboard.errorLoadingStatsLabel": "Erreur lors du chargement des statistiques : {details}", - "ecsDataQualityDashboard.errorLoadingUnallowedValuesLabel": "Erreur lors du chargement des valeurs non autorisées pour l'index {indexName} : {details}", - "ecsDataQualityDashboard.frozenPatternTooltip": "{indices} {indices, plural, other {index}} correspondant au modèle {pattern} {indices, plural, =1 {est} other {sont}} gelés. Les index gelés ne sont plus mis à jour et sont rarement interrogés. Les informations doivent toujours être interrogeables, mais il est acceptable que ces requêtes soient extrêmement lentes.", - "ecsDataQualityDashboard.hotPatternTooltip": "{indices} {indices, plural, other {index}} correspondant au modèle {pattern} {indices, plural, =1 {est} other {sont}} de type \"hot\". Les index \"hot\" sont mis à jour et interrogés de façon active.", - "ecsDataQualityDashboard.incompatibleTab.incompatibleFieldMappingsTableTitle": "Mappings de champ incompatibles – {indexName}", - "ecsDataQualityDashboard.incompatibleTab.incompatibleFieldValuesTableTitle": "Valeurs de champ incompatibles – {indexName}", - "ecsDataQualityDashboard.indexProperties.allCallout": "Tous les mappings relatifs aux champs de cet index, y compris ceux qui sont conformes à la version {version} d'Elastic Common Schema (ECS) et ceux qui ne le sont pas", - "ecsDataQualityDashboard.indexProperties.allCalloutTitle": "Tous les {fieldCount} {fieldCount, plural, =1 {mapping de champ} other {mappings de champ}}", - "ecsDataQualityDashboard.indexProperties.customCallout": "{fieldCount, plural, =1 {Ce champ n'est pas défini} other {Ces champs ne sont pas définis}} par la version {version} d'Elastic Common Schema (ECS). Un index peut contenir des champs personnalisés, cependant :", - "ecsDataQualityDashboard.indexProperties.customCalloutTitle": "{fieldCount} {fieldCount, plural, =1 {mapping de champ personnalisé} other {mappings de champ personnalisés}}", - "ecsDataQualityDashboard.indexProperties.ecsCompliantCallout": "{fieldCount, plural, =1 {Le type de mapping d'index et les valeurs de document de ce champ sont conformes} other {Les types de mapping d'index et les valeurs de document de ces champs sont conformes}} à la version {version} d'Elastic Common Schema (ECS)", - "ecsDataQualityDashboard.indexProperties.ecsCompliantCalloutTitle": "{fieldCount} {fieldCount, plural, =1 {champ conforme} other {champs conformes}} à ECS", - "ecsDataQualityDashboard.indexProperties.incompatibleCallout": "Les champs sont incompatibles avec ECS lorsque les mappings d'index, ou les valeurs des champs de l'index, ne sont pas conformes à la version {version} d'Elastic Common Schema (ECS).", - "ecsDataQualityDashboard.indexProperties.summaryMarkdownDescription": "L'index \"{indexName}\" contient des [mappings]({mappingUrl}) ou des valeurs de champs différents des [définitions]({ecsFieldReferenceUrl}) de la version \"{version}\" d'[Elastic Common Schema]({ecsReferenceUrl}) (ECS).", - "ecsDataQualityDashboard.patternDocsCountTooltip": "Nombre total de tous les index correspondant à : {pattern}", - "ecsDataQualityDashboard.statLabels.customIndexToolTip": "Décompte des mappings d'index personnalisés dans l'index {indexName}", - "ecsDataQualityDashboard.statLabels.customPatternToolTip": "Nombre total de mappings d'index personnalisés, dans les index correspondant au modèle {pattern}", - "ecsDataQualityDashboard.statLabels.incompatibleIndexToolTip": "Mappings et valeurs incompatibles avec ECS, dans l'index {indexName}", - "ecsDataQualityDashboard.statLabels.incompatiblePatternToolTip": "Nombre total de champs incompatibles avec ECS, dans les index correspondant au modèle {pattern}", - "ecsDataQualityDashboard.statLabels.indexDocsCountToolTip": "Nombre de documents dans l'index {indexName}", - "ecsDataQualityDashboard.statLabels.indexDocsPatternToolTip": "Nombre total de documents, dans les index correspondant au modèle {pattern}", - "ecsDataQualityDashboard.statLabels.totalCountOfIndicesCheckedMatchingPatternToolTip": "Nombre total d'index vérifiés correspondant au modèle {pattern}", - "ecsDataQualityDashboard.statLabels.totalCountOfIndicesMatchingPatternToolTip": "Nombre total d'index correspondant au modèle {pattern}", - "ecsDataQualityDashboard.summaryTable.indexToolTip": "Cet index correspond au nom d'index ou de modèle : {pattern}", - "ecsDataQualityDashboard.unmanagedPatternTooltip": "{indices} {indices, plural, other {index}} correspondant au modèle {pattern} {indices, plural, =1 {n'est pas géré} other {ne sont pas gérés}} par Index Lifecycle Management (ILM)", - "ecsDataQualityDashboard.warmPatternTooltip": "{indices} {indices, plural, other {index}} correspondant au modèle {pattern} {indices, plural, =1 {est} other {sont}} de type \"warm\". Les index \"warm\" ne sont plus mis à jour, mais ils sont toujours interrogés.", - "ecsDataQualityDashboard.addToCaseSuccessToast": "Résultats de qualité des données ajoutés avec succès au cas", - "ecsDataQualityDashboard.addToNewCaseButton": "Ajouter au nouveau cas", - "ecsDataQualityDashboard.cancelButton": "Annuler", - "ecsDataQualityDashboard.checkAllButton": "Tout vérifier", - "ecsDataQualityDashboard.coldDescription": "L'index n'est plus mis à jour et il est interrogé peu fréquemment. Les informations doivent toujours être interrogeables, mais il est acceptable que ces requêtes soient plus lentes.", - "ecsDataQualityDashboard.compareFieldsTable.documentValuesActualColumn": "Valeurs du document (réelles)", - "ecsDataQualityDashboard.compareFieldsTable.ecsDescriptionColumn": "Description ECS", - "ecsDataQualityDashboard.compareFieldsTable.ecsMappingTypeColumn": "Type de mapping ECS", - "ecsDataQualityDashboard.compareFieldsTable.ecsMappingTypeExpectedColumn": "Type de mapping ECS (attendu)", - "ecsDataQualityDashboard.compareFieldsTable.ecsValuesColumn": "Valeurs ECS", - "ecsDataQualityDashboard.compareFieldsTable.ecsValuesExpectedColumn": "Valeurs ECS (attendues)", - "ecsDataQualityDashboard.compareFieldsTable.fieldColumn": "Champ", - "ecsDataQualityDashboard.compareFieldsTable.indexMappingTypeActualColumn": "Type de mapping d'index (réel)", - "ecsDataQualityDashboard.compareFieldsTable.indexMappingTypeColumn": "Type de mapping d'index", - "ecsDataQualityDashboard.compareFieldsTable.searchFieldsPlaceholder": "Rechercher dans les champs", - "ecsDataQualityDashboard.copyToClipboardButton": "Copier dans le presse-papiers", - "ecsDataQualityDashboard.createADataQualityCaseHeaderText": "Créer un cas de qualité des données", - "ecsDataQualityDashboard.defaultPanelTitle": "Vérifier les mappings d'index", - "ecsDataQualityDashboard.ecsDataQualityDashboardSubtitle": "Vérifiez la compatibilité des mappings et des valeurs d'index avec", - "ecsDataQualityDashboard.ecsDataQualityDashboardTitle": "Qualité des données", - "ecsDataQualityDashboard.ecsSummaryDonutChart.chartTitle": "Mappings de champs", - "ecsDataQualityDashboard.ecsSummaryDonutChart.fieldsLabel": "Champs", - "ecsDataQualityDashboard.ecsVersionStat": "Version ECS", - "ecsDataQualityDashboard.emptyErrorPrompt.errorLoadingEcsMetadataTitle": "Impossible de charger les métadonnées ECS", - "ecsDataQualityDashboard.emptyErrorPrompt.errorLoadingEcsVersionTitle": "Impossible de charger la version ECS", - "ecsDataQualityDashboard.emptyErrorPrompt.errorLoadingMappingsTitle": "Impossible de charger les mappings d'index", - "ecsDataQualityDashboard.emptyErrorPrompt.errorLoadingUnallowedValuesTitle": "Impossible de charger les valeurs non autorisées", - "ecsDataQualityDashboard.emptyLoadingPrompt.loadingEcsMetadataPrompt": "Chargement des métadonnées ECS", - "ecsDataQualityDashboard.emptyLoadingPrompt.loadingMappingsPrompt": "Chargement des mappings", - "ecsDataQualityDashboard.emptyLoadingPrompt.loadingStatsPrompt": "Chargement des statistiques", - "ecsDataQualityDashboard.emptyLoadingPrompt.loadingUnallowedValuesPrompt": "Chargement des valeurs non autorisées", - "ecsDataQualityDashboard.errors.errorMayOccurLabel": "Des erreurs peuvent survenir lorsque le modèle ou les métadonnées de l'index sont temporairement indisponibles, ou si vous ne disposez pas des privilèges requis pour l'accès", - "ecsDataQualityDashboard.errors.manage": "gérer", - "ecsDataQualityDashboard.errors.monitor": "moniteur", - "ecsDataQualityDashboard.errors.or": "ou", - "ecsDataQualityDashboard.errors.read": "lire", - "ecsDataQualityDashboard.errors.theFollowingPrivilegesLabel": "Les privilèges suivants sont requis pour vérifier un index :", - "ecsDataQualityDashboard.errors.viewIndexMetadata": "view_index_metadata", - "ecsDataQualityDashboard.errorsPopover.copyToClipboardButton": "Copier dans le presse-papiers", - "ecsDataQualityDashboard.errorsPopover.errorsCalloutSummary": "La qualité des données n'a pas été vérifiée pour certains index", - "ecsDataQualityDashboard.errorsPopover.errorsTitle": "Erreurs", - "ecsDataQualityDashboard.errorsPopover.viewErrorsButton": "Afficher les erreurs", - "ecsDataQualityDashboard.errorsViewerTable.errorColumn": "Erreur", - "ecsDataQualityDashboard.errorsViewerTable.indexColumn": "Index", - "ecsDataQualityDashboard.errorsViewerTable.patternColumn": "Modèle", - "ecsDataQualityDashboard.fieldsLabel": "Champs", - "ecsDataQualityDashboard.frozenDescription": "L'index n'est plus mis à jour et il est rarement interrogé. Les informations doivent toujours être interrogeables, mais il est acceptable que ces requêtes soient extrêmement lentes.", - "ecsDataQualityDashboard.hotDescription": "L'index est mis à jour et interrogé de façon active", - "ecsDataQualityDashboard.ilmPhaseLabel": "Phase ILM", - "ecsDataQualityDashboard.ilmPhasesEmptyPromptBody": "La qualité des données sera vérifiée pour les index comprenant ces phases de gestion du cycle de vie des index (ILM, Index Lifecycle Management)", - "ecsDataQualityDashboard.ilmPhasesEmptyPromptColdLabel": "froid", - "ecsDataQualityDashboard.ilmPhasesEmptyPromptFrozenLabel": "gelé", - "ecsDataQualityDashboard.ilmPhasesEmptyPromptHotLabel": "hot", - "ecsDataQualityDashboard.ilmPhasesEmptyPromptIlmPhasesThatCanBeCheckedSubtitle": "Phases ILM dans lesquelles la qualité des données peut être vérifiée", - "ecsDataQualityDashboard.ilmPhasesEmptyPromptIlmPhasesThatCannotBeCheckedSubtitle": "Phases ILM dans lesquelles la vérification ne peut pas être effectuée", - "ecsDataQualityDashboard.ilmPhasesEmptyPromptITheFollowingIlmPhasesLabel": "Les phases ILM suivantes ne sont pas disponibles pour la vérification de la qualité des données, car leur accès est plus lent", - "ecsDataQualityDashboard.ilmPhasesEmptyPromptTitle": "Sélectionner une ou plusieurs phases ILM", - "ecsDataQualityDashboard.ilmPhasesEmptyPromptUnmanagedLabel": "non géré", - "ecsDataQualityDashboard.ilmPhasesEmptyPromptWarmLabel": "warm", - "ecsDataQualityDashboard.indexLifecycleManagementPhasesTooltip": "La qualité des données sera vérifiée pour les index comprenant ces phases de gestion du cycle de vie des index (ILM, Index Lifecycle Management)", - "ecsDataQualityDashboard.indexNameLabel": "Nom de l'index", - "ecsDataQualityDashboard.indexProperties.addToNewCaseButton": "Ajouter au nouveau cas", - "ecsDataQualityDashboard.indexProperties.allCalloutEmptyContent": "Cet index ne contient aucun mapping", - "ecsDataQualityDashboard.indexProperties.allCalloutEmptyTitle": "Aucun mapping", - "ecsDataQualityDashboard.indexProperties.allFieldsLabel": "Tous les champs", - "ecsDataQualityDashboard.indexProperties.copyToClipboardButton": "Copier dans le presse-papiers", - "ecsDataQualityDashboard.indexProperties.customEmptyContent": "Tous les mappings de champs de cet index sont définis par Elastic Common Schema", - "ecsDataQualityDashboard.indexProperties.customEmptyTitle": "Tous les mappings de champs définis par ECS", - "ecsDataQualityDashboard.indexProperties.customFieldsLabel": "Champs personnalisés", - "ecsDataQualityDashboard.indexProperties.custonDetectionEngineRulesWorkMessage": "✅ Les règles de moteur de détection personnalisées fonctionnent", - "ecsDataQualityDashboard.indexProperties.detectionEngineRulesWillWorkMessage": "✅ Les règles de moteur de détection fonctionneront pour ces champs", - "ecsDataQualityDashboard.indexProperties.detectionEngineRulesWontWorkMessage": "❌ Les règles de moteur de détection référençant ces champs ne leur correspondront peut-être pas correctement", - "ecsDataQualityDashboard.indexProperties.docsLabel": "Documents", - "ecsDataQualityDashboard.indexProperties.ecsCompliantEmptyContent": "Aucun mapping de champ de cet index n'est conforme à Elastic Common Schema (ECS). L'index doit (au moins) contenir un champ de date @timestamp.", - "ecsDataQualityDashboard.indexProperties.ecsCompliantEmptyTitle": "Aucun mapping conforme à ECS", - "ecsDataQualityDashboard.indexProperties.ecsCompliantFieldsLabel": "Champs conformes à ECS", - "ecsDataQualityDashboard.indexProperties.ecsCompliantMappingsAreFullySupportedMessage": "✅ Les mappings et valeurs de champs conformes à ECS sont totalement pris en charge", - "ecsDataQualityDashboard.indexProperties.ecsVersionMarkdownComment": "Version Elastic Common Schema (ECS)", - "ecsDataQualityDashboard.indexProperties.incompatibleEmptyContent": "Tous les mappings de champs et valeurs de documents de cet index sont conformes à Elastic Common Schema (ECS).", - "ecsDataQualityDashboard.indexProperties.incompatibleEmptyTitle": "Tous les mappings et valeurs de champs sont conformes à ECS", - "ecsDataQualityDashboard.indexProperties.incompatibleFieldsTab": "Champs incompatibles", - "ecsDataQualityDashboard.indexProperties.indexMarkdown": "Index", - "ecsDataQualityDashboard.indexProperties.mappingThatConflictWithEcsMessage": "❌ Les mappings ou valeurs de champs qui ne sont pas conformes à ECS ne sont pas pris en charge", - "ecsDataQualityDashboard.indexProperties.missingTimestampCallout": "Veuillez envisager d'ajouter un mapping de champ de @timestamp (date) à cet index, comme requis par Elastic Common Schema (ECS), car :", - "ecsDataQualityDashboard.indexProperties.missingTimestampCalloutTitle": "Mapping de champ @timestamp (date) manquant pour cet index", - "ecsDataQualityDashboard.indexProperties.otherAppCapabilitiesWorkProperlyMessage": "✅ Les autres capacités de l'application fonctionnent correctement", - "ecsDataQualityDashboard.indexProperties.pagesDisplayEventsMessage": "✅ Les pages affichent les événements et les champs correctement", - "ecsDataQualityDashboard.indexProperties.pagesMayNotDisplayFieldsMessage": "🌕 Certaines pages et fonctionnalités peuvent ne pas afficher ces champs", - "ecsDataQualityDashboard.indexProperties.preBuiltDetectionEngineRulesWorkMessage": "✅ Les règles de moteur de détection préconstruites fonctionnent", - "ecsDataQualityDashboard.indexProperties.sometimesIndicesCreatedByOlderDescription": "Parfois, les index créés par des intégrations plus anciennes comporteront des mappings ou des valeurs qui étaient conformes, mais ne le sont plus.", - "ecsDataQualityDashboard.indexProperties.summaryMarkdownTitle": "Qualité des données", - "ecsDataQualityDashboard.indexProperties.summaryTab": "Résumé", - "ecsDataQualityDashboard.indexProperties.unknownCategoryLabel": "Inconnu", - "ecsDataQualityDashboard.lastCheckedLabel": "Dernière vérification", - "ecsDataQualityDashboard.patternLabel.allPassedTooltip": "Tous les index correspondant à ce modèle ont réussi les vérifications de qualité des données", - "ecsDataQualityDashboard.patternLabel.someFailedTooltip": "Certains index correspondant à ce modèle ont échoué aux vérifications de qualité des données", - "ecsDataQualityDashboard.patternLabel.someUncheckedTooltip": "La qualité des données n'a pas été vérifiée pour certains index correspondant à ce modèle", - "ecsDataQualityDashboard.patternSummary.docsLabel": "Documents", - "ecsDataQualityDashboard.patternSummary.indicesLabel": "Index", - "ecsDataQualityDashboard.patternSummary.patternOrIndexTooltip": "Modèle, ou index spécifique", - "ecsDataQualityDashboard.selectAnIndexPrompt": "Sélectionner un index pour le comparer à la version ECS", - "ecsDataQualityDashboard.selectOneOrMorPhasesPlaceholder": "Sélectionner une ou plusieurs phases ILM", - "ecsDataQualityDashboard.statLabels.checkedLabel": "vérifié", - "ecsDataQualityDashboard.statLabels.customLabel": "Personnalisé", - "ecsDataQualityDashboard.statLabels.docsLabel": "Documents", - "ecsDataQualityDashboard.statLabels.fieldsLabel": "champs", - "ecsDataQualityDashboard.statLabels.incompatibleLabel": "Incompatible", - "ecsDataQualityDashboard.statLabels.indicesLabel": "Index", - "ecsDataQualityDashboard.statLabels.totalDocsToolTip": "Nombre total de documents, dans tous les index", - "ecsDataQualityDashboard.statLabels.totalIncompatibleToolTip": "Nombre total de champs incompatibles avec ECS, dans tous les index qui ont été vérifiés", - "ecsDataQualityDashboard.statLabels.totalIndicesCheckedToolTip": "Nombre total de tous les index vérifiés", - "ecsDataQualityDashboard.statLabels.totalIndicesToolTip": "Nombre total de tous les index", - "ecsDataQualityDashboard.summaryTable.collapseLabel": "Réduire", - "ecsDataQualityDashboard.summaryTable.docsColumn": "Documents", - "ecsDataQualityDashboard.summaryTable.expandLabel": "Développer", - "ecsDataQualityDashboard.summaryTable.expandRowsColumn": "Développer les lignes", - "ecsDataQualityDashboard.summaryTable.failedTooltip": "Échoué", - "ecsDataQualityDashboard.summaryTable.ilmPhaseColumn": "Phase ILM", - "ecsDataQualityDashboard.summaryTable.incompatibleFieldsColumn": "Champs incompatibles", - "ecsDataQualityDashboard.summaryTable.indexColumn": "Index", - "ecsDataQualityDashboard.summaryTable.indexesNameLabel": "Nom de l'index", - "ecsDataQualityDashboard.summaryTable.indicesCheckedColumn": "Index vérifiés", - "ecsDataQualityDashboard.summaryTable.indicesColumn": "Index", - "ecsDataQualityDashboard.summaryTable.passedTooltip": "Approuvé", - "ecsDataQualityDashboard.summaryTable.resultColumn": "Résultat", - "ecsDataQualityDashboard.summaryTable.thisIndexHasNotBeenCheckedTooltip": "Cet index n'a pas été vérifié", - "ecsDataQualityDashboard.technicalPreviewBadge": "Version d'évaluation technique", - "ecsDataQualityDashboard.timestampDescriptionLabel": "Date/heure d'origine de l'événement. Il s'agit des date et heure extraites de l'événement, représentant généralement le moment auquel l'événement a été généré par la source. Si la source de l'événement ne comporte pas d'horodatage original, cette valeur est habituellement remplie la première fois que l'événement a été reçu par le pipeline. Champs requis pour tous les événements.", - "ecsDataQualityDashboard.toasts.copiedErrorsToastTitle": "Erreurs copiées dans le presse-papiers", - "ecsDataQualityDashboard.toasts.copiedResultsToastTitle": "Résultats copiés dans le presse-papiers", - "ecsDataQualityDashboard.unmanagedDescription": "L'index n'est pas géré par la Gestion du cycle de vie des index (ILM)", - "ecsDataQualityDashboard.warmDescription": "L'index n'est plus mis à jour mais il est toujours interrogé", "embeddableApi.addPanel.savedObjectAddedToContainerSuccessMessageTitle": "{savedObjectName} a été ajouté", "embeddableApi.attributeService.saveToLibraryError": "Une erreur s'est produite lors de l'enregistrement. Erreur : {errorMessage}", "embeddableApi.errors.embeddableFactoryNotFound": "Impossible de charger {type}. Veuillez effectuer une mise à niveau vers la distribution par défaut d'Elasticsearch et de Kibana avec la licence appropriée.", diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 9caebf91e4f74..8c87e175bea63 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -2468,177 +2468,6 @@ "discover.viewAlert.searchSourceErrorTitle": "検索ソースの取得エラー", "discover.viewModes.document.label": "ドキュメント", "discover.viewModes.fieldStatistics.label": "フィールド統計情報", - "ecsDataQualityDashboard.allTab.allFieldsTableTitle": "すべてのフィールド - {indexName}", - "ecsDataQualityDashboard.checkAllErrorCheckingIndexMessage": "インデックス{indexName}の確認中にエラーが発生しました", - "ecsDataQualityDashboard.checkingLabel": "{index}の確認中", - "ecsDataQualityDashboard.coldPatternTooltip": "\"{pattern}\"パターンと一致する\"{indices}\"{indices, plural, =1 {インデックス} other {インデックス}}{indices, plural, =1 {は} other {は}}コールドです。コールドインデックスは更新されず、ほとんど照会されません。情報はまだ検索可能でなければなりませんが、クエリが低速でも問題ありません。", - "ecsDataQualityDashboard.createADataQualityCaseForIndexHeaderText": "インデックス{indexName}のデータ品質ケースを作成", - "ecsDataQualityDashboard.customTab.customFieldsTableTitle": "カスタムフィールド - {indexName}", - "ecsDataQualityDashboard.customTab.ecsComplaintFieldsTableTitle": "ECS互換フィールド - {indexName}", - "ecsDataQualityDashboard.emptyErrorPrompt.errorLoadingMappingsBody": "マッピングの読み込み中に問題が発生しました:{error}", - "ecsDataQualityDashboard.emptyErrorPrompt.errorLoadingMetadataBody": "次のエラーが発生したため、{pattern}パターンと一致するインデックスはチェックされません:{error}", - "ecsDataQualityDashboard.emptyErrorPrompt.errorLoadingMetadataTitle": "{pattern}パターンと一致するインデックスはチェックされません", - "ecsDataQualityDashboard.emptyErrorPrompt.errorLoadingUnallowedValuesBody": "許可されていない値の読み込み中に問題が発生しました:{error}", - "ecsDataQualityDashboard.errorLoadingIlmExplainLabel": "ILM Explainの読み込みエラー:{details}", - "ecsDataQualityDashboard.errorLoadingMappingsLabel": "{patternOrIndexName}のマッピングの読み込みエラー:{details}", - "ecsDataQualityDashboard.errorLoadingStatsLabel": "統計情報の読み込みエラー:{details}", - "ecsDataQualityDashboard.errorLoadingUnallowedValuesLabel": "インデックス{indexName}の許可されていない値の読み込みエラー:{details}", - "ecsDataQualityDashboard.frozenPatternTooltip": "\"{pattern}\"パターンと一致する\"{indices}\"{indices, plural, =1 {インデックス} other {インデックス}}{indices, plural, =1 {は} other {は}}フローズンです。フローズンインデックスは更新されず、ほとんど照会されません。情報はまだ検索可能でなければなりませんが、クエリが非常に低速でも問題ありません。", - "ecsDataQualityDashboard.hotPatternTooltip": "\"{pattern}\"パターンと一致する\"{indices}\"{indices, plural, =1 {インデックス} other {インデックス}}{indices, plural, =1 {は} other {は}}ホットです。ホットインデックスはアクティブに更新されており、照会されます。", - "ecsDataQualityDashboard.incompatibleTab.incompatibleFieldMappingsTableTitle": "非互換フィールドマッピング - {indexName}", - "ecsDataQualityDashboard.incompatibleTab.incompatibleFieldValuesTableTitle": "非互換フィールド値 - {indexName}", - "ecsDataQualityDashboard.indexProperties.allCallout": "Elastic Common Schema(ESC)、バージョン{version}と互換性があるフィールドも、互換性のないフィールドも含めて、このインデックスのフィールドのすべてのマッピング", - "ecsDataQualityDashboard.indexProperties.allCalloutTitle": "すべての{fieldCount}個の{fieldCount, plural, =1 {フィールドマッピング} other {フィールドマッピング}}", - "ecsDataQualityDashboard.indexProperties.customCallout": "{fieldCount, plural, =1 {このフィールドは} other {このフィールドは}}Elastic Common Schema(ECS)バージョン{version}で定義されていません。ただし、インデックスにはカスタムフィールドを含めることができます:", - "ecsDataQualityDashboard.indexProperties.customCalloutTitle": "{fieldCount}個のカスタム{fieldCount, plural, =1 {フィールドマッピング} other {フィールドマッピング}}", - "ecsDataQualityDashboard.indexProperties.ecsCompliantCallout": "{fieldCount, plural, =1 {このフィールドのインデックスマッピングタイプとドキュメント値は} other {これらのフィールドのインデックスマッピングタイプとドキュメント値は}}Elastic Common Schema(ECS)バージョン{version}に準拠しています", - "ecsDataQualityDashboard.indexProperties.ecsCompliantCalloutTitle": "{fieldCount}個のECS互換フィールド{fieldCount, plural, =1 {フィールド} other {フィールド}}", - "ecsDataQualityDashboard.indexProperties.incompatibleCallout": "インデックスのマッピングやインデックスのフィールドの値がElastic Common Schema(ECS)、バージョン{version}に準拠していない場合、フィールドはECSと非互換となります。", - "ecsDataQualityDashboard.indexProperties.summaryMarkdownDescription": "`{indexName}`インデックスは[マッピング]({mappingUrl})またはフィールド値が[Elastic Common Schema]({ecsReferenceUrl})(ECS)、バージョン`{version}`の[定義]({ecsFieldReferenceUrl})と異なっています。", - "ecsDataQualityDashboard.patternDocsCountTooltip": "{pattern}と一致するすべてのインデックスの合計件数", - "ecsDataQualityDashboard.statLabels.customIndexToolTip": "{indexName}インデックスのカスタムフィールドマッピングの件数", - "ecsDataQualityDashboard.statLabels.customPatternToolTip": "{pattern}パターンと一致するインデックスのカスタムフィールドマッピングの合計件数", - "ecsDataQualityDashboard.statLabels.incompatibleIndexToolTip": "{indexName}インデックスのESCと互換性があるマッピングと値", - "ecsDataQualityDashboard.statLabels.incompatiblePatternToolTip": "{pattern}パターンと一致するインデックスのECSと互換性があるフィールドの合計件数", - "ecsDataQualityDashboard.statLabels.indexDocsCountToolTip": "{indexName}インデックスのドキュメントの件数", - "ecsDataQualityDashboard.statLabels.indexDocsPatternToolTip": "{pattern}パターンと一致するインデックスのドキュメントの合計件数", - "ecsDataQualityDashboard.statLabels.totalCountOfIndicesCheckedMatchingPatternToolTip": "{pattern}パターンと一致する確認されたインデックスの合計件数", - "ecsDataQualityDashboard.statLabels.totalCountOfIndicesMatchingPatternToolTip": "{pattern}パターンと一致するインデックスの合計件数", - "ecsDataQualityDashboard.summaryTable.indexToolTip": "このインデックスはパターンまたはインデックス名と一致します:{pattern}", - "ecsDataQualityDashboard.unmanagedPatternTooltip": "\"{pattern}\"パターンと一致する\"{indices}\"{indices, plural, =1 {インデックス} other {インデックス}}{indices, plural, =1 {は} other {は}}インデックスライフサイクル管理(ILM)で管理されていません", - "ecsDataQualityDashboard.warmPatternTooltip": "\"{pattern}\"パターンと一致する\"{indices}\"{indices, plural, =1 {インデックス} other {インデックス}}{indices, plural, =1 {は} other {は}}ウォームです。ウォームインデックスは更新されませんが、まだ照会されています。", - "ecsDataQualityDashboard.addToCaseSuccessToast": "正常にデータ品質結果がケースに追加されました", - "ecsDataQualityDashboard.addToNewCaseButton": "新しいケースに追加", - "ecsDataQualityDashboard.cancelButton": "キャンセル", - "ecsDataQualityDashboard.checkAllButton": "すべて確認", - "ecsDataQualityDashboard.coldDescription": "インデックスは更新されず、頻繁に照会されません。情報はまだ検索可能でなければなりませんが、クエリが低速でも問題ありません。", - "ecsDataQualityDashboard.compareFieldsTable.documentValuesActualColumn": "ドキュメント値(実際)", - "ecsDataQualityDashboard.compareFieldsTable.ecsDescriptionColumn": "ECS説明", - "ecsDataQualityDashboard.compareFieldsTable.ecsMappingTypeColumn": "ECSマッピングタイプ", - "ecsDataQualityDashboard.compareFieldsTable.ecsMappingTypeExpectedColumn": "ECSマッピングタイプ(想定)", - "ecsDataQualityDashboard.compareFieldsTable.ecsValuesColumn": "ECS値", - "ecsDataQualityDashboard.compareFieldsTable.ecsValuesExpectedColumn": "ECS値(想定)", - "ecsDataQualityDashboard.compareFieldsTable.fieldColumn": "フィールド", - "ecsDataQualityDashboard.compareFieldsTable.indexMappingTypeActualColumn": "インデックスマッピングタイプ(実際)", - "ecsDataQualityDashboard.compareFieldsTable.indexMappingTypeColumn": "インデックスマッピングタイプ", - "ecsDataQualityDashboard.compareFieldsTable.searchFieldsPlaceholder": "検索フィールド", - "ecsDataQualityDashboard.copyToClipboardButton": "クリップボードにコピー", - "ecsDataQualityDashboard.createADataQualityCaseHeaderText": "データ品質ケースを作成", - "ecsDataQualityDashboard.defaultPanelTitle": "インデックスマッピングの確認", - "ecsDataQualityDashboard.ecsDataQualityDashboardSubtitle": "互換性に関してインデックスマッピングと値を確認", - "ecsDataQualityDashboard.ecsDataQualityDashboardTitle": "データ品質", - "ecsDataQualityDashboard.ecsSummaryDonutChart.chartTitle": "フィールドマッピング", - "ecsDataQualityDashboard.ecsSummaryDonutChart.fieldsLabel": "フィールド", - "ecsDataQualityDashboard.ecsVersionStat": "ECSバージョン", - "ecsDataQualityDashboard.emptyErrorPrompt.errorLoadingEcsMetadataTitle": "ECSメタデータを読み込めません", - "ecsDataQualityDashboard.emptyErrorPrompt.errorLoadingEcsVersionTitle": "ECSバージョンを読み込めません", - "ecsDataQualityDashboard.emptyErrorPrompt.errorLoadingMappingsTitle": "インデックスマッピングを読み込めません", - "ecsDataQualityDashboard.emptyErrorPrompt.errorLoadingUnallowedValuesTitle": "許可されていない値を読み込めません", - "ecsDataQualityDashboard.emptyLoadingPrompt.loadingEcsMetadataPrompt": "ECSメタデータを読み込んでいます", - "ecsDataQualityDashboard.emptyLoadingPrompt.loadingMappingsPrompt": "マッピングを読み込んでいます", - "ecsDataQualityDashboard.emptyLoadingPrompt.loadingStatsPrompt": "統計情報を読み込んでいます", - "ecsDataQualityDashboard.emptyLoadingPrompt.loadingUnallowedValuesPrompt": "許可されていない値を読み込んでいます", - "ecsDataQualityDashboard.errors.errorMayOccurLabel": "パターンまたはインデックスメタデータが一時的に使用できないか、アクセスに必要な権限がないため、エラーが発生する場合があります", - "ecsDataQualityDashboard.errors.manage": "管理", - "ecsDataQualityDashboard.errors.monitor": "監視", - "ecsDataQualityDashboard.errors.or": "または", - "ecsDataQualityDashboard.errors.read": "読み取り", - "ecsDataQualityDashboard.errors.theFollowingPrivilegesLabel": "インデックスを確認するには次の権限が必要です:", - "ecsDataQualityDashboard.errors.viewIndexMetadata": "view_index_metadata", - "ecsDataQualityDashboard.errorsPopover.copyToClipboardButton": "クリップボードにコピー", - "ecsDataQualityDashboard.errorsPopover.errorsCalloutSummary": "一部のインデックスのデータ品質が確認されませんでした", - "ecsDataQualityDashboard.errorsPopover.errorsTitle": "エラー", - "ecsDataQualityDashboard.errorsPopover.viewErrorsButton": "エラーを表示", - "ecsDataQualityDashboard.errorsViewerTable.errorColumn": "エラー", - "ecsDataQualityDashboard.errorsViewerTable.indexColumn": "インデックス", - "ecsDataQualityDashboard.errorsViewerTable.patternColumn": "パターン", - "ecsDataQualityDashboard.fieldsLabel": "フィールド", - "ecsDataQualityDashboard.frozenDescription": "インデックスは更新されず、ほとんど照会されません。情報はまだ検索可能でなければなりませんが、クエリが非常に低速でも問題ありません。", - "ecsDataQualityDashboard.hotDescription": "インデックスはアクティブに更新されており、照会されます", - "ecsDataQualityDashboard.ilmPhaseLabel": "ILMフェーズ", - "ecsDataQualityDashboard.ilmPhasesEmptyPromptBody": "これらのインデックスライフサイクル管理(ILM)フェーズのインデックスはデータ品質が確認されます", - "ecsDataQualityDashboard.ilmPhasesEmptyPromptColdLabel": "コールド", - "ecsDataQualityDashboard.ilmPhasesEmptyPromptFrozenLabel": "凍結", - "ecsDataQualityDashboard.ilmPhasesEmptyPromptHotLabel": "ホット", - "ecsDataQualityDashboard.ilmPhasesEmptyPromptIlmPhasesThatCanBeCheckedSubtitle": "データ品質を確認できるILMフェーズ", - "ecsDataQualityDashboard.ilmPhasesEmptyPromptIlmPhasesThatCannotBeCheckedSubtitle": "確認できないILMフェーズ", - "ecsDataQualityDashboard.ilmPhasesEmptyPromptITheFollowingIlmPhasesLabel": "次のILMフェーズは、アクセスが低速になるため、データ品質が確認できません", - "ecsDataQualityDashboard.ilmPhasesEmptyPromptTitle": "1つ以上のILMフェーズを選択", - "ecsDataQualityDashboard.ilmPhasesEmptyPromptUnmanagedLabel": "管理対象外", - "ecsDataQualityDashboard.ilmPhasesEmptyPromptWarmLabel": "ウォーム", - "ecsDataQualityDashboard.indexLifecycleManagementPhasesTooltip": "これらのインデックスライフサイクル管理(ILM)フェーズのインデックスはデータ品質が確認されます", - "ecsDataQualityDashboard.indexNameLabel": "インデックス名", - "ecsDataQualityDashboard.indexProperties.addToNewCaseButton": "新しいケースに追加", - "ecsDataQualityDashboard.indexProperties.allCalloutEmptyContent": "このインデックスにはマッピングが含まれていません", - "ecsDataQualityDashboard.indexProperties.allCalloutEmptyTitle": "マッピングなし", - "ecsDataQualityDashboard.indexProperties.allFieldsLabel": "すべてのフィールド", - "ecsDataQualityDashboard.indexProperties.copyToClipboardButton": "クリップボードにコピー", - "ecsDataQualityDashboard.indexProperties.customEmptyContent": "このインデックスのすべてのフィールドマッピングはElastic Common Schemaによって定義されています", - "ecsDataQualityDashboard.indexProperties.customEmptyTitle": "ECSによって定義されたすべてのフィールドマッピング", - "ecsDataQualityDashboard.indexProperties.customFieldsLabel": "カスタムフィールド", - "ecsDataQualityDashboard.indexProperties.custonDetectionEngineRulesWorkMessage": "✅ カスタム検出エンジンルールが動作する", - "ecsDataQualityDashboard.indexProperties.detectionEngineRulesWillWorkMessage": "✅ これらのフィールドの検出エンジンルールが動作する", - "ecsDataQualityDashboard.indexProperties.detectionEngineRulesWontWorkMessage": "❌ これらのフィールドを参照する検出エンジンルールが正常に一致しない場合がある", - "ecsDataQualityDashboard.indexProperties.docsLabel": "ドキュメント", - "ecsDataQualityDashboard.indexProperties.ecsCompliantEmptyContent": "このインデックスのどのフィールドマッピングもElastic Common Schema(ECS)と互換性がありません。インデックスには(1つ以上の)@timestamp日付フィールドを含める必要があります。", - "ecsDataQualityDashboard.indexProperties.ecsCompliantEmptyTitle": "ECS互換マッピングがありません", - "ecsDataQualityDashboard.indexProperties.ecsCompliantFieldsLabel": "ECS互換フィールド", - "ecsDataQualityDashboard.indexProperties.ecsCompliantMappingsAreFullySupportedMessage": "✅ ECS互換マッピングおよびフィールド値が完全にサポートされている", - "ecsDataQualityDashboard.indexProperties.ecsVersionMarkdownComment": "Elastic Common Schema(ECS)バージョン", - "ecsDataQualityDashboard.indexProperties.incompatibleEmptyContent": "このインデックスのすべてのフィールドマッピングとドキュメント値がElastic Common Schema(ECS)と互換性があります。", - "ecsDataQualityDashboard.indexProperties.incompatibleEmptyTitle": "すべてのフィールドマッピングと値がECSと互換性があります", - "ecsDataQualityDashboard.indexProperties.incompatibleFieldsTab": "非互換フィールド", - "ecsDataQualityDashboard.indexProperties.indexMarkdown": "インデックス", - "ecsDataQualityDashboard.indexProperties.mappingThatConflictWithEcsMessage": "❌ ECSと互換性がないマッピングまたはフィールド値はサポートされません", - "ecsDataQualityDashboard.indexProperties.missingTimestampCallout": "次の理由のため、Elastic Common Schema(ECS)で必要な@timestamp(日付)フィールドマッピングをこのインデックスに追加することを検討してください。", - "ecsDataQualityDashboard.indexProperties.missingTimestampCalloutTitle": "このインデックスの@timestamp(日付)フィールドマッピングが見つかりません", - "ecsDataQualityDashboard.indexProperties.otherAppCapabilitiesWorkProperlyMessage": "✅ 他のアプリ機能が正常に動作する", - "ecsDataQualityDashboard.indexProperties.pagesDisplayEventsMessage": "✅ ページにイベントとフィールドが正常に表示される", - "ecsDataQualityDashboard.indexProperties.pagesMayNotDisplayFieldsMessage": "🌕 一部のページと機能にこれらのフィールドが表示されない場合がある", - "ecsDataQualityDashboard.indexProperties.preBuiltDetectionEngineRulesWorkMessage": "✅ 構築済み検出エンジンルールが動作する", - "ecsDataQualityDashboard.indexProperties.sometimesIndicesCreatedByOlderDescription": "場合によって、古い統合で作成されたインデックスには、以前あった互換性がなくなったマッピングまたは値が含まれることがあります。", - "ecsDataQualityDashboard.indexProperties.summaryMarkdownTitle": "データ品質", - "ecsDataQualityDashboard.indexProperties.summaryTab": "まとめ", - "ecsDataQualityDashboard.indexProperties.unknownCategoryLabel": "不明", - "ecsDataQualityDashboard.lastCheckedLabel": "前回確認日時", - "ecsDataQualityDashboard.patternLabel.allPassedTooltip": "このパターンと一致するすべてのインデックスは、データ品質チェックに合格しました", - "ecsDataQualityDashboard.patternLabel.someFailedTooltip": "このパターンと一致する一部のインデックスは、データ品質チェックに失敗しました", - "ecsDataQualityDashboard.patternLabel.someUncheckedTooltip": "このパターンと一致する一部のインデックスは、データ品質が確認されませんでした", - "ecsDataQualityDashboard.patternSummary.docsLabel": "ドキュメント", - "ecsDataQualityDashboard.patternSummary.indicesLabel": "インデックス", - "ecsDataQualityDashboard.patternSummary.patternOrIndexTooltip": "パターンまたは特定のインデックス", - "ecsDataQualityDashboard.selectAnIndexPrompt": "ECSバージョンと比較するインデックスを選択", - "ecsDataQualityDashboard.selectOneOrMorPhasesPlaceholder": "1つ以上のILMフェーズを選択", - "ecsDataQualityDashboard.statLabels.checkedLabel": "確認済み", - "ecsDataQualityDashboard.statLabels.customLabel": "カスタム", - "ecsDataQualityDashboard.statLabels.docsLabel": "ドキュメント", - "ecsDataQualityDashboard.statLabels.fieldsLabel": "フィールド", - "ecsDataQualityDashboard.statLabels.incompatibleLabel": "非互換", - "ecsDataQualityDashboard.statLabels.indicesLabel": "インデックス", - "ecsDataQualityDashboard.statLabels.totalDocsToolTip": "すべてのインデックスのドキュメントの合計数", - "ecsDataQualityDashboard.statLabels.totalIncompatibleToolTip": "確認されたすべてのインデックスのECSと互換性がないフィールドの合計件数", - "ecsDataQualityDashboard.statLabels.totalIndicesCheckedToolTip": "確認されたすべてのインデックスの合計数", - "ecsDataQualityDashboard.statLabels.totalIndicesToolTip": "すべてのインデックスの合計数", - "ecsDataQualityDashboard.summaryTable.collapseLabel": "縮小", - "ecsDataQualityDashboard.summaryTable.docsColumn": "ドキュメント", - "ecsDataQualityDashboard.summaryTable.expandLabel": "拡張", - "ecsDataQualityDashboard.summaryTable.expandRowsColumn": "行を展開", - "ecsDataQualityDashboard.summaryTable.failedTooltip": "失敗", - "ecsDataQualityDashboard.summaryTable.ilmPhaseColumn": "ILMフェーズ", - "ecsDataQualityDashboard.summaryTable.incompatibleFieldsColumn": "非互換フィールド", - "ecsDataQualityDashboard.summaryTable.indexColumn": "インデックス", - "ecsDataQualityDashboard.summaryTable.indexesNameLabel": "インデックス名", - "ecsDataQualityDashboard.summaryTable.indicesCheckedColumn": "確認されたインデックス", - "ecsDataQualityDashboard.summaryTable.indicesColumn": "インデックス", - "ecsDataQualityDashboard.summaryTable.passedTooltip": "合格", - "ecsDataQualityDashboard.summaryTable.resultColumn": "結果", - "ecsDataQualityDashboard.summaryTable.thisIndexHasNotBeenCheckedTooltip": "このインデックスは確認されていません", - "ecsDataQualityDashboard.technicalPreviewBadge": "テクニカルプレビュー", - "ecsDataQualityDashboard.timestampDescriptionLabel": "イベントが生成された日時これはイベントから抽出された日時で、一般的にはイベントがソースから生成された日時を表します。イベントソースに元のタイムスタンプがない場合は、通常、この値はイベントがパイプラインによって受信された最初の日時が入力されます。すべてのイベントの必須フィールドです。", - "ecsDataQualityDashboard.toasts.copiedErrorsToastTitle": "エラーをクリップボードにコピーしました", - "ecsDataQualityDashboard.toasts.copiedResultsToastTitle": "結果をクリップボードにコピーしました", - "ecsDataQualityDashboard.unmanagedDescription": "インデックスはインデックスライフサイクル管理(ILM)で管理されていません", - "ecsDataQualityDashboard.warmDescription": "インデックスは更新されませんが、まだ照会されています", "embeddableApi.addPanel.savedObjectAddedToContainerSuccessMessageTitle": "{savedObjectName}が追加されました", "embeddableApi.attributeService.saveToLibraryError": "保存中にエラーが発生しました。エラー:{errorMessage}", "embeddableApi.errors.embeddableFactoryNotFound": "{type}を読み込めません。Elasticsearch と Kibanaのデフォルトのディストリビューションを適切なライセンスでアップグレードしてください。", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 75901fdc42c3c..23ecf9913249e 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -2468,177 +2468,6 @@ "discover.viewAlert.searchSourceErrorTitle": "提取搜索源时出错", "discover.viewModes.document.label": "文档", "discover.viewModes.fieldStatistics.label": "字段统计信息", - "ecsDataQualityDashboard.allTab.allFieldsTableTitle": "所有字段 - {indexName}", - "ecsDataQualityDashboard.checkAllErrorCheckingIndexMessage": "检查索引 {indexName} 时发生错误", - "ecsDataQualityDashboard.checkingLabel": "正在检查 {index}", - "ecsDataQualityDashboard.coldPatternTooltip": "与 {pattern} 模式匹配的 {indices} 个{indices, plural, other {索引}}{indices, plural, other {为}}冷索引。冷索引不再进行更新,且不被经常查询。这些信息仍需能够搜索,但查询速度快慢并不重要。", - "ecsDataQualityDashboard.createADataQualityCaseForIndexHeaderText": "为索引 {indexName} 创建数据质量案例", - "ecsDataQualityDashboard.customTab.customFieldsTableTitle": "定制字段 - {indexName}", - "ecsDataQualityDashboard.customTab.ecsComplaintFieldsTableTitle": "符合 ECS 规范的字段 - {indexName}", - "ecsDataQualityDashboard.emptyErrorPrompt.errorLoadingMappingsBody": "加载映射时出现问题:{error}", - "ecsDataQualityDashboard.emptyErrorPrompt.errorLoadingMetadataBody": "将不会检查与 {pattern} 模式匹配的索引,因为出现错误:{error}", - "ecsDataQualityDashboard.emptyErrorPrompt.errorLoadingMetadataTitle": "将不会检查与 {pattern} 模式匹配的索引", - "ecsDataQualityDashboard.emptyErrorPrompt.errorLoadingUnallowedValuesBody": "加载不允许使用的值时出现错误:{error}", - "ecsDataQualityDashboard.errorLoadingIlmExplainLabel": "加载 ILM 说明时出错:{details}", - "ecsDataQualityDashboard.errorLoadingMappingsLabel": "加载 {patternOrIndexName} 的映射时出错:{details}", - "ecsDataQualityDashboard.errorLoadingStatsLabel": "加载统计信息时出错:{details}", - "ecsDataQualityDashboard.errorLoadingUnallowedValuesLabel": "加载不允许索引 {indexName} 使用的值时出错:{details}", - "ecsDataQualityDashboard.frozenPatternTooltip": "与 {pattern} 模式匹配的 {indices} 个{indices, plural, other {索引}}{indices, plural, other {为}}已冻结索引。不再更新并且极少会查询已冻结索引。仍然需要能够搜索信息,但如果那些查询速度极慢,也没有关系。", - "ecsDataQualityDashboard.hotPatternTooltip": "与 {pattern} 模式匹配的 {indices} 个{indices, plural, other {索引}}{indices, plural, other {为}}热索引。热索引会被主动地更新和查询。", - "ecsDataQualityDashboard.incompatibleTab.incompatibleFieldMappingsTableTitle": "不兼容的字段映射 - {indexName}", - "ecsDataQualityDashboard.incompatibleTab.incompatibleFieldValuesTableTitle": "不兼容的字段值 - {indexName}", - "ecsDataQualityDashboard.indexProperties.allCallout": "此索引中字段的所有映射,包括遵循 Elastic Common Schema (ECS) 版本 {version} 的字段和不遵循该版本的字段", - "ecsDataQualityDashboard.indexProperties.allCalloutTitle": "所有 {fieldCount} 个{fieldCount, plural, other {字段映射}}", - "ecsDataQualityDashboard.indexProperties.customCallout": "{fieldCount, plural, =1 {此字段} other {这些字段}}不通过 Elastic Common Schema (ECS) 版本 {version} 来定义。索引可能包含定制字段,但是:", - "ecsDataQualityDashboard.indexProperties.customCalloutTitle": "{fieldCount} 个定制 {fieldCount, plural, other {字段映射}}", - "ecsDataQualityDashboard.indexProperties.ecsCompliantCallout": "{fieldCount, plural, =1 {此字段的索引映射类型和文档值} other {这些字段的索引映射类型和文档值}}遵循 Elastic Common Schema (ECS) 版本 {version}", - "ecsDataQualityDashboard.indexProperties.ecsCompliantCalloutTitle": "{fieldCount} 个符合 ECS 规范的{fieldCount, plural, other {字段}}", - "ecsDataQualityDashboard.indexProperties.incompatibleCallout": "索引映射或索引中字段的值未遵循 Elastic Common Schema (ECS) 版本 {version} 时,字段将与 ECS 不兼容。", - "ecsDataQualityDashboard.indexProperties.summaryMarkdownDescription": "`{indexName}` 索引具有与 [Elastic Common Schema]({ecsReferenceUrl}) (ECS) 版本 `{version}` [定义]({ecsFieldReferenceUrl}) 不同的[映射]({mappingUrl}) 或字段值。", - "ecsDataQualityDashboard.patternDocsCountTooltip": "与以下模式匹配的所有索引的总计数:{pattern}", - "ecsDataQualityDashboard.statLabels.customIndexToolTip": "{indexName} 索引中定制字段映射的计数", - "ecsDataQualityDashboard.statLabels.customPatternToolTip": "与 {pattern} 模式匹配的索引中定制字段映射的总计数", - "ecsDataQualityDashboard.statLabels.incompatibleIndexToolTip": "{indexName} 索引中与 ECS 不兼容的映射和值", - "ecsDataQualityDashboard.statLabels.incompatiblePatternToolTip": "与 {pattern} 模式匹配的索引中与 ECS 不兼容的字段的总计数", - "ecsDataQualityDashboard.statLabels.indexDocsCountToolTip": "{indexName} 索引中文档计数", - "ecsDataQualityDashboard.statLabels.indexDocsPatternToolTip": "与 {pattern} 模式匹配的索引中文档的总计数", - "ecsDataQualityDashboard.statLabels.totalCountOfIndicesCheckedMatchingPatternToolTip": "经检查与 {pattern} 模式匹配的索引的总计数", - "ecsDataQualityDashboard.statLabels.totalCountOfIndicesMatchingPatternToolTip": "与 {pattern} 模式匹配的索引的总计数", - "ecsDataQualityDashboard.summaryTable.indexToolTip": "此索引与模式或索引名称相匹配:{pattern}", - "ecsDataQualityDashboard.unmanagedPatternTooltip": "与 {pattern} 模式匹配的 {indices} 个{indices, plural, other {索引}}{indices, plural, other {}}不通过索引生命周期管理 (ILM) 进行管理", - "ecsDataQualityDashboard.warmPatternTooltip": "与 {pattern} 模式匹配的 {indices} 个{indices, plural, other {索引}}{indices, plural, other {为}}温索引。不再更新但仍会查询温索引。", - "ecsDataQualityDashboard.addToCaseSuccessToast": "已成功将数据质量结果添加到案例", - "ecsDataQualityDashboard.addToNewCaseButton": "添加到新案例", - "ecsDataQualityDashboard.cancelButton": "取消", - "ecsDataQualityDashboard.checkAllButton": "全部选中", - "ecsDataQualityDashboard.coldDescription": "该索引不再进行更新,且不被经常查询。这些信息仍需能够搜索,但查询速度快慢并不重要。", - "ecsDataQualityDashboard.compareFieldsTable.documentValuesActualColumn": "文档值(实际)", - "ecsDataQualityDashboard.compareFieldsTable.ecsDescriptionColumn": "ECS 描述", - "ecsDataQualityDashboard.compareFieldsTable.ecsMappingTypeColumn": "ECS 映射类型", - "ecsDataQualityDashboard.compareFieldsTable.ecsMappingTypeExpectedColumn": "ECS 映射类型(预期)", - "ecsDataQualityDashboard.compareFieldsTable.ecsValuesColumn": "ECS 值", - "ecsDataQualityDashboard.compareFieldsTable.ecsValuesExpectedColumn": "ECS 值(预期)", - "ecsDataQualityDashboard.compareFieldsTable.fieldColumn": "字段", - "ecsDataQualityDashboard.compareFieldsTable.indexMappingTypeActualColumn": "索引映射类型(实际)", - "ecsDataQualityDashboard.compareFieldsTable.indexMappingTypeColumn": "索引映射类型", - "ecsDataQualityDashboard.compareFieldsTable.searchFieldsPlaceholder": "搜索字段", - "ecsDataQualityDashboard.copyToClipboardButton": "复制到剪贴板", - "ecsDataQualityDashboard.createADataQualityCaseHeaderText": "创建数据质量案例", - "ecsDataQualityDashboard.defaultPanelTitle": "检查索引映射", - "ecsDataQualityDashboard.ecsDataQualityDashboardSubtitle": "检查索引映射和值以了解与以下项的兼容性", - "ecsDataQualityDashboard.ecsDataQualityDashboardTitle": "数据质量", - "ecsDataQualityDashboard.ecsSummaryDonutChart.chartTitle": "字段映射", - "ecsDataQualityDashboard.ecsSummaryDonutChart.fieldsLabel": "字段", - "ecsDataQualityDashboard.ecsVersionStat": "ECS 版本", - "ecsDataQualityDashboard.emptyErrorPrompt.errorLoadingEcsMetadataTitle": "无法加载 ECS 元数据", - "ecsDataQualityDashboard.emptyErrorPrompt.errorLoadingEcsVersionTitle": "无法加载 ECS 版本", - "ecsDataQualityDashboard.emptyErrorPrompt.errorLoadingMappingsTitle": "无法加载索引映射", - "ecsDataQualityDashboard.emptyErrorPrompt.errorLoadingUnallowedValuesTitle": "无法加载不允许使用的值", - "ecsDataQualityDashboard.emptyLoadingPrompt.loadingEcsMetadataPrompt": "正在加载 ECS 元数据", - "ecsDataQualityDashboard.emptyLoadingPrompt.loadingMappingsPrompt": "正在加载映射", - "ecsDataQualityDashboard.emptyLoadingPrompt.loadingStatsPrompt": "正在加载统计信息", - "ecsDataQualityDashboard.emptyLoadingPrompt.loadingUnallowedValuesPrompt": "正在加载不允许使用的值", - "ecsDataQualityDashboard.errors.errorMayOccurLabel": "模式或索引元数据暂时不可用,或由于您没有所需访问权限时,可能会发生错误", - "ecsDataQualityDashboard.errors.manage": "管理", - "ecsDataQualityDashboard.errors.monitor": "监测", - "ecsDataQualityDashboard.errors.or": "或", - "ecsDataQualityDashboard.errors.read": "读取", - "ecsDataQualityDashboard.errors.theFollowingPrivilegesLabel": "检查索引需要以下权限:", - "ecsDataQualityDashboard.errors.viewIndexMetadata": "view_index_metadata", - "ecsDataQualityDashboard.errorsPopover.copyToClipboardButton": "复制到剪贴板", - "ecsDataQualityDashboard.errorsPopover.errorsCalloutSummary": "未检查某些索引以了解数据质量", - "ecsDataQualityDashboard.errorsPopover.errorsTitle": "错误", - "ecsDataQualityDashboard.errorsPopover.viewErrorsButton": "查看错误", - "ecsDataQualityDashboard.errorsViewerTable.errorColumn": "错误", - "ecsDataQualityDashboard.errorsViewerTable.indexColumn": "索引", - "ecsDataQualityDashboard.errorsViewerTable.patternColumn": "模式", - "ecsDataQualityDashboard.fieldsLabel": "字段", - "ecsDataQualityDashboard.frozenDescription": "不再更新并且极少查询该索引。仍然需要能够搜索信息,但如果那些查询速度极慢,也没有关系。", - "ecsDataQualityDashboard.hotDescription": "该索引会被主动地更新和查询", - "ecsDataQualityDashboard.ilmPhaseLabel": "ILM 阶段", - "ecsDataQualityDashboard.ilmPhasesEmptyPromptBody": "将检查具有这些索引生命周期管理 (ILM) 阶段的索引以了解数据质量", - "ecsDataQualityDashboard.ilmPhasesEmptyPromptColdLabel": "冷", - "ecsDataQualityDashboard.ilmPhasesEmptyPromptFrozenLabel": "冻结", - "ecsDataQualityDashboard.ilmPhasesEmptyPromptHotLabel": "热", - "ecsDataQualityDashboard.ilmPhasesEmptyPromptIlmPhasesThatCanBeCheckedSubtitle": "可进行检查以了解数据质量的 ILM 阶段", - "ecsDataQualityDashboard.ilmPhasesEmptyPromptIlmPhasesThatCannotBeCheckedSubtitle": "无法检查的 ILM 阶段", - "ecsDataQualityDashboard.ilmPhasesEmptyPromptITheFollowingIlmPhasesLabel": "由于访问速度较慢,无法检查以下 ILM 阶段以了解数据质量", - "ecsDataQualityDashboard.ilmPhasesEmptyPromptTitle": "选择一个或多个 ILM 阶段", - "ecsDataQualityDashboard.ilmPhasesEmptyPromptUnmanagedLabel": "未受管", - "ecsDataQualityDashboard.ilmPhasesEmptyPromptWarmLabel": "温", - "ecsDataQualityDashboard.indexLifecycleManagementPhasesTooltip": "将检查具有这些索引生命周期管理 (ILM) 阶段的索引以了解数据质量", - "ecsDataQualityDashboard.indexNameLabel": "索引名称", - "ecsDataQualityDashboard.indexProperties.addToNewCaseButton": "添加到新案例", - "ecsDataQualityDashboard.indexProperties.allCalloutEmptyContent": "此索引不包含任何映射", - "ecsDataQualityDashboard.indexProperties.allCalloutEmptyTitle": "无映射", - "ecsDataQualityDashboard.indexProperties.allFieldsLabel": "所有字段", - "ecsDataQualityDashboard.indexProperties.copyToClipboardButton": "复制到剪贴板", - "ecsDataQualityDashboard.indexProperties.customEmptyContent": "此索引中的所有字段映射均由 Elastic Common Schema 定义", - "ecsDataQualityDashboard.indexProperties.customEmptyTitle": "由 ECS 字义的所有字段映射", - "ecsDataQualityDashboard.indexProperties.customFieldsLabel": "定制字段", - "ecsDataQualityDashboard.indexProperties.custonDetectionEngineRulesWorkMessage": "✅ 定制检测引擎规则有效", - "ecsDataQualityDashboard.indexProperties.detectionEngineRulesWillWorkMessage": "✅ 检测引擎规则将适用于这些字段", - "ecsDataQualityDashboard.indexProperties.detectionEngineRulesWontWorkMessage": "❌ 引用这些字段的检测引擎规则可能无法与其正确匹配", - "ecsDataQualityDashboard.indexProperties.docsLabel": "文档", - "ecsDataQualityDashboard.indexProperties.ecsCompliantEmptyContent": "此索引中没有任何字段映射遵循 Elastic Common Schema (ECS)。此索引必须(至少)包含一个 @timestamp 日期字段。", - "ecsDataQualityDashboard.indexProperties.ecsCompliantEmptyTitle": "没有符合 ECS 规范的映射", - "ecsDataQualityDashboard.indexProperties.ecsCompliantFieldsLabel": "符合 ECS 规范的字段", - "ecsDataQualityDashboard.indexProperties.ecsCompliantMappingsAreFullySupportedMessage": "✅ 完全支持符合 ECS 规范的映射和字段值", - "ecsDataQualityDashboard.indexProperties.ecsVersionMarkdownComment": "Elastic Common Schema (ECS) 版本", - "ecsDataQualityDashboard.indexProperties.incompatibleEmptyContent": "此索引中的所有字段映射和文档值均符合 Elastic Common Schema (ECS) 规范。", - "ecsDataQualityDashboard.indexProperties.incompatibleEmptyTitle": "所有字段映射和值均符合 ECS 规范", - "ecsDataQualityDashboard.indexProperties.incompatibleFieldsTab": "不兼容的字段", - "ecsDataQualityDashboard.indexProperties.indexMarkdown": "索引", - "ecsDataQualityDashboard.indexProperties.mappingThatConflictWithEcsMessage": "❌ 不支持不符合 ECS 规范的映射或字段值", - "ecsDataQualityDashboard.indexProperties.missingTimestampCallout": "考虑根据 Elastic Common Schema (ECS) 的要求将 @timestamp(日期)字段映射添加到此索引,因为:", - "ecsDataQualityDashboard.indexProperties.missingTimestampCalloutTitle": "缺少此索引的 @timestamp(日期)字段映射", - "ecsDataQualityDashboard.indexProperties.otherAppCapabilitiesWorkProperlyMessage": "✅ 其他应用功能正常运行", - "ecsDataQualityDashboard.indexProperties.pagesDisplayEventsMessage": "✅ 页面正确显示事件和字段", - "ecsDataQualityDashboard.indexProperties.pagesMayNotDisplayFieldsMessage": "🌕 某些页面和功能可能不会显示这些字段", - "ecsDataQualityDashboard.indexProperties.preBuiltDetectionEngineRulesWorkMessage": "✅ 预构建的检测引擎规则有效", - "ecsDataQualityDashboard.indexProperties.sometimesIndicesCreatedByOlderDescription": "有时候,用较旧集成创建的索引的映射或值可能过去符合规范,但现在不再符合。", - "ecsDataQualityDashboard.indexProperties.summaryMarkdownTitle": "数据质量", - "ecsDataQualityDashboard.indexProperties.summaryTab": "摘要", - "ecsDataQualityDashboard.indexProperties.unknownCategoryLabel": "未知", - "ecsDataQualityDashboard.lastCheckedLabel": "上次检查时间", - "ecsDataQualityDashboard.patternLabel.allPassedTooltip": "与此模式匹配的所有索引均通过了数据质量检查", - "ecsDataQualityDashboard.patternLabel.someFailedTooltip": "与此模式匹配的某些索引未通过数据质量检查", - "ecsDataQualityDashboard.patternLabel.someUncheckedTooltip": "与此模式匹配的某些索引尚未进行数据质量检查", - "ecsDataQualityDashboard.patternSummary.docsLabel": "文档", - "ecsDataQualityDashboard.patternSummary.indicesLabel": "索引", - "ecsDataQualityDashboard.patternSummary.patternOrIndexTooltip": "模式或特定索引", - "ecsDataQualityDashboard.selectAnIndexPrompt": "选择索引以将其与 ECS 版本进行比较", - "ecsDataQualityDashboard.selectOneOrMorPhasesPlaceholder": "选择一个或多个 ILM 阶段", - "ecsDataQualityDashboard.statLabels.checkedLabel": "已检查", - "ecsDataQualityDashboard.statLabels.customLabel": "定制", - "ecsDataQualityDashboard.statLabels.docsLabel": "文档", - "ecsDataQualityDashboard.statLabels.fieldsLabel": "字段", - "ecsDataQualityDashboard.statLabels.incompatibleLabel": "不兼容", - "ecsDataQualityDashboard.statLabels.indicesLabel": "索引", - "ecsDataQualityDashboard.statLabels.totalDocsToolTip": "所有索引中文档的总计数", - "ecsDataQualityDashboard.statLabels.totalIncompatibleToolTip": "检查的所有索引中与 ECS 不兼容的字段的总计数", - "ecsDataQualityDashboard.statLabels.totalIndicesCheckedToolTip": "检查的所有索引的总计数", - "ecsDataQualityDashboard.statLabels.totalIndicesToolTip": "所有索引的总计数", - "ecsDataQualityDashboard.summaryTable.collapseLabel": "折叠", - "ecsDataQualityDashboard.summaryTable.docsColumn": "文档", - "ecsDataQualityDashboard.summaryTable.expandLabel": "展开", - "ecsDataQualityDashboard.summaryTable.expandRowsColumn": "展开行", - "ecsDataQualityDashboard.summaryTable.failedTooltip": "失败", - "ecsDataQualityDashboard.summaryTable.ilmPhaseColumn": "ILM 阶段", - "ecsDataQualityDashboard.summaryTable.incompatibleFieldsColumn": "不兼容的字段", - "ecsDataQualityDashboard.summaryTable.indexColumn": "索引", - "ecsDataQualityDashboard.summaryTable.indexesNameLabel": "索引名称", - "ecsDataQualityDashboard.summaryTable.indicesCheckedColumn": "已检查索引", - "ecsDataQualityDashboard.summaryTable.indicesColumn": "索引", - "ecsDataQualityDashboard.summaryTable.passedTooltip": "通过", - "ecsDataQualityDashboard.summaryTable.resultColumn": "结果", - "ecsDataQualityDashboard.summaryTable.thisIndexHasNotBeenCheckedTooltip": "尚未检查此索引", - "ecsDataQualityDashboard.technicalPreviewBadge": "技术预览", - "ecsDataQualityDashboard.timestampDescriptionLabel": "事件发生时的日期/时间。这是从事件中提取的日期/时间,通常表示源生成事件的时间。如果事件源没有原始时间戳,通常会在管道首次收到事件时填充此值。所有事件的必填字段。", - "ecsDataQualityDashboard.toasts.copiedErrorsToastTitle": "已将错误复制到剪贴板", - "ecsDataQualityDashboard.toasts.copiedResultsToastTitle": "已将结果复制到剪贴板", - "ecsDataQualityDashboard.unmanagedDescription": "此索引不由索引生命周期管理 (ILM) 进行管理", - "ecsDataQualityDashboard.warmDescription": "不再更新但仍会查询此索引", "embeddableApi.addPanel.savedObjectAddedToContainerSuccessMessageTitle": "{savedObjectName} 已添加", "embeddableApi.attributeService.saveToLibraryError": "保存时出错。错误:{errorMessage}", "embeddableApi.errors.embeddableFactoryNotFound": "{type} 无法加载。请升级到具有适当许可的默认 Elasticsearch 和 Kibana 分发。", diff --git a/yarn.lock b/yarn.lock index da8427476a383..83f9b163487d8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4001,7 +4001,7 @@ version "0.0.0" uid "" -"@kbn/ecs-data-quality-dashboard@link:x-pack/packages/kbn-ecs-data-quality-dashboard": +"@kbn/ecs-data-quality-dashboard@link:x-pack/packages/security-solution/ecs_data_quality_dashboard": version "0.0.0" uid "" @@ -4921,11 +4921,11 @@ version "0.0.0" uid "" -"@kbn/security-solution-side-nav@link:packages/security-solution/side_nav": +"@kbn/security-solution-side-nav@link:x-pack/packages/security-solution/side_nav": version "0.0.0" uid "" -"@kbn/security-solution-storybook-config@link:packages/security-solution/storybook/config": +"@kbn/security-solution-storybook-config@link:x-pack/packages/security-solution/storybook/config": version "0.0.0" uid "" @@ -4937,7 +4937,7 @@ version "0.0.0" uid "" -"@kbn/securitysolution-data-table@link:x-pack/packages/kbn-securitysolution-data-table": +"@kbn/securitysolution-data-table@link:x-pack/packages/security-solution/data_table": version "0.0.0" uid "" From 61bb52c65b4a829218039e1e42057f6fe022aa59 Mon Sep 17 00:00:00 2001 From: Marco Antonio Ghiani Date: Wed, 26 Apr 2023 14:23:48 +0200 Subject: [PATCH 07/73] [Infrastructure UI] Implement Metrics explorer views CRUD endpoints (#155621) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 📓 Summary Part of #152617 Closes #155111 This PR implements the CRUD endpoints for the metrics explorer views. Following the approach used for the InventoryView service, it exposes a client that abstracts all the logic concerned to the `metrics-explorer-view` saved objects. It also follows the guideline provided for [Versioning interfaces](https://docs.elastic.dev/kibana-dev-docs/versioning-interfaces) and [Versioning HTTP APIs](https://docs.elastic.dev/kibana-dev-docs/versioning-http-apis), preparing for the serverless. ## 🤓 Tips for the reviewer You can open the Kibana dev tools and play with the following snippet to test the create APIs, or you can perform the same requests with your preferred client: ``` // Get all GET kbn:/api/infra/metrics_explorer_views // Create one POST kbn:/api/infra/metrics_explorer_views { "attributes": { "name": "My view" } } // Get one GET kbn:/api/infra/metrics_explorer_views/ // Update one PUT kbn:/api/infra/metrics_explorer_views/ { "attributes": { "name": "My view 2" } } // Delete one DELETE kbn:/api/infra/metrics_explorer_views/ ``` --------- Co-authored-by: Marco Antonio Ghiani Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> --- x-pack/plugins/infra/common/http_api/index.ts | 1 + .../http_api/inventory_views/v1/common.ts | 2 +- .../plugins/infra/common/http_api/latest.ts | 1 + .../metrics_explorer_views/v1/common.ts | 68 ++++ .../v1/create_metrics_explorer_view.ts | 29 ++ .../v1/find_metrics_explorer_view.ts | 36 ++ .../v1/get_metrics_explorer_view.ts | 16 + .../metrics_explorer_views/v1/index.ts | 12 + .../v1/update_metrics_explorer_view.ts | 29 ++ .../common/metrics_explorer_views/defaults.ts | 2 +- .../common/metrics_explorer_views/index.ts | 1 + ....mock.ts => metrics_explorer_view.mock.ts} | 2 +- x-pack/plugins/infra/server/infra_server.ts | 2 + x-pack/plugins/infra/server/mocks.ts | 2 + x-pack/plugins/infra/server/plugin.ts | 15 +- .../server/routes/inventory_views/README.md | 4 + .../inventory_views/create_inventory_view.ts | 2 +- .../inventory_views/delete_inventory_view.ts | 2 +- .../inventory_views/find_inventory_view.ts | 2 +- .../inventory_views/get_inventory_view.ts | 2 +- .../inventory_views/update_inventory_view.ts | 2 +- .../routes/metrics_explorer_views/README.md | 354 ++++++++++++++++++ .../create_metrics_explorer_view.ts | 58 +++ .../delete_metrics_explorer_view.ts | 54 +++ .../find_metrics_explorer_view.ts | 49 +++ .../get_metrics_explorer_view.ts | 62 +++ .../routes/metrics_explorer_views/index.ts | 23 ++ .../update_metrics_explorer_view.ts | 65 ++++ .../metrics_explorer_view/types.ts | 12 +- .../inventory_views/inventory_views_client.ts | 24 +- .../services/metrics_explorer_views/index.ts | 14 + .../metrics_explorer_views_client.mock.ts | 17 + .../metrics_explorer_views_client.test.ts | 262 +++++++++++++ .../metrics_explorer_views_client.ts | 218 +++++++++++ .../metrics_explorer_views_service.mock.ts | 18 + .../metrics_explorer_views_service.ts | 39 ++ .../services/metrics_explorer_views/types.ts | 48 +++ x-pack/plugins/infra/server/types.ts | 2 + 38 files changed, 1528 insertions(+), 23 deletions(-) create mode 100644 x-pack/plugins/infra/common/http_api/metrics_explorer_views/v1/common.ts create mode 100644 x-pack/plugins/infra/common/http_api/metrics_explorer_views/v1/create_metrics_explorer_view.ts create mode 100644 x-pack/plugins/infra/common/http_api/metrics_explorer_views/v1/find_metrics_explorer_view.ts create mode 100644 x-pack/plugins/infra/common/http_api/metrics_explorer_views/v1/get_metrics_explorer_view.ts create mode 100644 x-pack/plugins/infra/common/http_api/metrics_explorer_views/v1/index.ts create mode 100644 x-pack/plugins/infra/common/http_api/metrics_explorer_views/v1/update_metrics_explorer_view.ts rename x-pack/plugins/infra/common/metrics_explorer_views/{metric_explorer_view.mock.ts => metrics_explorer_view.mock.ts} (93%) create mode 100644 x-pack/plugins/infra/server/routes/metrics_explorer_views/README.md create mode 100644 x-pack/plugins/infra/server/routes/metrics_explorer_views/create_metrics_explorer_view.ts create mode 100644 x-pack/plugins/infra/server/routes/metrics_explorer_views/delete_metrics_explorer_view.ts create mode 100644 x-pack/plugins/infra/server/routes/metrics_explorer_views/find_metrics_explorer_view.ts create mode 100644 x-pack/plugins/infra/server/routes/metrics_explorer_views/get_metrics_explorer_view.ts create mode 100644 x-pack/plugins/infra/server/routes/metrics_explorer_views/index.ts create mode 100644 x-pack/plugins/infra/server/routes/metrics_explorer_views/update_metrics_explorer_view.ts create mode 100644 x-pack/plugins/infra/server/services/metrics_explorer_views/index.ts create mode 100644 x-pack/plugins/infra/server/services/metrics_explorer_views/metrics_explorer_views_client.mock.ts create mode 100644 x-pack/plugins/infra/server/services/metrics_explorer_views/metrics_explorer_views_client.test.ts create mode 100644 x-pack/plugins/infra/server/services/metrics_explorer_views/metrics_explorer_views_client.ts create mode 100644 x-pack/plugins/infra/server/services/metrics_explorer_views/metrics_explorer_views_service.mock.ts create mode 100644 x-pack/plugins/infra/server/services/metrics_explorer_views/metrics_explorer_views_service.ts create mode 100644 x-pack/plugins/infra/server/services/metrics_explorer_views/types.ts diff --git a/x-pack/plugins/infra/common/http_api/index.ts b/x-pack/plugins/infra/common/http_api/index.ts index 355c5925702f7..e9dc741f39652 100644 --- a/x-pack/plugins/infra/common/http_api/index.ts +++ b/x-pack/plugins/infra/common/http_api/index.ts @@ -20,3 +20,4 @@ export * from './infra'; */ export * from './latest'; export * as inventoryViewsV1 from './inventory_views/v1'; +export * as metricsExplorerViewsV1 from './metrics_explorer_views/v1'; diff --git a/x-pack/plugins/infra/common/http_api/inventory_views/v1/common.ts b/x-pack/plugins/infra/common/http_api/inventory_views/v1/common.ts index c229170b8007b..3db684628334e 100644 --- a/x-pack/plugins/infra/common/http_api/inventory_views/v1/common.ts +++ b/x-pack/plugins/infra/common/http_api/inventory_views/v1/common.ts @@ -63,4 +63,4 @@ export const inventoryViewResponsePayloadRT = rt.type({ data: inventoryViewResponseRT, }); -export type GetInventoryViewResponsePayload = rt.TypeOf; +export type InventoryViewResponsePayload = rt.TypeOf; diff --git a/x-pack/plugins/infra/common/http_api/latest.ts b/x-pack/plugins/infra/common/http_api/latest.ts index 519da4a60dec1..effdbeda041da 100644 --- a/x-pack/plugins/infra/common/http_api/latest.ts +++ b/x-pack/plugins/infra/common/http_api/latest.ts @@ -6,3 +6,4 @@ */ export * from './inventory_views/v1'; +export * from './metrics_explorer_views/v1'; diff --git a/x-pack/plugins/infra/common/http_api/metrics_explorer_views/v1/common.ts b/x-pack/plugins/infra/common/http_api/metrics_explorer_views/v1/common.ts new file mode 100644 index 0000000000000..76b6daf60a324 --- /dev/null +++ b/x-pack/plugins/infra/common/http_api/metrics_explorer_views/v1/common.ts @@ -0,0 +1,68 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import { nonEmptyStringRt } from '@kbn/io-ts-utils'; +import * as rt from 'io-ts'; +import { either } from 'fp-ts/Either'; + +export const METRICS_EXPLORER_VIEW_URL = '/api/infra/metrics_explorer_views'; +export const METRICS_EXPLORER_VIEW_URL_ENTITY = `${METRICS_EXPLORER_VIEW_URL}/{metricsExplorerViewId}`; +export const getMetricsExplorerViewUrl = (metricsExplorerViewId?: string) => + [METRICS_EXPLORER_VIEW_URL, metricsExplorerViewId].filter(Boolean).join('/'); + +const metricsExplorerViewIdRT = new rt.Type( + 'MetricsExplorerViewId', + rt.string.is, + (u, c) => + either.chain(rt.string.validate(u, c), (id) => { + return id === '0' + ? rt.failure(u, c, `The metrics explorer view with id ${id} is not configurable.`) + : rt.success(id); + }), + String +); + +export const metricsExplorerViewRequestParamsRT = rt.type({ + metricsExplorerViewId: metricsExplorerViewIdRT, +}); + +export type MetricsExplorerViewRequestParams = rt.TypeOf; + +export const metricsExplorerViewRequestQueryRT = rt.partial({ + sourceId: rt.string, +}); + +export type MetricsExplorerViewRequestQuery = rt.TypeOf; + +const metricsExplorerViewAttributesResponseRT = rt.intersection([ + rt.strict({ + name: nonEmptyStringRt, + isDefault: rt.boolean, + isStatic: rt.boolean, + }), + rt.UnknownRecord, +]); + +const metricsExplorerViewResponseRT = rt.exact( + rt.intersection([ + rt.type({ + id: rt.string, + attributes: metricsExplorerViewAttributesResponseRT, + }), + rt.partial({ + updatedAt: rt.number, + version: rt.string, + }), + ]) +); + +export const metricsExplorerViewResponsePayloadRT = rt.type({ + data: metricsExplorerViewResponseRT, +}); + +export type GetMetricsExplorerViewResponsePayload = rt.TypeOf< + typeof metricsExplorerViewResponsePayloadRT +>; diff --git a/x-pack/plugins/infra/common/http_api/metrics_explorer_views/v1/create_metrics_explorer_view.ts b/x-pack/plugins/infra/common/http_api/metrics_explorer_views/v1/create_metrics_explorer_view.ts new file mode 100644 index 0000000000000..5550404529cf1 --- /dev/null +++ b/x-pack/plugins/infra/common/http_api/metrics_explorer_views/v1/create_metrics_explorer_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 { nonEmptyStringRt } from '@kbn/io-ts-utils'; +import * as rt from 'io-ts'; + +export const createMetricsExplorerViewAttributesRequestPayloadRT = rt.intersection([ + rt.type({ + name: nonEmptyStringRt, + }), + rt.UnknownRecord, + rt.exact(rt.partial({ isDefault: rt.undefined, isStatic: rt.undefined })), +]); + +export type CreateMetricsExplorerViewAttributesRequestPayload = rt.TypeOf< + typeof createMetricsExplorerViewAttributesRequestPayloadRT +>; + +export const createMetricsExplorerViewRequestPayloadRT = rt.type({ + attributes: createMetricsExplorerViewAttributesRequestPayloadRT, +}); + +export type CreateMetricsExplorerViewRequestPayload = rt.TypeOf< + typeof createMetricsExplorerViewRequestPayloadRT +>; diff --git a/x-pack/plugins/infra/common/http_api/metrics_explorer_views/v1/find_metrics_explorer_view.ts b/x-pack/plugins/infra/common/http_api/metrics_explorer_views/v1/find_metrics_explorer_view.ts new file mode 100644 index 0000000000000..c504b54a4f914 --- /dev/null +++ b/x-pack/plugins/infra/common/http_api/metrics_explorer_views/v1/find_metrics_explorer_view.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 { nonEmptyStringRt } from '@kbn/io-ts-utils'; +import * as rt from 'io-ts'; + +export const findMetricsExplorerViewAttributesResponseRT = rt.strict({ + name: nonEmptyStringRt, + isDefault: rt.boolean, + isStatic: rt.boolean, +}); + +const findMetricsExplorerViewResponseRT = rt.exact( + rt.intersection([ + rt.type({ + id: rt.string, + attributes: findMetricsExplorerViewAttributesResponseRT, + }), + rt.partial({ + updatedAt: rt.number, + version: rt.string, + }), + ]) +); + +export const findMetricsExplorerViewResponsePayloadRT = rt.type({ + data: rt.array(findMetricsExplorerViewResponseRT), +}); + +export type FindMetricsExplorerViewResponsePayload = rt.TypeOf< + typeof findMetricsExplorerViewResponsePayloadRT +>; diff --git a/x-pack/plugins/infra/common/http_api/metrics_explorer_views/v1/get_metrics_explorer_view.ts b/x-pack/plugins/infra/common/http_api/metrics_explorer_views/v1/get_metrics_explorer_view.ts new file mode 100644 index 0000000000000..8a828e00c917f --- /dev/null +++ b/x-pack/plugins/infra/common/http_api/metrics_explorer_views/v1/get_metrics_explorer_view.ts @@ -0,0 +1,16 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import * as rt from 'io-ts'; + +export const getMetricsExplorerViewRequestParamsRT = rt.type({ + metricsExplorerViewId: rt.string, +}); + +export type GetMetricsExplorerViewRequestParams = rt.TypeOf< + typeof getMetricsExplorerViewRequestParamsRT +>; diff --git a/x-pack/plugins/infra/common/http_api/metrics_explorer_views/v1/index.ts b/x-pack/plugins/infra/common/http_api/metrics_explorer_views/v1/index.ts new file mode 100644 index 0000000000000..62a0b7a633975 --- /dev/null +++ b/x-pack/plugins/infra/common/http_api/metrics_explorer_views/v1/index.ts @@ -0,0 +1,12 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export * from './common'; +export * from './get_metrics_explorer_view'; +export * from './find_metrics_explorer_view'; +export * from './create_metrics_explorer_view'; +export * from './update_metrics_explorer_view'; diff --git a/x-pack/plugins/infra/common/http_api/metrics_explorer_views/v1/update_metrics_explorer_view.ts b/x-pack/plugins/infra/common/http_api/metrics_explorer_views/v1/update_metrics_explorer_view.ts new file mode 100644 index 0000000000000..5bf327789a65c --- /dev/null +++ b/x-pack/plugins/infra/common/http_api/metrics_explorer_views/v1/update_metrics_explorer_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 { nonEmptyStringRt } from '@kbn/io-ts-utils'; +import * as rt from 'io-ts'; + +export const updateMetricsExplorerViewAttributesRequestPayloadRT = rt.intersection([ + rt.type({ + name: nonEmptyStringRt, + }), + rt.UnknownRecord, + rt.exact(rt.partial({ isDefault: rt.undefined, isStatic: rt.undefined })), +]); + +export type UpdateMetricsExplorerViewAttributesRequestPayload = rt.TypeOf< + typeof updateMetricsExplorerViewAttributesRequestPayloadRT +>; + +export const updateMetricsExplorerViewRequestPayloadRT = rt.type({ + attributes: updateMetricsExplorerViewAttributesRequestPayloadRT, +}); + +export type UpdateMetricsExplorerViewRequestPayload = rt.TypeOf< + typeof updateMetricsExplorerViewRequestPayloadRT +>; diff --git a/x-pack/plugins/infra/common/metrics_explorer_views/defaults.ts b/x-pack/plugins/infra/common/metrics_explorer_views/defaults.ts index 88771d1a76fcb..8c7e6ffff192f 100644 --- a/x-pack/plugins/infra/common/metrics_explorer_views/defaults.ts +++ b/x-pack/plugins/infra/common/metrics_explorer_views/defaults.ts @@ -9,7 +9,7 @@ import { i18n } from '@kbn/i18n'; import type { NonEmptyString } from '@kbn/io-ts-utils'; import type { MetricsExplorerViewAttributes } from './types'; -export const staticMetricsExplorerViewId = 'static'; +export const staticMetricsExplorerViewId = '0'; export const staticMetricsExplorerViewAttributes: MetricsExplorerViewAttributes = { name: i18n.translate('xpack.infra.savedView.defaultViewNameHosts', { diff --git a/x-pack/plugins/infra/common/metrics_explorer_views/index.ts b/x-pack/plugins/infra/common/metrics_explorer_views/index.ts index 6cc0ccaa93a6d..ae809a6c7c615 100644 --- a/x-pack/plugins/infra/common/metrics_explorer_views/index.ts +++ b/x-pack/plugins/infra/common/metrics_explorer_views/index.ts @@ -5,4 +5,5 @@ * 2.0. */ +export * from './defaults'; export * from './types'; diff --git a/x-pack/plugins/infra/common/metrics_explorer_views/metric_explorer_view.mock.ts b/x-pack/plugins/infra/common/metrics_explorer_views/metrics_explorer_view.mock.ts similarity index 93% rename from x-pack/plugins/infra/common/metrics_explorer_views/metric_explorer_view.mock.ts rename to x-pack/plugins/infra/common/metrics_explorer_views/metrics_explorer_view.mock.ts index e921c37dd21f8..98f6675f42a66 100644 --- a/x-pack/plugins/infra/common/metrics_explorer_views/metric_explorer_view.mock.ts +++ b/x-pack/plugins/infra/common/metrics_explorer_views/metrics_explorer_view.mock.ts @@ -8,7 +8,7 @@ import { staticMetricsExplorerViewAttributes } from './defaults'; import type { MetricsExplorerView, MetricsExplorerViewAttributes } from './types'; -export const createmetricsExplorerViewMock = ( +export const createMetricsExplorerViewMock = ( id: string, attributes: MetricsExplorerViewAttributes, updatedAt?: number, diff --git a/x-pack/plugins/infra/server/infra_server.ts b/x-pack/plugins/infra/server/infra_server.ts index 3b6ea0333f236..4d29974ceb75f 100644 --- a/x-pack/plugins/infra/server/infra_server.ts +++ b/x-pack/plugins/infra/server/infra_server.ts @@ -37,6 +37,7 @@ import { initOverviewRoute } from './routes/overview'; import { initProcessListRoute } from './routes/process_list'; import { initSnapshotRoute } from './routes/snapshot'; import { initInfraMetricsRoute } from './routes/infra'; +import { initMetricsExplorerViewRoutes } from './routes/metrics_explorer_views'; export const initInfraServer = (libs: InfraBackendLibs) => { initIpToHostName(libs); @@ -59,6 +60,7 @@ export const initInfraServer = (libs: InfraBackendLibs) => { initLogEntriesSummaryHighlightsRoute(libs); initLogViewRoutes(libs); initMetricExplorerRoute(libs); + initMetricsExplorerViewRoutes(libs); initMetricsAPIRoute(libs); initMetadataRoute(libs); initInventoryMetaRoute(libs); diff --git a/x-pack/plugins/infra/server/mocks.ts b/x-pack/plugins/infra/server/mocks.ts index 5a97f4a7d9a52..7e5a349cb1e01 100644 --- a/x-pack/plugins/infra/server/mocks.ts +++ b/x-pack/plugins/infra/server/mocks.ts @@ -10,6 +10,7 @@ import { createLogViewsServiceSetupMock, createLogViewsServiceStartMock, } from './services/log_views/log_views_service.mock'; +import { createMetricsExplorerViewsServiceStartMock } from './services/metrics_explorer_views/metrics_explorer_views_service.mock'; import { InfraPluginSetup, InfraPluginStart } from './types'; const createInfraSetupMock = () => { @@ -26,6 +27,7 @@ const createInfraStartMock = () => { getMetricIndices: jest.fn(), inventoryViews: createInventoryViewsServiceStartMock(), logViews: createLogViewsServiceStartMock(), + metricsExplorerViews: createMetricsExplorerViewsServiceStartMock(), }; return infraStartMock; }; diff --git a/x-pack/plugins/infra/server/plugin.ts b/x-pack/plugins/infra/server/plugin.ts index 2c114bb75d6e5..eb8777665895a 100644 --- a/x-pack/plugins/infra/server/plugin.ts +++ b/x-pack/plugins/infra/server/plugin.ts @@ -49,6 +49,7 @@ import { import { InventoryViewsService } from './services/inventory_views'; import { LogEntriesService } from './services/log_entries'; import { LogViewsService } from './services/log_views'; +import { MetricsExplorerViewsService } from './services/metrics_explorer_views'; import { RulesService } from './services/rules'; import { InfraConfig, @@ -122,6 +123,7 @@ export class InfraServerPlugin private metricsRules: RulesService; private inventoryViews: InventoryViewsService; private logViews: LogViewsService; + private metricsExplorerViews: MetricsExplorerViewsService; constructor(context: PluginInitializerContext) { this.config = context.config.get(); @@ -140,6 +142,9 @@ export class InfraServerPlugin this.inventoryViews = new InventoryViewsService(this.logger.get('inventoryViews')); this.logViews = new LogViewsService(this.logger.get('logViews')); + this.metricsExplorerViews = new MetricsExplorerViewsService( + this.logger.get('metricsExplorerViews') + ); } setup(core: InfraPluginCoreSetup, plugins: InfraServerPluginSetupDeps) { @@ -155,12 +160,13 @@ export class InfraServerPlugin ); const inventoryViews = this.inventoryViews.setup(); const logViews = this.logViews.setup(); + const metricsExplorerViews = this.metricsExplorerViews.setup(); // register saved object types core.savedObjects.registerType(infraSourceConfigurationSavedObjectType); - core.savedObjects.registerType(metricsExplorerViewSavedObjectType); core.savedObjects.registerType(inventoryViewSavedObjectType); core.savedObjects.registerType(logViewSavedObjectType); + core.savedObjects.registerType(metricsExplorerViewSavedObjectType); // TODO: separate these out individually and do away with "domains" as a temporary group // and make them available via the request context so we can do away with @@ -237,6 +243,7 @@ export class InfraServerPlugin defineInternalSourceConfiguration: sources.defineInternalSourceConfiguration.bind(sources), inventoryViews, logViews, + metricsExplorerViews, } as InfraPluginSetup; } @@ -258,9 +265,15 @@ export class InfraServerPlugin }, }); + const metricsExplorerViews = this.metricsExplorerViews.start({ + infraSources: this.libs.sources, + savedObjects: core.savedObjects, + }); + return { inventoryViews, logViews, + metricsExplorerViews, getMetricIndices: makeGetMetricIndices(this.libs.sources), }; } diff --git a/x-pack/plugins/infra/server/routes/inventory_views/README.md b/x-pack/plugins/infra/server/routes/inventory_views/README.md index 8a09aedef1b75..be7d1c3734157 100644 --- a/x-pack/plugins/infra/server/routes/inventory_views/README.md +++ b/x-pack/plugins/infra/server/routes/inventory_views/README.md @@ -221,6 +221,8 @@ Updates an inventory view. Any attribute can be updated except for `isDefault` and `isStatic`, which are derived by the source configuration preference set by the user. +Any attempt to update the static view with id `0` will return a `400 The inventory view with id 0 is not configurable.` + ### Request - **Method**: PUT @@ -324,6 +326,8 @@ Status code: 409 Deletes an inventory view. +Any attempt to delete the static view with id `0` will return a `400 The inventory view with id 0 is not configurable.` + ### Request - **Method**: DELETE diff --git a/x-pack/plugins/infra/server/routes/inventory_views/create_inventory_view.ts b/x-pack/plugins/infra/server/routes/inventory_views/create_inventory_view.ts index 8f3d52db7a6dd..90bb47d8a2d76 100644 --- a/x-pack/plugins/infra/server/routes/inventory_views/create_inventory_view.ts +++ b/x-pack/plugins/infra/server/routes/inventory_views/create_inventory_view.ts @@ -28,7 +28,7 @@ export const initCreateInventoryViewRoute = ({ }, async (_requestContext, request, response) => { const { body } = request; - const { inventoryViews } = (await getStartServices())[2]; + const [, , { inventoryViews }] = await getStartServices(); const inventoryViewsClient = inventoryViews.getScopedClient(request); try { diff --git a/x-pack/plugins/infra/server/routes/inventory_views/delete_inventory_view.ts b/x-pack/plugins/infra/server/routes/inventory_views/delete_inventory_view.ts index 83ad61fc46c52..e86e44fc0ac05 100644 --- a/x-pack/plugins/infra/server/routes/inventory_views/delete_inventory_view.ts +++ b/x-pack/plugins/infra/server/routes/inventory_views/delete_inventory_view.ts @@ -27,7 +27,7 @@ export const initDeleteInventoryViewRoute = ({ }, async (_requestContext, request, response) => { const { params } = request; - const { inventoryViews } = (await getStartServices())[2]; + const [, , { inventoryViews }] = await getStartServices(); const inventoryViewsClient = inventoryViews.getScopedClient(request); try { diff --git a/x-pack/plugins/infra/server/routes/inventory_views/find_inventory_view.ts b/x-pack/plugins/infra/server/routes/inventory_views/find_inventory_view.ts index abdfc2f8749e4..a9de3a426f14f 100644 --- a/x-pack/plugins/infra/server/routes/inventory_views/find_inventory_view.ts +++ b/x-pack/plugins/infra/server/routes/inventory_views/find_inventory_view.ts @@ -27,7 +27,7 @@ export const initFindInventoryViewRoute = ({ }, async (_requestContext, request, response) => { const { query } = request; - const { inventoryViews } = (await getStartServices())[2]; + const [, , { inventoryViews }] = await getStartServices(); const inventoryViewsClient = inventoryViews.getScopedClient(request); try { diff --git a/x-pack/plugins/infra/server/routes/inventory_views/get_inventory_view.ts b/x-pack/plugins/infra/server/routes/inventory_views/get_inventory_view.ts index 1a5f5adec136d..0cb9f815ef089 100644 --- a/x-pack/plugins/infra/server/routes/inventory_views/get_inventory_view.ts +++ b/x-pack/plugins/infra/server/routes/inventory_views/get_inventory_view.ts @@ -30,7 +30,7 @@ export const initGetInventoryViewRoute = ({ }, async (_requestContext, request, response) => { const { params, query } = request; - const { inventoryViews } = (await getStartServices())[2]; + const [, , { inventoryViews }] = await getStartServices(); const inventoryViewsClient = inventoryViews.getScopedClient(request); try { diff --git a/x-pack/plugins/infra/server/routes/inventory_views/update_inventory_view.ts b/x-pack/plugins/infra/server/routes/inventory_views/update_inventory_view.ts index d2b583437d177..0f225e0546fd1 100644 --- a/x-pack/plugins/infra/server/routes/inventory_views/update_inventory_view.ts +++ b/x-pack/plugins/infra/server/routes/inventory_views/update_inventory_view.ts @@ -32,7 +32,7 @@ export const initUpdateInventoryViewRoute = ({ }, async (_requestContext, request, response) => { const { body, params, query } = request; - const { inventoryViews } = (await getStartServices())[2]; + const [, , { inventoryViews }] = await getStartServices(); const inventoryViewsClient = inventoryViews.getScopedClient(request); try { diff --git a/x-pack/plugins/infra/server/routes/metrics_explorer_views/README.md b/x-pack/plugins/infra/server/routes/metrics_explorer_views/README.md new file mode 100644 index 0000000000000..d14d8298d0d0f --- /dev/null +++ b/x-pack/plugins/infra/server/routes/metrics_explorer_views/README.md @@ -0,0 +1,354 @@ +# Metrics Explorer Views CRUD api + +## Find all: `GET /api/infra/metrics_explorer_views` + +Retrieves all metrics explorer views in a reduced version. + +### Request + +- **Method**: GET +- **Path**: /api/infra/metrics_explorer_views +- **Query params**: + - `sourceId` _(optional)_: Specify a source id related to the metrics explorer views. Default value: `default`. + +### Response + +```json +GET /api/infra/metrics_explorer_views + +Status code: 200 + +{ + "data": [ + { + "id": "static", + "attributes": { + "name": "Default view", + "isDefault": false, + "isStatic": true + } + }, + { + "id": "927ad6a0-da0c-11ed-9487-41e9b90f96b9", + "version": "WzQwMiwxXQ==", + "updatedAt": 1681398305034, + "attributes": { + "name": "Ad-hoc", + "isDefault": true, + "isStatic": false + } + }, + { + "id": "c301ef20-da0c-11ed-aac0-77131228e6f1", + "version": "WzQxMCwxXQ==", + "updatedAt": 1681398386450, + "attributes": { + "name": "Custom", + "isDefault": false, + "isStatic": false + } + } + ] +} +``` + +## Get one: `GET /api/infra/metrics_explorer_views/{metricsExplorerViewId}` + +Retrieves a single metrics explorer view by ID + +### Request + +- **Method**: GET +- **Path**: /api/infra/metrics_explorer_views/{metricsExplorerViewId} +- **Query params**: + - `sourceId` _(optional)_: Specify a source id related to the metrics explorer view. Default value: `default`. + +### Response + +```json +GET /api/infra/metrics_explorer_views/927ad6a0-da0c-11ed-9487-41e9b90f96b9 + +Status code: 200 + +{ + "data": { + "id": "927ad6a0-da0c-11ed-9487-41e9b90f96b9", + "version": "WzQwMiwxXQ==", + "updatedAt": 1681398305034, + "attributes": { + "name": "Ad-hoc", + "isDefault": true, + "isStatic": false, + "metric": { + "type": "cpu" + }, + "sort": { + "by": "name", + "direction": "desc" + }, + "groupBy": [], + "nodeType": "host", + "view": "map", + "customOptions": [], + "customMetrics": [], + "boundsOverride": { + "max": 1, + "min": 0 + }, + "autoBounds": true, + "accountId": "", + "region": "", + "autoReload": false, + "filterQuery": { + "expression": "", + "kind": "kuery" + }, + "legend": { + "palette": "cool", + "reverseColors": false, + "steps": 10 + }, + "timelineOpen": false + } + } +} +``` + +```json +GET /api/infra/metrics_explorer_views/random-id + +Status code: 404 + +{ + "statusCode": 404, + "error": "Not Found", + "message": "Saved object [metrics-explorer-view/random-id] not found" +} +``` + +## Create one: `POST /api/infra/metrics_explorer_views` + +Creates a new metrics explorer view. + +### Request + +- **Method**: POST +- **Path**: /api/infra/metrics_explorer_views +- **Request body**: + ```json + { + "attributes": { + "name": "View name", + "metric": { + "type": "cpu" + }, + "sort": { + "by": "name", + "direction": "desc" + }, + //... + } + } + ``` + +### Response + +```json +POST /api/infra/metrics_explorer_views + +Status code: 201 + +{ + "data": { + "id": "927ad6a0-da0c-11ed-9487-41e9b90f96b9", + "version": "WzQwMiwxXQ==", + "updatedAt": 1681398305034, + "attributes": { + "name": "View name", + "isDefault": false, + "isStatic": false, + "metric": { + "type": "cpu" + }, + "sort": { + "by": "name", + "direction": "desc" + }, + "groupBy": [], + "nodeType": "host", + "view": "map", + "customOptions": [], + "customMetrics": [], + "boundsOverride": { + "max": 1, + "min": 0 + }, + "autoBounds": true, + "accountId": "", + "region": "", + "autoReload": false, + "filterQuery": { + "expression": "", + "kind": "kuery" + }, + "legend": { + "palette": "cool", + "reverseColors": false, + "steps": 10 + }, + "timelineOpen": false + } + } +} +``` + +Send in the payload a `name` attribute already held by another view: +```json +POST /api/infra/metrics_explorer_views + +Status code: 409 + +{ + "statusCode": 409, + "error": "Conflict", + "message": "A view with that name already exists." +} +``` + +## Update one: `PUT /api/infra/metrics_explorer_views/{metricsExplorerViewId}` + +Updates a metrics explorer view. + +Any attribute can be updated except for `isDefault` and `isStatic`, which are derived by the source configuration preference set by the user. + +Any attempt to update the static view with id `0` will return a `400 The metrics explorer view with id 0 is not configurable.` + +### Request + +- **Method**: PUT +- **Path**: /api/infra/metrics_explorer_views/{metricsExplorerViewId} +- **Query params**: + - `sourceId` _(optional)_: Specify a source id related to the metrics explorer view. Default value: `default`. +- **Request body**: + ```json + { + "attributes": { + "name": "View name", + "metric": { + "type": "cpu" + }, + "sort": { + "by": "name", + "direction": "desc" + }, + //... + } + } + ``` + +### Response + +```json +PUT /api/infra/metrics_explorer_views/927ad6a0-da0c-11ed-9487-41e9b90f96b9 + +Status code: 200 + +{ + "data": { + "id": "927ad6a0-da0c-11ed-9487-41e9b90f96b9", + "version": "WzQwMiwxXQ==", + "updatedAt": 1681398305034, + "attributes": { + "name": "View name", + "isDefault": false, + "isStatic": false, + "metric": { + "type": "cpu" + }, + "sort": { + "by": "name", + "direction": "desc" + }, + "groupBy": [], + "nodeType": "host", + "view": "map", + "customOptions": [], + "customMetrics": [], + "boundsOverride": { + "max": 1, + "min": 0 + }, + "autoBounds": true, + "accountId": "", + "region": "", + "autoReload": false, + "filterQuery": { + "expression": "", + "kind": "kuery" + }, + "legend": { + "palette": "cool", + "reverseColors": false, + "steps": 10 + }, + "timelineOpen": false + } + } +} +``` + +```json +PUT /api/infra/metrics_explorer_views/random-id + +Status code: 404 + +{ + "statusCode": 404, + "error": "Not Found", + "message": "Saved object [metrics-explorer-view/random-id] not found" +} +``` + +Send in the payload a `name` attribute already held by another view: +```json +PUT /api/infra/metrics_explorer_views/927ad6a0-da0c-11ed-9487-41e9b90f96b9 + +Status code: 409 + +{ + "statusCode": 409, + "error": "Conflict", + "message": "A view with that name already exists." +} +``` + +## Delete one: `DELETE /api/infra/metrics_explorer_views/{metricsExplorerViewId}` + +Deletes a metrics explorer view. + +Any attempt to delete the static view with id `0` will return a `400 The metrics explorer view with id 0 is not configurable.` + +### Request + +- **Method**: DELETE +- **Path**: /api/infra/metrics_explorer_views/{metricsExplorerViewId} + +### Response + +```json +DELETE /api/infra/metrics_explorer_views/927ad6a0-da0c-11ed-9487-41e9b90f96b9 + +Status code: 204 No content +``` + +```json +DELETE /api/infra/metrics_explorer_views/random-id + +Status code: 404 + +{ + "statusCode": 404, + "error": "Not Found", + "message": "Saved object [metrics-explorer-view/random-id] not found" +} +``` diff --git a/x-pack/plugins/infra/server/routes/metrics_explorer_views/create_metrics_explorer_view.ts b/x-pack/plugins/infra/server/routes/metrics_explorer_views/create_metrics_explorer_view.ts new file mode 100644 index 0000000000000..948dd757e7e01 --- /dev/null +++ b/x-pack/plugins/infra/server/routes/metrics_explorer_views/create_metrics_explorer_view.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 { isBoom } from '@hapi/boom'; +import { createValidationFunction } from '../../../common/runtime_types'; +import { + createMetricsExplorerViewRequestPayloadRT, + metricsExplorerViewResponsePayloadRT, + METRICS_EXPLORER_VIEW_URL, +} from '../../../common/http_api/latest'; +import type { InfraBackendLibs } from '../../lib/infra_types'; + +export const initCreateMetricsExplorerViewRoute = ({ + framework, + getStartServices, +}: Pick) => { + framework.registerRoute( + { + method: 'post', + path: METRICS_EXPLORER_VIEW_URL, + validate: { + body: createValidationFunction(createMetricsExplorerViewRequestPayloadRT), + }, + }, + async (_requestContext, request, response) => { + const { body } = request; + const [, , { metricsExplorerViews }] = await getStartServices(); + const metricsExplorerViewsClient = metricsExplorerViews.getScopedClient(request); + + try { + const metricsExplorerView = await metricsExplorerViewsClient.create(body.attributes); + + return response.custom({ + statusCode: 201, + body: metricsExplorerViewResponsePayloadRT.encode({ data: metricsExplorerView }), + }); + } catch (error) { + if (isBoom(error)) { + return response.customError({ + statusCode: error.output.statusCode, + body: { message: error.output.payload.message }, + }); + } + + return response.customError({ + statusCode: error.statusCode ?? 500, + body: { + message: error.message ?? 'An unexpected error occurred', + }, + }); + } + } + ); +}; diff --git a/x-pack/plugins/infra/server/routes/metrics_explorer_views/delete_metrics_explorer_view.ts b/x-pack/plugins/infra/server/routes/metrics_explorer_views/delete_metrics_explorer_view.ts new file mode 100644 index 0000000000000..a3b6f8b05f099 --- /dev/null +++ b/x-pack/plugins/infra/server/routes/metrics_explorer_views/delete_metrics_explorer_view.ts @@ -0,0 +1,54 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { isBoom } from '@hapi/boom'; +import { createValidationFunction } from '../../../common/runtime_types'; +import { + metricsExplorerViewRequestParamsRT, + METRICS_EXPLORER_VIEW_URL_ENTITY, +} from '../../../common/http_api/latest'; +import type { InfraBackendLibs } from '../../lib/infra_types'; + +export const initDeleteMetricsExplorerViewRoute = ({ + framework, + getStartServices, +}: Pick) => { + framework.registerRoute( + { + method: 'delete', + path: METRICS_EXPLORER_VIEW_URL_ENTITY, + validate: { + params: createValidationFunction(metricsExplorerViewRequestParamsRT), + }, + }, + async (_requestContext, request, response) => { + const { params } = request; + const [, , { metricsExplorerViews }] = await getStartServices(); + const metricsExplorerViewsClient = metricsExplorerViews.getScopedClient(request); + + try { + await metricsExplorerViewsClient.delete(params.metricsExplorerViewId); + + return response.noContent(); + } catch (error) { + if (isBoom(error)) { + return response.customError({ + statusCode: error.output.statusCode, + body: { message: error.output.payload.message }, + }); + } + + return response.customError({ + statusCode: error.statusCode ?? 500, + body: { + message: error.message ?? 'An unexpected error occurred', + }, + }); + } + } + ); +}; diff --git a/x-pack/plugins/infra/server/routes/metrics_explorer_views/find_metrics_explorer_view.ts b/x-pack/plugins/infra/server/routes/metrics_explorer_views/find_metrics_explorer_view.ts new file mode 100644 index 0000000000000..fbae7790b04eb --- /dev/null +++ b/x-pack/plugins/infra/server/routes/metrics_explorer_views/find_metrics_explorer_view.ts @@ -0,0 +1,49 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { createValidationFunction } from '../../../common/runtime_types'; +import { + findMetricsExplorerViewResponsePayloadRT, + metricsExplorerViewRequestQueryRT, + METRICS_EXPLORER_VIEW_URL, +} from '../../../common/http_api/latest'; +import type { InfraBackendLibs } from '../../lib/infra_types'; + +export const initFindMetricsExplorerViewRoute = ({ + framework, + getStartServices, +}: Pick) => { + framework.registerRoute( + { + method: 'get', + path: METRICS_EXPLORER_VIEW_URL, + validate: { + query: createValidationFunction(metricsExplorerViewRequestQueryRT), + }, + }, + async (_requestContext, request, response) => { + const { query } = request; + const [, , { metricsExplorerViews }] = await getStartServices(); + const metricsExplorerViewsClient = metricsExplorerViews.getScopedClient(request); + + try { + const metricsExplorerViewsList = await metricsExplorerViewsClient.find(query); + + return response.ok({ + body: findMetricsExplorerViewResponsePayloadRT.encode({ data: metricsExplorerViewsList }), + }); + } catch (error) { + return response.customError({ + statusCode: error.statusCode ?? 500, + body: { + message: error.message ?? 'An unexpected error occurred', + }, + }); + } + } + ); +}; diff --git a/x-pack/plugins/infra/server/routes/metrics_explorer_views/get_metrics_explorer_view.ts b/x-pack/plugins/infra/server/routes/metrics_explorer_views/get_metrics_explorer_view.ts new file mode 100644 index 0000000000000..b8e71a3c662d6 --- /dev/null +++ b/x-pack/plugins/infra/server/routes/metrics_explorer_views/get_metrics_explorer_view.ts @@ -0,0 +1,62 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { isBoom } from '@hapi/boom'; +import { createValidationFunction } from '../../../common/runtime_types'; +import { + metricsExplorerViewResponsePayloadRT, + metricsExplorerViewRequestQueryRT, + METRICS_EXPLORER_VIEW_URL_ENTITY, + getMetricsExplorerViewRequestParamsRT, +} from '../../../common/http_api/latest'; +import type { InfraBackendLibs } from '../../lib/infra_types'; + +export const initGetMetricsExplorerViewRoute = ({ + framework, + getStartServices, +}: Pick) => { + framework.registerRoute( + { + method: 'get', + path: METRICS_EXPLORER_VIEW_URL_ENTITY, + validate: { + params: createValidationFunction(getMetricsExplorerViewRequestParamsRT), + query: createValidationFunction(metricsExplorerViewRequestQueryRT), + }, + }, + async (_requestContext, request, response) => { + const { params, query } = request; + const [, , { metricsExplorerViews }] = await getStartServices(); + const metricsExplorerViewsClient = metricsExplorerViews.getScopedClient(request); + + try { + const metricsExplorerView = await metricsExplorerViewsClient.get( + params.metricsExplorerViewId, + query + ); + + return response.ok({ + body: metricsExplorerViewResponsePayloadRT.encode({ data: metricsExplorerView }), + }); + } catch (error) { + if (isBoom(error)) { + return response.customError({ + statusCode: error.output.statusCode, + body: { message: error.output.payload.message }, + }); + } + + return response.customError({ + statusCode: error.statusCode ?? 500, + body: { + message: error.message ?? 'An unexpected error occurred', + }, + }); + } + } + ); +}; diff --git a/x-pack/plugins/infra/server/routes/metrics_explorer_views/index.ts b/x-pack/plugins/infra/server/routes/metrics_explorer_views/index.ts new file mode 100644 index 0000000000000..e4a6165374422 --- /dev/null +++ b/x-pack/plugins/infra/server/routes/metrics_explorer_views/index.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 { InfraBackendLibs } from '../../lib/infra_types'; +import { initCreateMetricsExplorerViewRoute } from './create_metrics_explorer_view'; +import { initDeleteMetricsExplorerViewRoute } from './delete_metrics_explorer_view'; +import { initFindMetricsExplorerViewRoute } from './find_metrics_explorer_view'; +import { initGetMetricsExplorerViewRoute } from './get_metrics_explorer_view'; +import { initUpdateMetricsExplorerViewRoute } from './update_metrics_explorer_view'; + +export const initMetricsExplorerViewRoutes = ( + dependencies: Pick +) => { + initCreateMetricsExplorerViewRoute(dependencies); + initDeleteMetricsExplorerViewRoute(dependencies); + initFindMetricsExplorerViewRoute(dependencies); + initGetMetricsExplorerViewRoute(dependencies); + initUpdateMetricsExplorerViewRoute(dependencies); +}; diff --git a/x-pack/plugins/infra/server/routes/metrics_explorer_views/update_metrics_explorer_view.ts b/x-pack/plugins/infra/server/routes/metrics_explorer_views/update_metrics_explorer_view.ts new file mode 100644 index 0000000000000..ebd8caef8e030 --- /dev/null +++ b/x-pack/plugins/infra/server/routes/metrics_explorer_views/update_metrics_explorer_view.ts @@ -0,0 +1,65 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { isBoom } from '@hapi/boom'; +import { createValidationFunction } from '../../../common/runtime_types'; +import { + metricsExplorerViewRequestParamsRT, + metricsExplorerViewRequestQueryRT, + metricsExplorerViewResponsePayloadRT, + METRICS_EXPLORER_VIEW_URL_ENTITY, + updateMetricsExplorerViewRequestPayloadRT, +} from '../../../common/http_api/latest'; +import type { InfraBackendLibs } from '../../lib/infra_types'; + +export const initUpdateMetricsExplorerViewRoute = ({ + framework, + getStartServices, +}: Pick) => { + framework.registerRoute( + { + method: 'put', + path: METRICS_EXPLORER_VIEW_URL_ENTITY, + validate: { + params: createValidationFunction(metricsExplorerViewRequestParamsRT), + query: createValidationFunction(metricsExplorerViewRequestQueryRT), + body: createValidationFunction(updateMetricsExplorerViewRequestPayloadRT), + }, + }, + async (_requestContext, request, response) => { + const { body, params, query } = request; + const [, , { metricsExplorerViews }] = await getStartServices(); + const metricsExplorerViewsClient = metricsExplorerViews.getScopedClient(request); + + try { + const metricsExplorerView = await metricsExplorerViewsClient.update( + params.metricsExplorerViewId, + body.attributes, + query + ); + + return response.ok({ + body: metricsExplorerViewResponsePayloadRT.encode({ data: metricsExplorerView }), + }); + } catch (error) { + if (isBoom(error)) { + return response.customError({ + statusCode: error.output.statusCode, + body: { message: error.output.payload.message }, + }); + } + + return response.customError({ + statusCode: error.statusCode ?? 500, + body: { + message: error.message ?? 'An unexpected error occurred', + }, + }); + } + } + ); +}; diff --git a/x-pack/plugins/infra/server/saved_objects/metrics_explorer_view/types.ts b/x-pack/plugins/infra/server/saved_objects/metrics_explorer_view/types.ts index 1168b2003994e..15fe0eb970cc2 100644 --- a/x-pack/plugins/infra/server/saved_objects/metrics_explorer_view/types.ts +++ b/x-pack/plugins/infra/server/saved_objects/metrics_explorer_view/types.ts @@ -5,14 +5,20 @@ * 2.0. */ -import { isoToEpochRt } from '@kbn/io-ts-utils'; +import { isoToEpochRt, nonEmptyStringRt } from '@kbn/io-ts-utils'; import * as rt from 'io-ts'; -import { metricsExplorerViewAttributesRT } from '../../../common/metrics_explorer_views'; + +export const metricsExplorerViewSavedObjectAttributesRT = rt.intersection([ + rt.strict({ + name: nonEmptyStringRt, + }), + rt.UnknownRecord, +]); export const metricsExplorerViewSavedObjectRT = rt.intersection([ rt.type({ id: rt.string, - attributes: metricsExplorerViewAttributesRT, + attributes: metricsExplorerViewSavedObjectAttributesRT, }), rt.partial({ version: rt.string, diff --git a/x-pack/plugins/infra/server/services/inventory_views/inventory_views_client.ts b/x-pack/plugins/infra/server/services/inventory_views/inventory_views_client.ts index 55a8df1024a6e..c32da344354b6 100644 --- a/x-pack/plugins/infra/server/services/inventory_views/inventory_views_client.ts +++ b/x-pack/plugins/infra/server/services/inventory_views/inventory_views_client.ts @@ -35,18 +35,16 @@ export class InventoryViewsClient implements IInventoryViewsClient { ) {} static STATIC_VIEW_ID = '0'; + static DEFAULT_SOURCE_ID = 'default'; public async find(query: InventoryViewRequestQuery): Promise { this.logger.debug('Trying to load inventory views ...'); - const sourceId = query.sourceId ?? 'default'; + const sourceId = query.sourceId ?? InventoryViewsClient.DEFAULT_SOURCE_ID; const [sourceConfiguration, inventoryViewSavedObject] = await Promise.all([ this.infraSources.getSourceConfiguration(this.savedObjectsClient, sourceId), - this.savedObjectsClient.find({ - type: inventoryViewSavedObjectName, - perPage: 1000, // Fetch 1 page by default with a max of 1000 results - }), + this.getAllViews(), ]); const defaultView = InventoryViewsClient.createStaticView( @@ -72,7 +70,7 @@ export class InventoryViewsClient implements IInventoryViewsClient { ): Promise { this.logger.debug(`Trying to load inventory view with id ${inventoryViewId} ...`); - const sourceId = query.sourceId ?? 'default'; + const sourceId = query.sourceId ?? InventoryViewsClient.DEFAULT_SOURCE_ID; // Handle the case where the requested resource is the static inventory view if (inventoryViewId === InventoryViewsClient.STATIC_VIEW_ID) { @@ -123,7 +121,7 @@ export class InventoryViewsClient implements IInventoryViewsClient { // Validate there is not a view with the same name await this.assertNameConflict(attributes.name, [inventoryViewId]); - const sourceId = query.sourceId ?? 'default'; + const sourceId = query.sourceId ?? InventoryViewsClient.DEFAULT_SOURCE_ID; const [sourceConfiguration, inventoryViewSavedObject] = await Promise.all([ this.infraSources.getSourceConfiguration(this.savedObjectsClient, sourceId), @@ -160,6 +158,13 @@ export class InventoryViewsClient implements IInventoryViewsClient { }; } + private getAllViews() { + return this.savedObjectsClient.find({ + type: inventoryViewSavedObjectName, + perPage: 1000, // Fetch 1 page by default with a max of 1000 results + }); + } + private moveDefaultViewOnTop(views: InventoryView[]) { const defaultViewPosition = views.findIndex((view) => view.attributes.isDefault); @@ -175,10 +180,7 @@ export class InventoryViewsClient implements IInventoryViewsClient { * We want to control conflicting names on the views */ private async assertNameConflict(name: string, whitelist: string[] = []) { - const results = await this.savedObjectsClient.find({ - type: inventoryViewSavedObjectName, - perPage: 1000, - }); + const results = await this.getAllViews(); const hasConflict = [InventoryViewsClient.createStaticView(), ...results.saved_objects].some( (obj) => !whitelist.includes(obj.id) && obj.attributes.name === name diff --git a/x-pack/plugins/infra/server/services/metrics_explorer_views/index.ts b/x-pack/plugins/infra/server/services/metrics_explorer_views/index.ts new file mode 100644 index 0000000000000..3cd3efd6c0f67 --- /dev/null +++ b/x-pack/plugins/infra/server/services/metrics_explorer_views/index.ts @@ -0,0 +1,14 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export { MetricsExplorerViewsService } from './metrics_explorer_views_service'; +export { MetricsExplorerViewsClient } from './metrics_explorer_views_client'; +export type { + MetricsExplorerViewsServiceSetup, + MetricsExplorerViewsServiceStart, + MetricsExplorerViewsServiceStartDeps, +} from './types'; diff --git a/x-pack/plugins/infra/server/services/metrics_explorer_views/metrics_explorer_views_client.mock.ts b/x-pack/plugins/infra/server/services/metrics_explorer_views/metrics_explorer_views_client.mock.ts new file mode 100644 index 0000000000000..82a8cba3f6427 --- /dev/null +++ b/x-pack/plugins/infra/server/services/metrics_explorer_views/metrics_explorer_views_client.mock.ts @@ -0,0 +1,17 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { IMetricsExplorerViewsClient } from './types'; + +export const createMetricsExplorerViewsClientMock = + (): jest.Mocked => ({ + delete: jest.fn(), + find: jest.fn(), + get: jest.fn(), + create: jest.fn(), + update: jest.fn(), + }); diff --git a/x-pack/plugins/infra/server/services/metrics_explorer_views/metrics_explorer_views_client.test.ts b/x-pack/plugins/infra/server/services/metrics_explorer_views/metrics_explorer_views_client.test.ts new file mode 100644 index 0000000000000..c903e9af360f8 --- /dev/null +++ b/x-pack/plugins/infra/server/services/metrics_explorer_views/metrics_explorer_views_client.test.ts @@ -0,0 +1,262 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license 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 { SavedObjectsClientContract } from '@kbn/core/server'; +import { savedObjectsClientMock } from '@kbn/core/server/mocks'; +import { MetricsExplorerViewAttributes } from '../../../common/metrics_explorer_views'; + +import { InfraSource } from '../../lib/sources'; +import { createInfraSourcesMock } from '../../lib/sources/mocks'; +import { metricsExplorerViewSavedObjectName } from '../../saved_objects/metrics_explorer_view'; +import { MetricsExplorerViewsClient } from './metrics_explorer_views_client'; +import { createMetricsExplorerViewMock } from '../../../common/metrics_explorer_views/metrics_explorer_view.mock'; +import { + CreateMetricsExplorerViewAttributesRequestPayload, + UpdateMetricsExplorerViewAttributesRequestPayload, +} from '../../../common/http_api/latest'; + +describe('MetricsExplorerViewsClient class', () => { + const mockFindMetricsExplorerList = ( + savedObjectsClient: jest.Mocked + ) => { + const metricsExplorerViewListMock = [ + createMetricsExplorerViewMock('0', { + isDefault: true, + } as MetricsExplorerViewAttributes), + createMetricsExplorerViewMock('default_id', { + name: 'Default view 2', + isStatic: false, + } as MetricsExplorerViewAttributes), + createMetricsExplorerViewMock('custom_id', { + name: 'Custom', + isStatic: false, + } as MetricsExplorerViewAttributes), + ]; + + savedObjectsClient.find.mockResolvedValue({ + total: 2, + saved_objects: metricsExplorerViewListMock.slice(1).map((view) => ({ + ...view, + type: metricsExplorerViewSavedObjectName, + score: 0, + references: [], + })), + per_page: 1000, + page: 1, + }); + + return metricsExplorerViewListMock; + }; + + describe('.find', () => { + it('resolves the list of existing metrics explorer views', async () => { + const { metricsExplorerViewsClient, infraSources, savedObjectsClient } = + createMetricsExplorerViewsClient(); + + infraSources.getSourceConfiguration.mockResolvedValue(basicTestSourceConfiguration); + + const metricsExplorerViewListMock = mockFindMetricsExplorerList(savedObjectsClient); + + const metricsExplorerViewList = await metricsExplorerViewsClient.find({}); + + expect(savedObjectsClient.find).toHaveBeenCalled(); + expect(metricsExplorerViewList).toEqual(metricsExplorerViewListMock); + }); + + it('always resolves at least the static metrics explorer view', async () => { + const { metricsExplorerViewsClient, infraSources, savedObjectsClient } = + createMetricsExplorerViewsClient(); + + const metricsExplorerViewListMock = [ + createMetricsExplorerViewMock('0', { + isDefault: true, + } as MetricsExplorerViewAttributes), + ]; + + infraSources.getSourceConfiguration.mockResolvedValue(basicTestSourceConfiguration); + + savedObjectsClient.find.mockResolvedValue({ + total: 2, + saved_objects: [], + per_page: 1000, + page: 1, + }); + + const metricsExplorerViewList = await metricsExplorerViewsClient.find({}); + + expect(savedObjectsClient.find).toHaveBeenCalled(); + expect(metricsExplorerViewList).toEqual(metricsExplorerViewListMock); + }); + }); + + it('.get resolves the an metrics explorer view by id', async () => { + const { metricsExplorerViewsClient, infraSources, savedObjectsClient } = + createMetricsExplorerViewsClient(); + + const metricsExplorerViewMock = createMetricsExplorerViewMock('custom_id', { + name: 'Custom', + isDefault: false, + isStatic: false, + } as MetricsExplorerViewAttributes); + + infraSources.getSourceConfiguration.mockResolvedValue(basicTestSourceConfiguration); + + savedObjectsClient.get.mockResolvedValue({ + ...metricsExplorerViewMock, + type: metricsExplorerViewSavedObjectName, + references: [], + }); + + const metricsExplorerView = await metricsExplorerViewsClient.get('custom_id', {}); + + expect(savedObjectsClient.get).toHaveBeenCalled(); + expect(metricsExplorerView).toEqual(metricsExplorerViewMock); + }); + + describe('.create', () => { + it('generate a new metrics explorer view', async () => { + const { metricsExplorerViewsClient, savedObjectsClient } = createMetricsExplorerViewsClient(); + + const metricsExplorerViewMock = createMetricsExplorerViewMock('new_id', { + name: 'New view', + isStatic: false, + } as MetricsExplorerViewAttributes); + + mockFindMetricsExplorerList(savedObjectsClient); + + savedObjectsClient.create.mockResolvedValue({ + ...metricsExplorerViewMock, + type: metricsExplorerViewSavedObjectName, + references: [], + }); + + const metricsExplorerView = await metricsExplorerViewsClient.create({ + name: 'New view', + } as CreateMetricsExplorerViewAttributesRequestPayload); + + expect(savedObjectsClient.create).toHaveBeenCalled(); + expect(metricsExplorerView).toEqual(metricsExplorerViewMock); + }); + + it('throws an error when a conflicting name is given', async () => { + const { metricsExplorerViewsClient, savedObjectsClient } = createMetricsExplorerViewsClient(); + + mockFindMetricsExplorerList(savedObjectsClient); + + await expect( + async () => + await metricsExplorerViewsClient.create({ + name: 'Custom', + } as CreateMetricsExplorerViewAttributesRequestPayload) + ).rejects.toThrow('A view with that name already exists.'); + }); + }); + + describe('.update', () => { + it('update an existing metrics explorer view by id', async () => { + const { metricsExplorerViewsClient, infraSources, savedObjectsClient } = + createMetricsExplorerViewsClient(); + + const metricsExplorerViews = mockFindMetricsExplorerList(savedObjectsClient); + + const metricsExplorerViewMock = { + ...metricsExplorerViews[1], + attributes: { + ...metricsExplorerViews[1].attributes, + name: 'New name', + }, + }; + + infraSources.getSourceConfiguration.mockResolvedValue(basicTestSourceConfiguration); + + savedObjectsClient.update.mockResolvedValue({ + ...metricsExplorerViewMock, + type: metricsExplorerViewSavedObjectName, + references: [], + }); + + const metricsExplorerView = await metricsExplorerViewsClient.update( + 'default_id', + { + name: 'New name', + } as UpdateMetricsExplorerViewAttributesRequestPayload, + {} + ); + + expect(savedObjectsClient.update).toHaveBeenCalled(); + expect(metricsExplorerView).toEqual(metricsExplorerViewMock); + }); + + it('throws an error when a conflicting name is given', async () => { + const { metricsExplorerViewsClient, savedObjectsClient } = createMetricsExplorerViewsClient(); + + mockFindMetricsExplorerList(savedObjectsClient); + + await expect( + async () => + await metricsExplorerViewsClient.update( + 'default_id', + { + name: 'Custom', + } as UpdateMetricsExplorerViewAttributesRequestPayload, + {} + ) + ).rejects.toThrow('A view with that name already exists.'); + }); + }); + + it('.delete removes an metrics explorer view by id', async () => { + const { metricsExplorerViewsClient, savedObjectsClient } = createMetricsExplorerViewsClient(); + + savedObjectsClient.delete.mockResolvedValue({}); + + const metricsExplorerView = await metricsExplorerViewsClient.delete('custom_id'); + + expect(savedObjectsClient.delete).toHaveBeenCalled(); + expect(metricsExplorerView).toEqual({}); + }); +}); + +const createMetricsExplorerViewsClient = () => { + const logger = loggerMock.create(); + const savedObjectsClient = savedObjectsClientMock.create(); + const infraSources = createInfraSourcesMock(); + + const metricsExplorerViewsClient = new MetricsExplorerViewsClient( + logger, + savedObjectsClient, + infraSources + ); + + return { + infraSources, + metricsExplorerViewsClient, + savedObjectsClient, + }; +}; + +const basicTestSourceConfiguration: InfraSource = { + id: 'ID', + origin: 'stored', + configuration: { + name: 'NAME', + description: 'DESCRIPTION', + logIndices: { + type: 'index_pattern', + indexPatternId: 'INDEX_PATTERN_ID', + }, + logColumns: [], + fields: { + message: [], + }, + metricAlias: 'METRIC_ALIAS', + inventoryDefaultView: '0', + metricsExplorerDefaultView: '0', + anomalyThreshold: 0, + }, +}; diff --git a/x-pack/plugins/infra/server/services/metrics_explorer_views/metrics_explorer_views_client.ts b/x-pack/plugins/infra/server/services/metrics_explorer_views/metrics_explorer_views_client.ts new file mode 100644 index 0000000000000..1ba34456d88a8 --- /dev/null +++ b/x-pack/plugins/infra/server/services/metrics_explorer_views/metrics_explorer_views_client.ts @@ -0,0 +1,218 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { + Logger, + SavedObject, + SavedObjectsClientContract, + SavedObjectsUpdateResponse, +} from '@kbn/core/server'; +import Boom from '@hapi/boom'; +import { + staticMetricsExplorerViewAttributes, + staticMetricsExplorerViewId, +} from '../../../common/metrics_explorer_views'; +import type { + CreateMetricsExplorerViewAttributesRequestPayload, + MetricsExplorerViewRequestQuery, +} from '../../../common/http_api/latest'; +import type { + MetricsExplorerView, + MetricsExplorerViewAttributes, +} from '../../../common/metrics_explorer_views'; +import { decodeOrThrow } from '../../../common/runtime_types'; +import type { IInfraSources } from '../../lib/sources'; +import { metricsExplorerViewSavedObjectName } from '../../saved_objects/metrics_explorer_view'; +import { metricsExplorerViewSavedObjectRT } from '../../saved_objects/metrics_explorer_view/types'; +import type { IMetricsExplorerViewsClient } from './types'; + +export class MetricsExplorerViewsClient implements IMetricsExplorerViewsClient { + constructor( + private readonly logger: Logger, + private readonly savedObjectsClient: SavedObjectsClientContract, + private readonly infraSources: IInfraSources + ) {} + + static STATIC_VIEW_ID = '0'; + static DEFAULT_SOURCE_ID = 'default'; + + public async find(query: MetricsExplorerViewRequestQuery): Promise { + this.logger.debug('Trying to load metrics explorer views ...'); + + const sourceId = query.sourceId ?? MetricsExplorerViewsClient.DEFAULT_SOURCE_ID; + + const [sourceConfiguration, metricsExplorerViewSavedObject] = await Promise.all([ + this.infraSources.getSourceConfiguration(this.savedObjectsClient, sourceId), + this.getAllViews(), + ]); + + const defaultView = MetricsExplorerViewsClient.createStaticView( + sourceConfiguration.configuration.metricsExplorerDefaultView + ); + const views = metricsExplorerViewSavedObject.saved_objects.map((savedObject) => + this.mapSavedObjectToMetricsExplorerView( + savedObject, + sourceConfiguration.configuration.metricsExplorerDefaultView + ) + ); + + const metricsExplorerViews = [defaultView, ...views]; + + const sortedMetricsExplorerViews = this.moveDefaultViewOnTop(metricsExplorerViews); + + return sortedMetricsExplorerViews; + } + + public async get( + metricsExplorerViewId: string, + query: MetricsExplorerViewRequestQuery + ): Promise { + this.logger.debug(`Trying to load metrics explorer view with id ${metricsExplorerViewId} ...`); + + const sourceId = query.sourceId ?? MetricsExplorerViewsClient.DEFAULT_SOURCE_ID; + + // Handle the case where the requested resource is the static metrics explorer view + if (metricsExplorerViewId === MetricsExplorerViewsClient.STATIC_VIEW_ID) { + const sourceConfiguration = await this.infraSources.getSourceConfiguration( + this.savedObjectsClient, + sourceId + ); + + return MetricsExplorerViewsClient.createStaticView( + sourceConfiguration.configuration.metricsExplorerDefaultView + ); + } + + const [sourceConfiguration, metricsExplorerViewSavedObject] = await Promise.all([ + this.infraSources.getSourceConfiguration(this.savedObjectsClient, sourceId), + this.savedObjectsClient.get(metricsExplorerViewSavedObjectName, metricsExplorerViewId), + ]); + + return this.mapSavedObjectToMetricsExplorerView( + metricsExplorerViewSavedObject, + sourceConfiguration.configuration.metricsExplorerDefaultView + ); + } + + public async create( + attributes: CreateMetricsExplorerViewAttributesRequestPayload + ): Promise { + this.logger.debug(`Trying to create metrics explorer view ...`); + + // Validate there is not a view with the same name + await this.assertNameConflict(attributes.name); + + const metricsExplorerViewSavedObject = await this.savedObjectsClient.create( + metricsExplorerViewSavedObjectName, + attributes + ); + + return this.mapSavedObjectToMetricsExplorerView(metricsExplorerViewSavedObject); + } + + public async update( + metricsExplorerViewId: string, + attributes: CreateMetricsExplorerViewAttributesRequestPayload, + query: MetricsExplorerViewRequestQuery + ): Promise { + this.logger.debug( + `Trying to update metrics explorer view with id "${metricsExplorerViewId}"...` + ); + + // Validate there is not a view with the same name + await this.assertNameConflict(attributes.name, [metricsExplorerViewId]); + + const sourceId = query.sourceId ?? MetricsExplorerViewsClient.DEFAULT_SOURCE_ID; + + const [sourceConfiguration, metricsExplorerViewSavedObject] = await Promise.all([ + this.infraSources.getSourceConfiguration(this.savedObjectsClient, sourceId), + this.savedObjectsClient.update( + metricsExplorerViewSavedObjectName, + metricsExplorerViewId, + attributes + ), + ]); + + return this.mapSavedObjectToMetricsExplorerView( + metricsExplorerViewSavedObject, + sourceConfiguration.configuration.metricsExplorerDefaultView + ); + } + + public delete(metricsExplorerViewId: string): Promise<{}> { + this.logger.debug( + `Trying to delete metrics explorer view with id ${metricsExplorerViewId} ...` + ); + + return this.savedObjectsClient.delete( + metricsExplorerViewSavedObjectName, + metricsExplorerViewId + ); + } + + private getAllViews() { + return this.savedObjectsClient.find({ + type: metricsExplorerViewSavedObjectName, + perPage: 1000, // Fetch 1 page by default with a max of 1000 results + }); + } + + private mapSavedObjectToMetricsExplorerView( + savedObject: SavedObject | SavedObjectsUpdateResponse, + defaultViewId?: string + ) { + const metricsExplorerViewSavedObject = decodeOrThrow(metricsExplorerViewSavedObjectRT)( + savedObject + ); + + return { + id: metricsExplorerViewSavedObject.id, + version: metricsExplorerViewSavedObject.version, + updatedAt: metricsExplorerViewSavedObject.updated_at, + attributes: { + ...metricsExplorerViewSavedObject.attributes, + isDefault: metricsExplorerViewSavedObject.id === defaultViewId, + isStatic: false, + }, + }; + } + + private moveDefaultViewOnTop(views: MetricsExplorerView[]) { + const defaultViewPosition = views.findIndex((view) => view.attributes.isDefault); + + if (defaultViewPosition !== -1) { + const element = views.splice(defaultViewPosition, 1)[0]; + views.unshift(element); + } + + return views; + } + + /** + * We want to control conflicting names on the views + */ + private async assertNameConflict(name: string, whitelist: string[] = []) { + const results = await this.getAllViews(); + + const hasConflict = [ + MetricsExplorerViewsClient.createStaticView(), + ...results.saved_objects, + ].some((obj) => !whitelist.includes(obj.id) && obj.attributes.name === name); + + if (hasConflict) { + throw Boom.conflict('A view with that name already exists.'); + } + } + + private static createStaticView = (defaultViewId?: string): MetricsExplorerView => ({ + id: staticMetricsExplorerViewId, + attributes: { + ...staticMetricsExplorerViewAttributes, + isDefault: defaultViewId === MetricsExplorerViewsClient.STATIC_VIEW_ID, + }, + }); +} diff --git a/x-pack/plugins/infra/server/services/metrics_explorer_views/metrics_explorer_views_service.mock.ts b/x-pack/plugins/infra/server/services/metrics_explorer_views/metrics_explorer_views_service.mock.ts new file mode 100644 index 0000000000000..3739930944571 --- /dev/null +++ b/x-pack/plugins/infra/server/services/metrics_explorer_views/metrics_explorer_views_service.mock.ts @@ -0,0 +1,18 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { createMetricsExplorerViewsClientMock } from './metrics_explorer_views_client.mock'; +import type { MetricsExplorerViewsServiceSetup, MetricsExplorerViewsServiceStart } from './types'; + +export const createMetricsExplorerViewsServiceSetupMock = + (): jest.Mocked => {}; + +export const createMetricsExplorerViewsServiceStartMock = + (): jest.Mocked => ({ + getClient: jest.fn((_savedObjectsClient: any) => createMetricsExplorerViewsClientMock()), + getScopedClient: jest.fn((_request: any) => createMetricsExplorerViewsClientMock()), + }); diff --git a/x-pack/plugins/infra/server/services/metrics_explorer_views/metrics_explorer_views_service.ts b/x-pack/plugins/infra/server/services/metrics_explorer_views/metrics_explorer_views_service.ts new file mode 100644 index 0000000000000..38c7ab4e1f925 --- /dev/null +++ b/x-pack/plugins/infra/server/services/metrics_explorer_views/metrics_explorer_views_service.ts @@ -0,0 +1,39 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { KibanaRequest, Logger, SavedObjectsClientContract } from '@kbn/core/server'; +import { MetricsExplorerViewsClient } from './metrics_explorer_views_client'; +import type { + MetricsExplorerViewsServiceSetup, + MetricsExplorerViewsServiceStart, + MetricsExplorerViewsServiceStartDeps, +} from './types'; + +export class MetricsExplorerViewsService { + constructor(private readonly logger: Logger) {} + + public setup(): MetricsExplorerViewsServiceSetup {} + + public start({ + infraSources, + savedObjects, + }: MetricsExplorerViewsServiceStartDeps): MetricsExplorerViewsServiceStart { + const { logger } = this; + + return { + getClient(savedObjectsClient: SavedObjectsClientContract) { + return new MetricsExplorerViewsClient(logger, savedObjectsClient, infraSources); + }, + + getScopedClient(request: KibanaRequest) { + const savedObjectsClient = savedObjects.getScopedClient(request); + + return this.getClient(savedObjectsClient); + }, + }; + } +} diff --git a/x-pack/plugins/infra/server/services/metrics_explorer_views/types.ts b/x-pack/plugins/infra/server/services/metrics_explorer_views/types.ts new file mode 100644 index 0000000000000..0e64aaa83d27e --- /dev/null +++ b/x-pack/plugins/infra/server/services/metrics_explorer_views/types.ts @@ -0,0 +1,48 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { + KibanaRequest, + SavedObjectsClientContract, + SavedObjectsServiceStart, +} from '@kbn/core/server'; +import type { + CreateMetricsExplorerViewAttributesRequestPayload, + MetricsExplorerViewRequestQuery, + UpdateMetricsExplorerViewAttributesRequestPayload, +} from '../../../common/http_api/latest'; +import type { MetricsExplorerView } from '../../../common/metrics_explorer_views'; +import type { InfraSources } from '../../lib/sources'; + +export interface MetricsExplorerViewsServiceStartDeps { + infraSources: InfraSources; + savedObjects: SavedObjectsServiceStart; +} + +export type MetricsExplorerViewsServiceSetup = void; + +export interface MetricsExplorerViewsServiceStart { + getClient(savedObjectsClient: SavedObjectsClientContract): IMetricsExplorerViewsClient; + getScopedClient(request: KibanaRequest): IMetricsExplorerViewsClient; +} + +export interface IMetricsExplorerViewsClient { + delete(metricsExplorerViewId: string): Promise<{}>; + find(query: MetricsExplorerViewRequestQuery): Promise; + get( + metricsExplorerViewId: string, + query: MetricsExplorerViewRequestQuery + ): Promise; + create( + metricsExplorerViewAttributes: CreateMetricsExplorerViewAttributesRequestPayload + ): Promise; + update( + metricsExplorerViewId: string, + metricsExplorerViewAttributes: UpdateMetricsExplorerViewAttributesRequestPayload, + query: MetricsExplorerViewRequestQuery + ): Promise; +} diff --git a/x-pack/plugins/infra/server/types.ts b/x-pack/plugins/infra/server/types.ts index c415103d2256d..49dbca9b276b2 100644 --- a/x-pack/plugins/infra/server/types.ts +++ b/x-pack/plugins/infra/server/types.ts @@ -16,6 +16,7 @@ import type { InfraStaticSourceConfiguration } from '../common/source_configurat import { InfraServerPluginStartDeps } from './lib/adapters/framework'; import { InventoryViewsServiceStart } from './services/inventory_views'; import { LogViewsServiceSetup, LogViewsServiceStart } from './services/log_views/types'; +import { MetricsExplorerViewsServiceStart } from './services/metrics_explorer_views'; export type { InfraConfig } from '../common/plugin_config_types'; @@ -33,6 +34,7 @@ export interface InfraPluginSetup { export interface InfraPluginStart { inventoryViews: InventoryViewsServiceStart; logViews: LogViewsServiceStart; + metricsExplorerViews: MetricsExplorerViewsServiceStart; getMetricIndices: ( savedObjectsClient: SavedObjectsClientContract, sourceId?: string From 2376ee95e24ee0119a5cf4be9e1cc3d6d04f4994 Mon Sep 17 00:00:00 2001 From: Marco Liberati Date: Wed, 26 Apr 2023 14:37:21 +0200 Subject: [PATCH 08/73] [Lens] Fix formula error popover too wide issue (#155529) ## Summary This PR limits the width for the formula error popup. Screenshot 2023-04-21 at 17 34 03 ### Checklist Delete any items that are not applicable to this PR. - [ ] Any text added follows [EUI's writing guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses sentence case text and includes [i18n support](https://github.com/elastic/kibana/blob/main/packages/kbn-i18n/README.md) - [ ] [Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html) was added for features that require explanation or tutorials - [ ] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios - [ ] Any UI touched in this PR is usable by keyboard only (learn more about [keyboard accessibility](https://webaim.org/techniques/keyboard/)) - [ ] Any UI touched in this PR does not create any new axe failures (run axe in browser: [FF](https://addons.mozilla.org/en-US/firefox/addon/axe-devtools/), [Chrome](https://chrome.google.com/webstore/detail/axe-web-accessibility-tes/lhdoppojpmngadmnindnejefpokejbdd?hl=en-US)) - [ ] If a plugin configuration key changed, check if it needs to be allowlisted in the cloud and added to the [docker list](https://github.com/elastic/kibana/blob/main/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker) - [ ] This renders correctly on smaller devices using a responsive layout. (You can test this [in your browser](https://www.browserstack.com/guide/responsive-testing-on-local-server)) - [ ] This was checked for [cross-browser compatibility](https://www.elastic.co/support/matrix#matrix_browsers) ### Risk Matrix Delete this section if it is not applicable to this PR. Before closing this PR, invite QA, stakeholders, and other developers to identify risks that should be tested prior to the change/feature release. When forming the risk matrix, consider some of the following examples and how they may potentially impact the change: | Risk | Probability | Severity | Mitigation/Notes | |---------------------------|-------------|----------|-------------------------| | Multiple Spaces—unexpected behavior in non-default Kibana Space. | Low | High | Integration tests will verify that all features are still supported in non-default Kibana Space and when user switches between spaces. | | Multiple nodes—Elasticsearch polling might have race conditions when multiple Kibana nodes are polling for the same tasks. | High | Low | Tasks are idempotent, so executing them multiple times will not result in logical error, but will degrade performance. To test for this case we add plenty of unit tests around this logic and document manual testing procedure. | | Code should gracefully handle cases when feature X or plugin Y are disabled. | Medium | High | Unit tests will verify that any feature flag or plugin combination still results in our service operational. | | [See more potential risk examples](https://github.com/elastic/kibana/blob/main/RISK_MATRIX.mdx) | ### For maintainers - [ ] This was checked for breaking API changes and was [labeled appropriately](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process) --- .../formula/editor/formula_editor.tsx | 30 +++++++++++-------- 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/x-pack/plugins/lens/public/datasources/form_based/operations/definitions/formula/editor/formula_editor.tsx b/x-pack/plugins/lens/public/datasources/form_based/operations/definitions/formula/editor/formula_editor.tsx index 941047519cdac..c565c437bc59d 100644 --- a/x-pack/plugins/lens/public/datasources/form_based/operations/definitions/formula/editor/formula_editor.tsx +++ b/x-pack/plugins/lens/public/datasources/form_based/operations/definitions/formula/editor/formula_editor.tsx @@ -908,18 +908,24 @@ export function FormulaEditor({ } > - {warnings.map(({ message, severity }, index) => ( -
- - {message} - -
- ))} +
+ {warnings.map(({ message, severity }, index) => ( +
+ + {message} + +
+ ))} +
) : null} From 049c51093b5cc44a4fcad4a81486db89b2fcd8ad Mon Sep 17 00:00:00 2001 From: Devon Thomson Date: Wed, 26 Apr 2023 09:02:59 -0400 Subject: [PATCH 09/73] [Dashboard] fix custom time ranges not applied to panel until global query context changes (#155458) Fixes https://github.com/elastic/kibana/issues/155409 https://github.com/elastic/kibana/pull/152516 incorrectly attempted to resolve https://github.com/elastic/kibana/issues/151221. https://github.com/elastic/kibana/pull/152516 updated shouldFetch$ to only check searchSessionId to determine if re-fetching is required. This logic did not work when custom time ranges are applied to panels since custom time ranges do not require new search session id yet the time range changed. This PR reverts shouldFetch$ logic of only checking searchSessionId to determine if re-fetching is required. Instead, this PR moves searchSessionId out of input and into dashboard instance state. That way, `input` updates, such as query, do not trigger additional `input` updates. The PR also updates seachSessionId logic from async to sync so that dashboard can update seachSessionId on input changes prior to child embeddables updating to parent input changes. This avoids the double fetch and allows children to only have a single input update on query state change. There was a functional test, panel_time_range, that should have caught the regression but that test had a bug in it. The assertion that the custom time range was applied looked like `expect(await testSubjects.exists('emptyPlaceholder'))` which will never fail the test because the last part of the expect is missing. Instead, the statements should be `expect(await testSubjects.exists('emptyPlaceholder')).to.be(true)`. These updates to the functional test would have caught the regression (I verified this by making these changes on main and running the test. They do indeed fail). --------- Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: nreese --- .../create/create_dashboard.test.ts | 63 +++++++++++++++++ .../embeddable/create/create_dashboard.ts | 5 +- ...rt_dashboard_search_session_integration.ts | 11 ++- .../embeddable/dashboard_container.test.tsx | 15 ++-- .../embeddable/dashboard_container.tsx | 7 +- .../state/dashboard_container_reducers.ts | 7 -- .../diffing/dashboard_diffing_functions.ts | 21 +++++- .../dashboard_diffing_integration.test.ts | 47 +++++++++---- .../diffing/dashboard_diffing_integration.ts | 70 +++++++++---------- .../filterable_embeddable/should_fetch.tsx | 27 ++++--- .../test_suites/data_plugin/session.ts | 2 +- .../public/embeddable/embeddable.test.tsx | 5 -- .../apps/dashboard/group2/panel_time_range.ts | 8 +-- 13 files changed, 193 insertions(+), 95 deletions(-) diff --git a/src/plugins/dashboard/public/dashboard_container/embeddable/create/create_dashboard.test.ts b/src/plugins/dashboard/public/dashboard_container/embeddable/create/create_dashboard.test.ts index 6f9f40745322b..947b062b3a551 100644 --- a/src/plugins/dashboard/public/dashboard_container/embeddable/create/create_dashboard.test.ts +++ b/src/plugins/dashboard/public/dashboard_container/embeddable/create/create_dashboard.test.ts @@ -7,7 +7,10 @@ */ import { + ContactCardEmbeddable, + ContactCardEmbeddableFactory, ContactCardEmbeddableInput, + ContactCardEmbeddableOutput, CONTACT_CARD_EMBEDDABLE, } from '@kbn/embeddable-plugin/public/lib/test_samples'; import { @@ -253,3 +256,63 @@ test('creates a control group from the control group factory and waits for it to ); expect(mockControlGroupContainer.untilInitialized).toHaveBeenCalled(); }); + +/* + * dashboard.getInput$() subscriptions are used to update: + * 1) dashboard instance searchSessionId state + * 2) child input on parent input changes + * + * Rxjs subscriptions are executed in the order that they are created. + * This test ensures that searchSessionId update subscription is created before child input subscription + * to ensure child input subscription includes updated searchSessionId. + */ +test('searchSessionId is updated prior to child embeddable parent subscription execution', async () => { + const embeddableFactory = { + create: new ContactCardEmbeddableFactory((() => null) as any, {} as any), + getDefaultInput: jest.fn().mockResolvedValue({ + timeRange: { + to: 'now', + from: 'now-15m', + }, + }), + }; + pluginServices.getServices().embeddable.getEmbeddableFactory = jest + .fn() + .mockReturnValue(embeddableFactory); + let sessionCount = 0; + pluginServices.getServices().data.search.session.start = () => { + sessionCount++; + return `searchSessionId${sessionCount}`; + }; + const dashboard = await createDashboard(embeddableId, { + searchSessionSettings: { + getSearchSessionIdFromURL: () => undefined, + removeSessionIdFromUrl: () => {}, + createSessionRestorationDataProvider: () => {}, + } as unknown as DashboardCreationOptions['searchSessionSettings'], + }); + const embeddable = await dashboard.addNewEmbeddable< + ContactCardEmbeddableInput, + ContactCardEmbeddableOutput, + ContactCardEmbeddable + >(CONTACT_CARD_EMBEDDABLE, { + firstName: 'Bob', + }); + + expect(embeddable.getInput().searchSessionId).toBe('searchSessionId1'); + + dashboard.updateInput({ + timeRange: { + to: 'now', + from: 'now-7d', + }, + }); + + expect(sessionCount).toBeGreaterThan(1); + const embeddableInput = embeddable.getInput(); + expect((embeddableInput as any).timeRange).toEqual({ + to: 'now', + from: 'now-7d', + }); + expect(embeddableInput.searchSessionId).toBe(`searchSessionId${sessionCount}`); +}); diff --git a/src/plugins/dashboard/public/dashboard_container/embeddable/create/create_dashboard.ts b/src/plugins/dashboard/public/dashboard_container/embeddable/create/create_dashboard.ts index f0a20e832e431..ef810f025b84b 100644 --- a/src/plugins/dashboard/public/dashboard_container/embeddable/create/create_dashboard.ts +++ b/src/plugins/dashboard/public/dashboard_container/embeddable/create/create_dashboard.ts @@ -217,6 +217,7 @@ export const createDashboard = async ( // -------------------------------------------------------------------------------------- // Set up search sessions integration. // -------------------------------------------------------------------------------------- + let initialSearchSessionId; if (searchSessionSettings) { const { sessionIdToRestore } = searchSessionSettings; @@ -229,7 +230,7 @@ export const createDashboard = async ( } const existingSession = session.getSessionId(); - const initialSearchSessionId = + initialSearchSessionId = sessionIdToRestore ?? (existingSession && incomingEmbeddable ? existingSession : session.start()); @@ -238,7 +239,6 @@ export const createDashboard = async ( creationOptions?.searchSessionSettings ); }); - initialInput.searchSessionId = initialSearchSessionId; } // -------------------------------------------------------------------------------------- @@ -284,6 +284,7 @@ export const createDashboard = async ( const dashboardContainer = new DashboardContainer( initialInput, reduxEmbeddablePackage, + initialSearchSessionId, savedObjectResult?.dashboardInput, dashboardCreationStartTime, undefined, diff --git a/src/plugins/dashboard/public/dashboard_container/embeddable/create/search_sessions/start_dashboard_search_session_integration.ts b/src/plugins/dashboard/public/dashboard_container/embeddable/create/search_sessions/start_dashboard_search_session_integration.ts index 506083ab25386..7f59b56c228b6 100644 --- a/src/plugins/dashboard/public/dashboard_container/embeddable/create/search_sessions/start_dashboard_search_session_integration.ts +++ b/src/plugins/dashboard/public/dashboard_container/embeddable/create/search_sessions/start_dashboard_search_session_integration.ts @@ -6,14 +6,13 @@ * Side Public License, v 1. */ -import { debounceTime, pairwise, skip } from 'rxjs/operators'; +import { pairwise, skip } from 'rxjs/operators'; import { noSearchSessionStorageCapabilityMessage } from '@kbn/data-plugin/public'; import { DashboardContainer } from '../../dashboard_container'; import { DashboardContainerInput } from '../../../../../common'; import { pluginServices } from '../../../../services/plugin_services'; -import { CHANGE_CHECK_DEBOUNCE } from '../../../../dashboard_constants'; import { DashboardCreationOptions } from '../../dashboard_container_factory'; import { getShouldRefresh } from '../../../state/diffing/dashboard_diffing_integration'; @@ -57,10 +56,10 @@ export function startDashboardSearchSessionIntegration( // listen to and compare states to determine when to launch a new session. this.getInput$() - .pipe(pairwise(), debounceTime(CHANGE_CHECK_DEBOUNCE)) - .subscribe(async (states) => { + .pipe(pairwise()) + .subscribe((states) => { const [previous, current] = states as DashboardContainerInput[]; - const shouldRefetch = await getShouldRefresh.bind(this)(previous, current); + const shouldRefetch = getShouldRefresh.bind(this)(previous, current); if (!shouldRefetch) return; const currentSearchSessionId = this.getState().explicitInput.searchSessionId; @@ -83,7 +82,7 @@ export function startDashboardSearchSessionIntegration( })(); if (updatedSearchSessionId && updatedSearchSessionId !== currentSearchSessionId) { - this.dispatch.setSearchSessionId(updatedSearchSessionId); + this.searchSessionId = updatedSearchSessionId; } }); diff --git a/src/plugins/dashboard/public/dashboard_container/embeddable/dashboard_container.test.tsx b/src/plugins/dashboard/public/dashboard_container/embeddable/dashboard_container.test.tsx index bdce2754ba0dd..5a360446f03a8 100644 --- a/src/plugins/dashboard/public/dashboard_container/embeddable/dashboard_container.test.tsx +++ b/src/plugins/dashboard/public/dashboard_container/embeddable/dashboard_container.test.tsx @@ -9,6 +9,7 @@ import React from 'react'; import { mount } from 'enzyme'; +import { mockedReduxEmbeddablePackage } from '@kbn/presentation-util-plugin/public/mocks'; import { findTestSubject, nextTick } from '@kbn/test-jest-helpers'; import { I18nProvider } from '@kbn/i18n-react'; import { @@ -29,9 +30,10 @@ import { applicationServiceMock, coreMock } from '@kbn/core/public/mocks'; import { uiActionsPluginMock } from '@kbn/ui-actions-plugin/public/mocks'; import { createEditModeActionDefinition } from '@kbn/embeddable-plugin/public/lib/test_samples'; -import { buildMockDashboard, getSampleDashboardPanel } from '../../mocks'; +import { buildMockDashboard, getSampleDashboardInput, getSampleDashboardPanel } from '../../mocks'; import { pluginServices } from '../../services/plugin_services'; import { ApplicationStart } from '@kbn/core-application-browser'; +import { DashboardContainer } from './dashboard_container'; const theme = coreMock.createStart().theme; let application: ApplicationStart | undefined; @@ -171,7 +173,11 @@ test('Container view mode change propagates to new children', async () => { test('searchSessionId propagates to children', async () => { const searchSessionId1 = 'searchSessionId1'; - const container = buildMockDashboard({ searchSessionId: searchSessionId1 }); + const container = new DashboardContainer( + getSampleDashboardInput(), + mockedReduxEmbeddablePackage, + searchSessionId1 + ); const embeddable = await container.addNewEmbeddable< ContactCardEmbeddableInput, ContactCardEmbeddableOutput, @@ -181,11 +187,6 @@ test('searchSessionId propagates to children', async () => { }); expect(embeddable.getInput().searchSessionId).toBe(searchSessionId1); - - const searchSessionId2 = 'searchSessionId2'; - container.updateInput({ searchSessionId: searchSessionId2 }); - - expect(embeddable.getInput().searchSessionId).toBe(searchSessionId2); }); test('DashboardContainer in edit mode shows edit mode actions', async () => { diff --git a/src/plugins/dashboard/public/dashboard_container/embeddable/dashboard_container.tsx b/src/plugins/dashboard/public/dashboard_container/embeddable/dashboard_container.tsx index d5a5385e779b3..a0aec2c395524 100644 --- a/src/plugins/dashboard/public/dashboard_container/embeddable/dashboard_container.tsx +++ b/src/plugins/dashboard/public/dashboard_container/embeddable/dashboard_container.tsx @@ -95,6 +95,8 @@ export class DashboardContainer extends Container void; private cleanupStateTools: () => void; @@ -117,6 +119,7 @@ export class DashboardContainer extends Container - ) => { - state.explicitInput.searchSessionId = action.payload; - }, - // ------------------------------------------------------------------------------ // Unsaved Changes Reducers // ------------------------------------------------------------------------------ diff --git a/src/plugins/dashboard/public/dashboard_container/state/diffing/dashboard_diffing_functions.ts b/src/plugins/dashboard/public/dashboard_container/state/diffing/dashboard_diffing_functions.ts index 7f2a55044b527..fe8e18528e2c0 100644 --- a/src/plugins/dashboard/public/dashboard_container/state/diffing/dashboard_diffing_functions.ts +++ b/src/plugins/dashboard/public/dashboard_container/state/diffing/dashboard_diffing_functions.ts @@ -37,7 +37,7 @@ export type DashboardDiffFunctions = { ) => boolean | Promise; }; -export const isKeyEqual = async ( +export const isKeyEqualAsync = async ( key: keyof DashboardContainerInput, diffFunctionProps: DiffFunctionProps, diffingFunctions: DashboardDiffFunctions @@ -52,6 +52,25 @@ export const isKeyEqual = async ( return fastIsEqual(diffFunctionProps.currentValue, diffFunctionProps.lastValue); }; +export const isKeyEqual = ( + key: keyof Omit, // only Panels is async + diffFunctionProps: DiffFunctionProps, + diffingFunctions: DashboardDiffFunctions +) => { + const propsAsNever = diffFunctionProps as never; // todo figure out why props has conflicting types in some constituents. + const diffingFunction = diffingFunctions[key]; + if (!diffingFunction) { + return fastIsEqual(diffFunctionProps.currentValue, diffFunctionProps.lastValue); + } + + if (diffingFunction?.prototype?.name === 'AsyncFunction') { + throw new Error( + `The function for key "${key}" is async, must use isKeyEqualAsync for asynchronous functions` + ); + } + return diffingFunction(propsAsNever); +}; + /** * A collection of functions which diff individual keys of dashboard state. If a key is missing from this list it is * diffed by the default diffing function, fastIsEqual. diff --git a/src/plugins/dashboard/public/dashboard_container/state/diffing/dashboard_diffing_integration.test.ts b/src/plugins/dashboard/public/dashboard_container/state/diffing/dashboard_diffing_integration.test.ts index c0953f8bbc98a..b79eb27af3d79 100644 --- a/src/plugins/dashboard/public/dashboard_container/state/diffing/dashboard_diffing_integration.test.ts +++ b/src/plugins/dashboard/public/dashboard_container/state/diffing/dashboard_diffing_integration.test.ts @@ -29,14 +29,14 @@ describe('getShouldRefresh', () => { ); describe('filter changes', () => { - test('should return false when filters do not change', async () => { + test('should return false when filters do not change', () => { const lastInput = { filters: [existsFilter], } as unknown as DashboardContainerInput; - expect(await getShouldRefresh.bind(dashboardContainerMock)(lastInput, lastInput)).toBe(false); + expect(getShouldRefresh.bind(dashboardContainerMock)(lastInput, lastInput)).toBe(false); }); - test('should return true when pinned filters change', async () => { + test('should return true when pinned filters change', () => { const pinnedFilter = pinFilter(existsFilter); const lastInput = { filters: [pinnedFilter], @@ -44,10 +44,10 @@ describe('getShouldRefresh', () => { const input = { filters: [toggleFilterNegated(pinnedFilter)], } as unknown as DashboardContainerInput; - expect(await getShouldRefresh.bind(dashboardContainerMock)(lastInput, input)).toBe(true); + expect(getShouldRefresh.bind(dashboardContainerMock)(lastInput, input)).toBe(true); }); - test('should return false when disabled filters change', async () => { + test('should return false when disabled filters change', () => { const disabledFilter = disableFilter(existsFilter); const lastInput = { filters: [disabledFilter], @@ -55,29 +55,29 @@ describe('getShouldRefresh', () => { const input = { filters: [toggleFilterNegated(disabledFilter)], } as unknown as DashboardContainerInput; - expect(await getShouldRefresh.bind(dashboardContainerMock)(lastInput, input)).toBe(false); + expect(getShouldRefresh.bind(dashboardContainerMock)(lastInput, input)).toBe(false); }); - test('should return false when pinned filter changes to unpinned', async () => { + test('should return false when pinned filter changes to unpinned', () => { const lastInput = { filters: [existsFilter], } as unknown as DashboardContainerInput; const input = { filters: [pinFilter(existsFilter)], } as unknown as DashboardContainerInput; - expect(await getShouldRefresh.bind(dashboardContainerMock)(lastInput, input)).toBe(false); + expect(getShouldRefresh.bind(dashboardContainerMock)(lastInput, input)).toBe(false); }); }); describe('timeRange changes', () => { - test('should return false when timeRange does not change', async () => { + test('should return false when timeRange does not change', () => { const lastInput = { timeRange: { from: 'now-15m', to: 'now' }, } as unknown as DashboardContainerInput; - expect(await getShouldRefresh.bind(dashboardContainerMock)(lastInput, lastInput)).toBe(false); + expect(getShouldRefresh.bind(dashboardContainerMock)(lastInput, lastInput)).toBe(false); }); - test('should return true when timeRange changes (timeRestore is true)', async () => { + test('should return true when timeRange changes (timeRestore is true)', () => { const lastInput = { timeRange: { from: 'now-15m', to: 'now' }, timeRestore: true, @@ -86,10 +86,10 @@ describe('getShouldRefresh', () => { timeRange: { from: 'now-30m', to: 'now' }, timeRestore: true, } as unknown as DashboardContainerInput; - expect(await getShouldRefresh.bind(dashboardContainerMock)(lastInput, input)).toBe(true); + expect(getShouldRefresh.bind(dashboardContainerMock)(lastInput, input)).toBe(true); }); - test('should return true when timeRange changes (timeRestore is false)', async () => { + test('should return true when timeRange changes (timeRestore is false)', () => { const lastInput = { timeRange: { from: 'now-15m', to: 'now' }, timeRestore: false, @@ -98,7 +98,26 @@ describe('getShouldRefresh', () => { timeRange: { from: 'now-30m', to: 'now' }, timeRestore: false, } as unknown as DashboardContainerInput; - expect(await getShouldRefresh.bind(dashboardContainerMock)(lastInput, input)).toBe(true); + expect(getShouldRefresh.bind(dashboardContainerMock)(lastInput, input)).toBe(true); + }); + }); + + describe('key without custom diffing function (syncColors)', () => { + test('should return false when syncColors do not change', () => { + const lastInput = { + syncColors: false, + } as unknown as DashboardContainerInput; + expect(getShouldRefresh.bind(dashboardContainerMock)(lastInput, lastInput)).toBe(false); + }); + + test('should return true when syncColors change', () => { + const lastInput = { + syncColors: false, + } as unknown as DashboardContainerInput; + const input = { + syncColors: true, + } as unknown as DashboardContainerInput; + expect(getShouldRefresh.bind(dashboardContainerMock)(lastInput, input)).toBe(true); }); }); }); diff --git a/src/plugins/dashboard/public/dashboard_container/state/diffing/dashboard_diffing_integration.ts b/src/plugins/dashboard/public/dashboard_container/state/diffing/dashboard_diffing_integration.ts index f91cfe51fe739..897ac529fe61d 100644 --- a/src/plugins/dashboard/public/dashboard_container/state/diffing/dashboard_diffing_integration.ts +++ b/src/plugins/dashboard/public/dashboard_container/state/diffing/dashboard_diffing_integration.ts @@ -9,13 +9,13 @@ import { omit } from 'lodash'; import { AnyAction, Middleware } from 'redux'; import { debounceTime, Observable, startWith, Subject, switchMap } from 'rxjs'; -import { DashboardContainerInput } from '../../../../common'; -import type { DashboardDiffFunctions } from './dashboard_diffing_functions'; import { isKeyEqual, + isKeyEqualAsync, shouldRefreshDiffingFunctions, unsavedChangesDiffingFunctions, } from './dashboard_diffing_functions'; +import { DashboardContainerInput } from '../../../../common'; import { pluginServices } from '../../../services/plugin_services'; import { DashboardContainer, DashboardCreationOptions } from '../..'; import { CHANGE_CHECK_DEBOUNCE } from '../../../dashboard_constants'; @@ -29,7 +29,6 @@ import { dashboardContainerReducers } from '../dashboard_container_reducers'; export const reducersToIgnore: Array = [ 'setTimeslice', 'setFullScreenMode', - 'setSearchSessionId', 'setExpandedPanelId', 'setHasUnsavedChanges', ]; @@ -40,7 +39,6 @@ export const reducersToIgnore: Array = const keysToOmitFromSessionStorage: Array = [ 'lastReloadRequestTime', 'executionContext', - 'searchSessionId', 'timeslice', 'id', @@ -55,7 +53,6 @@ const keysToOmitFromSessionStorage: Array = [ export const keysNotConsideredUnsavedChanges: Array = [ 'lastReloadRequestTime', 'executionContext', - 'searchSessionId', 'timeslice', 'viewMode', 'id', @@ -64,7 +61,7 @@ export const keysNotConsideredUnsavedChanges: Array = [ +const sessionChangeKeys: Array> = [ 'query', 'filters', 'timeRange', @@ -139,42 +136,17 @@ export async function getUnsavedChanges( const allKeys = [...new Set([...Object.keys(lastInput), ...Object.keys(input)])] as Array< keyof DashboardContainerInput >; - return await getInputChanges(this, lastInput, input, allKeys, unsavedChangesDiffingFunctions); -} - -export async function getShouldRefresh( - this: DashboardContainer, - lastInput: DashboardContainerInput, - input: DashboardContainerInput -): Promise { - const inputChanges = await getInputChanges( - this, - lastInput, - input, - refetchKeys, - shouldRefreshDiffingFunctions - ); - return Object.keys(inputChanges).length > 0; -} - -async function getInputChanges( - container: DashboardContainer, - lastInput: DashboardContainerInput, - input: DashboardContainerInput, - keys: Array, - diffingFunctions: DashboardDiffFunctions -): Promise> { - const keyComparePromises = keys.map( + const keyComparePromises = allKeys.map( (key) => new Promise<{ key: keyof DashboardContainerInput; isEqual: boolean }>((resolve) => { if (input[key] === undefined && lastInput[key] === undefined) { resolve({ key, isEqual: true }); } - isKeyEqual( + isKeyEqualAsync( key, { - container, + container: this, currentValue: input[key], currentInput: input, @@ -182,7 +154,7 @@ async function getInputChanges( lastValue: lastInput[key], lastInput, }, - diffingFunctions + unsavedChangesDiffingFunctions ).then((isEqual) => resolve({ key, isEqual })); }) ); @@ -196,6 +168,34 @@ async function getInputChanges( return inputChanges; } +export function getShouldRefresh( + this: DashboardContainer, + lastInput: DashboardContainerInput, + input: DashboardContainerInput +): boolean { + for (const key of sessionChangeKeys) { + if (input[key] === undefined && lastInput[key] === undefined) { + continue; + } + if ( + !isKeyEqual( + key, + { + container: this, + currentValue: input[key], + currentInput: input, + lastValue: lastInput[key], + lastInput, + }, + shouldRefreshDiffingFunctions + ) + ) { + return true; + } + } + return false; +} + function updateUnsavedChangesState( this: DashboardContainer, unsavedChanges: Partial diff --git a/src/plugins/embeddable/public/lib/filterable_embeddable/should_fetch.tsx b/src/plugins/embeddable/public/lib/filterable_embeddable/should_fetch.tsx index b8b9fb1f4795f..68d9df23bb612 100644 --- a/src/plugins/embeddable/public/lib/filterable_embeddable/should_fetch.tsx +++ b/src/plugins/embeddable/public/lib/filterable_embeddable/should_fetch.tsx @@ -27,19 +27,24 @@ export function shouldFetch$< return updated$.pipe(map(() => getInput())).pipe( // wrapping distinctUntilChanged with startWith and skip to prime distinctUntilChanged with an initial input value. startWith(getInput()), - distinctUntilChanged((a: TFilterableEmbeddableInput, b: TFilterableEmbeddableInput) => { - // Only need to diff searchSessionId when container uses search sessions because - // searchSessionId changes with any filter, query, or time changes - if (a.searchSessionId !== undefined || b.searchSessionId !== undefined) { - return a.searchSessionId === b.searchSessionId; - } + distinctUntilChanged( + (previous: TFilterableEmbeddableInput, current: TFilterableEmbeddableInput) => { + if ( + !fastIsEqual( + [previous.searchSessionId, previous.query, previous.timeRange, previous.timeslice], + [current.searchSessionId, current.query, current.timeRange, current.timeslice] + ) + ) { + return false; + } - if (!fastIsEqual([a.query, a.timeRange, a.timeslice], [b.query, b.timeRange, b.timeslice])) { - return false; + return onlyDisabledFiltersChanged( + previous.filters, + current.filters, + shouldRefreshFilterCompareOptions + ); } - - return onlyDisabledFiltersChanged(a.filters, b.filters, shouldRefreshFilterCompareOptions); - }), + ), skip(1) ); } diff --git a/test/plugin_functional/test_suites/data_plugin/session.ts b/test/plugin_functional/test_suites/data_plugin/session.ts index 6c485a76db32e..469e6f992e79f 100644 --- a/test/plugin_functional/test_suites/data_plugin/session.ts +++ b/test/plugin_functional/test_suites/data_plugin/session.ts @@ -106,7 +106,7 @@ export default function ({ getService, getPageObjects }: PluginFunctionalProvide }); it('starts a session on filter change', async () => { - await filterBar.removeAllFilters(); + await filterBar.removeFilter('animal'); const sessionIds = await getSessionIds(); expect(sessionIds.length).to.be(1); }); diff --git a/x-pack/plugins/lens/public/embeddable/embeddable.test.tsx b/x-pack/plugins/lens/public/embeddable/embeddable.test.tsx index c127e9f1130d3..5e48e1f46dd63 100644 --- a/x-pack/plugins/lens/public/embeddable/embeddable.test.tsx +++ b/x-pack/plugins/lens/public/embeddable/embeddable.test.tsx @@ -441,11 +441,6 @@ describe('embeddable', () => { expect(expressionRenderer).toHaveBeenCalledTimes(1); - embeddable.updateInput({ - filters: [{ meta: { alias: 'test', negate: false, disabled: false } }], - }); - await new Promise((resolve) => setTimeout(resolve, 0)); - embeddable.updateInput({ searchSessionId: 'nextSession', }); diff --git a/x-pack/test/functional/apps/dashboard/group2/panel_time_range.ts b/x-pack/test/functional/apps/dashboard/group2/panel_time_range.ts index ee07446783603..2295c90d60c65 100644 --- a/x-pack/test/functional/apps/dashboard/group2/panel_time_range.ts +++ b/x-pack/test/functional/apps/dashboard/group2/panel_time_range.ts @@ -50,7 +50,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await dashboardCustomizePanel.clickSaveButton(); await PageObjects.dashboard.waitForRenderComplete(); await dashboardBadgeActions.expectExistsTimeRangeBadgeAction(); - expect(await testSubjects.exists('emptyPlaceholder')); + expect(await testSubjects.exists('emptyPlaceholder')).to.be(true); await PageObjects.dashboard.clickQuickSave(); }); @@ -60,7 +60,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await dashboardCustomizePanel.clickSaveButton(); await PageObjects.dashboard.waitForRenderComplete(); await dashboardBadgeActions.expectMissingTimeRangeBadgeAction(); - expect(await testSubjects.exists('xyVisChart')); + expect(await testSubjects.exists('xyVisChart')).to.be(true); }); }); @@ -74,7 +74,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await dashboardCustomizePanel.clickSaveButton(); await PageObjects.dashboard.waitForRenderComplete(); await dashboardBadgeActions.expectExistsTimeRangeBadgeAction(); - expect(await testSubjects.exists('emptyPlaceholder')); + expect(await testSubjects.exists('emptyPlaceholder')).to.be(true); await PageObjects.dashboard.clickQuickSave(); }); @@ -84,7 +84,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await dashboardCustomizePanel.clickSaveButton(); await PageObjects.dashboard.waitForRenderComplete(); await dashboardBadgeActions.expectMissingTimeRangeBadgeAction(); - expect(await testSubjects.exists('xyVisChart')); + expect(await testSubjects.exists('xyVisChart')).to.be(true); }); }); From 341974ae1629e8f2dc86853dfbfec98517f900b4 Mon Sep 17 00:00:00 2001 From: Yan Savitski Date: Wed, 26 Apr 2023 15:12:57 +0200 Subject: [PATCH 10/73] [Enterprise Search] [Behavioral analytics] Add locations table (#155807) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - ✔️ Add locations table with flag - ✔️ Create locations formula with multi_terms - ✔️ Fix bug with updating explore table after dataView changed - ✔️ Show flags of countries by iso code image --- .../analytics_collection_data_view_logic.ts | 2 +- ...ytics_collection_explore_table_formulas.ts | 4 +- ...ics_collection_explore_table_logic.test.ts | 30 +-- ...nalytics_collection_explore_table_logic.ts | 172 +++++++++++++----- ...nalytics_collection_explore_table_types.ts | 21 ++- ...alytics_collection_explorer_table.test.tsx | 4 +- .../analytics_collection_explorer_table.tsx | 66 ++++++- ...alytics_collection_overview_table.test.tsx | 2 +- .../analytics_collection_overview_table.tsx | 67 ++++++- .../applications/analytics/utils/get_flag.ts | 13 ++ 10 files changed, 299 insertions(+), 82 deletions(-) create mode 100644 x-pack/plugins/enterprise_search/public/applications/analytics/utils/get_flag.ts 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 index f00ef0fecc1ed..bebd7e04278c9 100644 --- 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 @@ -20,7 +20,7 @@ export interface AnalyticsCollectionDataViewLogicValues { dataView: DataView | null; } -interface AnalyticsCollectionDataViewLogicActions { +export interface AnalyticsCollectionDataViewLogicActions { fetchedAnalyticsCollection: FetchAnalyticsCollectionActions['apiSuccess']; setDataView(dataView: DataView): { dataView: DataView }; } 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 index 87e9812b94ac7..b809f23b53d02 100644 --- 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 @@ -5,7 +5,7 @@ * 2.0. */ -import { IKibanaSearchRequest, TimeRange } from '@kbn/data-plugin/common'; +import { DataView, IKibanaSearchRequest, TimeRange } from '@kbn/data-plugin/common'; const getSearchQueryRequestParams = (field: string, search: string): { regexp: {} } => { const createRegexQuery = (queryString: string) => { @@ -44,6 +44,7 @@ export const getPaginationRequestParams = (pageIndex: number, pageSize: number) }); export const getBaseSearchTemplate = ( + dataView: DataView, aggregationFieldName: string, { search, @@ -53,6 +54,7 @@ export const getBaseSearchTemplate = ( aggs: IKibanaSearchRequest['params']['aggs'] ): IKibanaSearchRequest => ({ params: { + index: dataView.title, aggs, query: { bool: { 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 index 226c521c44894..8c37a7f41e8c2 100644 --- 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 @@ -7,6 +7,7 @@ import { LogicMounter } from '../../../__mocks__/kea_logic'; +import { DataView } from '@kbn/data-views-plugin/common'; import { nextTick } from '@kbn/test-jest-helpers'; import { KibanaLogic } from '../../../shared/kibana/kibana_logic'; @@ -87,7 +88,8 @@ describe('AnalyticsCollectionExplorerTablesLogic', () => { describe('isLoading', () => { beforeEach(() => { - mount({ selectedTable: ExploreTables.TopReferrers }); + mount({ selectedTable: ExploreTables.Referrers }); + AnalyticsCollectionExploreTableLogic.actions.setDataView({ id: 'test' } as DataView); }); it('should handle onTableChange', () => { @@ -112,7 +114,7 @@ describe('AnalyticsCollectionExplorerTablesLogic', () => { }); it('should handle setSelectedTable', () => { - AnalyticsCollectionExploreTableLogic.actions.setSelectedTable(ExploreTables.TopReferrers); + AnalyticsCollectionExploreTableLogic.actions.setSelectedTable(ExploreTables.Referrers); expect(AnalyticsCollectionExploreTableLogic.values.isLoading).toEqual(true); }); @@ -139,7 +141,7 @@ describe('AnalyticsCollectionExplorerTablesLogic', () => { AnalyticsCollectionExploreTableLogic.actions.onTableChange({ page: { index: 2, size: 10 }, }); - AnalyticsCollectionExploreTableLogic.actions.setSelectedTable(ExploreTables.TopReferrers); + AnalyticsCollectionExploreTableLogic.actions.setSelectedTable(ExploreTables.Referrers); expect(AnalyticsCollectionExploreTableLogic.values.pageIndex).toEqual(0); }); @@ -172,7 +174,7 @@ describe('AnalyticsCollectionExplorerTablesLogic', () => { AnalyticsCollectionExploreTableLogic.actions.onTableChange({ page: { index: 2, size: 10 }, }); - AnalyticsCollectionExploreTableLogic.actions.setSelectedTable(ExploreTables.TopReferrers); + AnalyticsCollectionExploreTableLogic.actions.setSelectedTable(ExploreTables.Referrers); expect(AnalyticsCollectionExploreTableLogic.values.pageSize).toEqual(10); }); @@ -193,7 +195,7 @@ describe('AnalyticsCollectionExplorerTablesLogic', () => { it('should handle setSelectedTable', () => { AnalyticsCollectionExploreTableLogic.actions.setSearch('test'); - AnalyticsCollectionExploreTableLogic.actions.setSelectedTable(ExploreTables.TopReferrers); + AnalyticsCollectionExploreTableLogic.actions.setSelectedTable(ExploreTables.Referrers); expect(AnalyticsCollectionExploreTableLogic.values.search).toEqual(''); }); @@ -211,10 +213,16 @@ describe('AnalyticsCollectionExplorerTablesLogic', () => { }); describe('listeners', () => { + const mockDataView = { id: 'test' } as DataView; + beforeEach(() => { + mount({ selectedTable: ExploreTables.Referrers }); + AnalyticsCollectionExploreTableLogic.actions.setDataView(mockDataView); + }); + it('should fetch items when selectedTable changes', () => { - AnalyticsCollectionExploreTableLogic.actions.setSelectedTable(ExploreTables.TopReferrers); + AnalyticsCollectionExploreTableLogic.actions.setSelectedTable(ExploreTables.Referrers); expect(KibanaLogic.values.data.search.search).toHaveBeenCalledWith(expect.any(Object), { - indexPattern: undefined, + indexPattern: mockDataView, sessionId: undefined, }); }); @@ -225,7 +233,7 @@ describe('AnalyticsCollectionExplorerTablesLogic', () => { AnalyticsCollectionToolbarLogic.actions.setTimeRange({ from: 'now-7d', to: 'now' }); expect(KibanaLogic.values.data.search.search).toHaveBeenCalledWith(expect.any(Object), { - indexPattern: undefined, + indexPattern: mockDataView, sessionId: undefined, }); }); @@ -236,7 +244,7 @@ describe('AnalyticsCollectionExplorerTablesLogic', () => { AnalyticsCollectionToolbarLogic.actions.setSearchSessionId('1234'); expect(KibanaLogic.values.data.search.search).toHaveBeenCalledWith(expect.any(Object), { - indexPattern: undefined, + indexPattern: mockDataView, sessionId: '1234', }); }); @@ -247,7 +255,7 @@ describe('AnalyticsCollectionExplorerTablesLogic', () => { AnalyticsCollectionExploreTableLogic.actions.onTableChange({}); expect(KibanaLogic.values.data.search.search).toHaveBeenCalledWith(expect.any(Object), { - indexPattern: undefined, + indexPattern: mockDataView, sessionId: undefined, }); }); @@ -262,7 +270,7 @@ describe('AnalyticsCollectionExplorerTablesLogic', () => { await nextTick(); expect(KibanaLogic.values.data.search.search).toHaveBeenCalledWith(expect.any(Object), { - indexPattern: undefined, + indexPattern: mockDataView, 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 index e5e181ccfa266..26d9a227eb1c0 100644 --- 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 @@ -8,6 +8,7 @@ import { kea, MakeLogicType } from 'kea'; import { + DataView, IKibanaSearchRequest, IKibanaSearchResponse, isCompleteResponse, @@ -18,6 +19,7 @@ import { KibanaLogic } from '../../../shared/kibana/kibana_logic'; import { AnalyticsCollectionDataViewLogic, + AnalyticsCollectionDataViewLogicActions, AnalyticsCollectionDataViewLogicValues, } from './analytics_collection_data_view_logic'; @@ -32,9 +34,10 @@ import { ExploreTableItem, ExploreTables, SearchTermsTable, - TopClickedTable, - TopReferrersTable, + ClickedTable, + ReferrersTable, WorsePerformersTable, + LocationsTable, } from './analytics_collection_explore_table_types'; import { AnalyticsCollectionToolbarLogic, @@ -51,43 +54,50 @@ export interface Sorting { interface TableParams { parseResponse(response: IKibanaSearchResponse): { items: T[]; totalCount: number }; - requestParams(props: { - pageIndex: number; - pageSize: number; - search: string; - sorting: Sorting | null; - timeRange: TimeRange; - }): IKibanaSearchRequest; + requestParams( + dataView: DataView, + props: { + pageIndex: number; + pageSize: number; + search: string; + sorting: Sorting | null; + timeRange: TimeRange; + } + ): IKibanaSearchRequest; } const tablesParams: { + [ExploreTables.Clicked]: TableParams; + [ExploreTables.Locations]: TableParams; + [ExploreTables.Referrers]: TableParams; [ExploreTables.SearchTerms]: TableParams; - [ExploreTables.TopClicked]: TableParams; - [ExploreTables.TopReferrers]: TableParams; [ExploreTables.WorsePerformers]: TableParams; } = { [ExploreTables.SearchTerms]: { parseResponse: ( response: IKibanaSearchResponse<{ - aggregations: { + 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, + items: + response.rawResponse.aggregations?.searches.buckets.map((bucket) => ({ + [ExploreTableColumns.count]: bucket.doc_count, + [ExploreTableColumns.searchTerms]: bucket.key, + })) || [], + totalCount: response.rawResponse.aggregations?.totalCount.value || 0, }), requestParams: ( + dataView, { timeRange, sorting, pageIndex, pageSize, search }, aggregationFieldName = 'search.query' ) => getBaseSearchTemplate( + dataView, aggregationFieldName, - { search, timeRange, eventType: 'search' }, + { eventType: 'search', search, timeRange }, { searches: { terms: { @@ -109,7 +119,7 @@ const tablesParams: { [ExploreTables.WorsePerformers]: { parseResponse: ( response: IKibanaSearchResponse<{ - aggregations: { + aggregations?: { formula: { searches: { buckets: Array<{ doc_count: number; key: string }> }; totalCount: { value: number }; @@ -117,19 +127,22 @@ const tablesParams: { }; }> ) => ({ - 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, + 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 || 0, }), requestParams: ( + dataView, { timeRange, sorting, pageIndex, pageSize, search }, aggregationFieldName = 'search.query' ) => getBaseSearchTemplate( + dataView, aggregationFieldName, - { search, timeRange, eventType: 'search' }, + { eventType: 'search', search, timeRange }, { formula: { aggs: { @@ -153,7 +166,7 @@ const tablesParams: { } ), }, - [ExploreTables.TopClicked]: { + [ExploreTables.Clicked]: { parseResponse: ( response: IKibanaSearchResponse<{ aggregations: { @@ -164,19 +177,22 @@ const tablesParams: { }; }> ) => ({ - 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, + 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 || 0, }), requestParams: ( + dataView, { timeRange, sorting, pageIndex, pageSize, search }, aggregationFieldName = 'search.results.items.page.url' ) => getBaseSearchTemplate( + dataView, aggregationFieldName, - { search, timeRange, eventType: 'search_click' }, + { eventType: 'search_click', search, timeRange }, { formula: { aggs: { @@ -200,10 +216,10 @@ const tablesParams: { } ), }, - [ExploreTables.TopReferrers]: { + [ExploreTables.Referrers]: { parseResponse: ( response: IKibanaSearchResponse<{ - aggregations: { + aggregations?: { formula: { searches: { buckets: Array<{ doc_count: number; key: string }> }; totalCount: { value: number }; @@ -211,19 +227,22 @@ const tablesParams: { }; }> ) => ({ - 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, + 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 || 0, }), requestParams: ( + dataView, { timeRange, sorting, pageIndex, pageSize, search }, aggregationFieldName = 'page.referrer' ) => getBaseSearchTemplate( + dataView, aggregationFieldName, - { search, timeRange, eventType: 'page_view' }, + { eventType: 'page_view', search, timeRange }, { formula: { aggs: { @@ -247,6 +266,60 @@ const tablesParams: { } ), }, + [ExploreTables.Locations]: { + 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.location]: bucket.key[0], + countryISOCode: bucket.key[1], + })) || [], + totalCount: response.rawResponse.aggregations?.formula.totalCount.value || 0, + }), + requestParams: ( + dataView, + { timeRange, sorting, pageIndex, pageSize, search }, + aggregationFieldName = 'session.location.country_name' + ) => + getBaseSearchTemplate( + dataView, + aggregationFieldName, + { eventType: 'page_view', search, timeRange }, + { + formula: { + aggs: { + ...getTotalCountRequestParams(aggregationFieldName), + searches: { + multi_terms: { + ...getPaginationRequestSizeParams(pageIndex, pageSize), + order: sorting + ? { + [sorting?.field === ExploreTableColumns.sessions ? '_count' : '_key']: + sorting?.direction, + } + : undefined, + terms: [ + { field: aggregationFieldName }, + { field: 'session.location.country_iso_code' }, + ], + }, + ...getPaginationRequestParams(pageIndex, pageSize), + }, + }, + filter: { term: { 'event.action': 'page_view' } }, + }, + } + ), + }, }; export interface AnalyticsCollectionExploreTableLogicValues { @@ -269,6 +342,7 @@ export interface AnalyticsCollectionExploreTableLogicActions { sort?: Sorting; }; reset(): void; + setDataView: AnalyticsCollectionDataViewLogicActions['setDataView']; setItems(items: ExploreTableItem[]): { items: ExploreTableItem[] }; setSearch(search: string): { search: string }; setSelectedTable( @@ -293,7 +367,12 @@ export const AnalyticsCollectionExploreTableLogic = kea< setTotalItemsCount: (count) => ({ count }), }, connect: { - actions: [AnalyticsCollectionToolbarLogic, ['setTimeRange', 'setSearchSessionId']], + actions: [ + AnalyticsCollectionToolbarLogic, + ['setTimeRange', 'setSearchSessionId'], + AnalyticsCollectionDataViewLogic, + ['setDataView'], + ], values: [ AnalyticsCollectionDataViewLogic, ['dataView'], @@ -303,7 +382,11 @@ export const AnalyticsCollectionExploreTableLogic = kea< }, listeners: ({ actions, values }) => { const fetchItems = () => { - if (values.selectedTable === null || !(values.selectedTable in tablesParams)) { + if ( + values.selectedTable === null || + !(values.selectedTable in tablesParams) || + !values.dataView + ) { actions.setItems([]); actions.setTotalItemsCount(0); @@ -315,7 +398,7 @@ export const AnalyticsCollectionExploreTableLogic = kea< const search$ = KibanaLogic.values.data.search .search( - requestParams({ + requestParams(values.dataView, { pageIndex: values.pageIndex, pageSize: values.pageSize, search: values.search, @@ -323,7 +406,7 @@ export const AnalyticsCollectionExploreTableLogic = kea< timeRange, }), { - indexPattern: values.dataView || undefined, + indexPattern: values.dataView, sessionId: values.searchSessionId, } ) @@ -345,6 +428,7 @@ export const AnalyticsCollectionExploreTableLogic = kea< return { onTableChange: fetchItems, + setDataView: fetchItems, setSearch: async (_, breakpoint) => { await breakpoint(SEARCH_COOLDOWN); fetchItems(); diff --git a/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/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 index c9c6e4c3a0244..ffca2172440b3 100644 --- a/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/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 @@ -8,14 +8,16 @@ export enum ExploreTables { SearchTerms, WorsePerformers, - TopClicked, - TopReferrers, + Clicked, + Referrers, + Locations, } export enum ExploreTableColumns { count = 'count', searchTerms = 'searchTerms', query = 'query', + location = 'location', page = 'page', sessions = 'sessions', } @@ -30,18 +32,25 @@ export interface WorsePerformersTable { [ExploreTableColumns.query]: string; } -export interface TopClickedTable { +export interface ClickedTable { [ExploreTableColumns.count]: number; [ExploreTableColumns.page]: string; } -export interface TopReferrersTable { +export interface ReferrersTable { [ExploreTableColumns.page]: string; [ExploreTableColumns.sessions]: number; } +export interface LocationsTable { + [ExploreTableColumns.location]: string; + [ExploreTableColumns.sessions]: number; + countryISOCode: string; +} + export type ExploreTableItem = | SearchTermsTable | WorsePerformersTable - | TopClickedTable - | TopReferrersTable; + | ClickedTable + | ReferrersTable + | LocationsTable; 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 index fc702a0493369..3fc29c6d9e687 100644 --- 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 @@ -27,7 +27,7 @@ describe('AnalyticsCollectionExplorerTable', () => { beforeEach(() => { jest.clearAllMocks(); - setMockValues({ items: [], selectedTable: ExploreTables.TopClicked }); + setMockValues({ items: [], selectedTable: ExploreTables.Clicked }); setMockActions(mockActions); }); @@ -46,7 +46,7 @@ describe('AnalyticsCollectionExplorerTable', () => { it('should call setSelectedTable when click on a tab', () => { const tabs = shallow().find('EuiTab'); - expect(tabs.length).toBe(4); + expect(tabs.length).toBe(5); tabs.at(2).simulate('click'); expect(mockActions.setSelectedTable).toHaveBeenCalledWith(ExploreTables.WorsePerformers, { 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 index cc104fe93b7ba..35cf7afbd1243 100644 --- 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 @@ -32,15 +32,17 @@ import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n-react'; +import { getFlag } from '../../../utils/get_flag'; import { AnalyticsCollectionExploreTableLogic } from '../analytics_collection_explore_table_logic'; import { ExploreTableColumns, ExploreTableItem, ExploreTables, SearchTermsTable, - TopClickedTable, - TopReferrersTable, + ClickedTable, + ReferrersTable, WorsePerformersTable, + LocationsTable, } from '../analytics_collection_explore_table_types'; import { AnalyticsCollectionExplorerCallout } from './analytics_collection_explorer_callout'; @@ -63,7 +65,7 @@ const tabs: Array<{ id: ExploreTables; name: string }> = [ ), }, { - id: ExploreTables.TopClicked, + id: ExploreTables.Clicked, name: i18n.translate( 'xpack.enterpriseSearch.analytics.collections.collectionsView.explorer.topClickedTab', { defaultMessage: 'Top clicked results' } @@ -77,7 +79,14 @@ const tabs: Array<{ id: ExploreTables; name: string }> = [ ), }, { - id: ExploreTables.TopReferrers, + id: ExploreTables.Locations, + name: i18n.translate( + 'xpack.enterpriseSearch.analytics.collections.collectionsView.explorer.locationsTab', + { defaultMessage: 'Locations' } + ), + }, + { + id: ExploreTables.Referrers, name: i18n.translate( 'xpack.enterpriseSearch.analytics.collections.collectionsView.explorer.referrersTab', { defaultMessage: 'Referrers' } @@ -86,9 +95,10 @@ const tabs: Array<{ id: ExploreTables; name: string }> = [ ]; const tableSettings: { + [ExploreTables.Clicked]: TableSetting; + [ExploreTables.Locations]: TableSetting; + [ExploreTables.Referrers]: TableSetting; [ExploreTables.SearchTerms]: TableSetting; - [ExploreTables.TopClicked]: TableSetting; - [ExploreTables.TopReferrers]: TableSetting; [ExploreTables.WorsePerformers]: TableSetting; } = { [ExploreTables.SearchTerms]: { @@ -149,7 +159,7 @@ const tableSettings: { }, }, }, - [ExploreTables.TopClicked]: { + [ExploreTables.Clicked]: { columns: [ { field: ExploreTableColumns.page, @@ -184,7 +194,7 @@ const tableSettings: { }, }, }, - [ExploreTables.TopReferrers]: { + [ExploreTables.Referrers]: { columns: [ { field: ExploreTableColumns.page, @@ -219,6 +229,46 @@ const tableSettings: { }, }, }, + [ExploreTables.Locations]: { + columns: [ + { + field: ExploreTableColumns.location, + name: i18n.translate( + 'xpack.enterpriseSearch.analytics.collections.collectionsView.exploreTable.location', + { defaultMessage: 'Location' } + ), + render: (euiTheme: UseEuiTheme['euiTheme']) => (value: string, data: LocationsTable) => + ( + + +

{getFlag(data.countryISOCode)}

+
+ +

{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 = () => { diff --git a/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/analytics_collection_overview/analytics_collection_overview_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 index e6e553dc51792..60d50e28fa802 100644 --- a/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/analytics_collection_overview/analytics_collection_overview_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 @@ -48,7 +48,7 @@ describe('AnalyticsCollectionOverviewTable', () => { topReferrersTab.simulate('click'); expect(mockActions.setSelectedTable).toHaveBeenCalledTimes(1); - expect(mockActions.setSelectedTable).toHaveBeenCalledWith(ExploreTables.TopReferrers, { + expect(mockActions.setSelectedTable).toHaveBeenCalledWith(ExploreTables.Locations, { direction: 'desc', field: 'sessions', }); diff --git a/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/analytics_collection_overview/analytics_collection_overview_table.tsx b/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/analytics_collection_overview/analytics_collection_overview_table.tsx index 8538b11f748fe..3bad1189a0181 100644 --- a/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/analytics_collection_overview/analytics_collection_overview_table.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/analytics_collection_overview/analytics_collection_overview_table.tsx @@ -25,6 +25,7 @@ import { 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'; @@ -32,6 +33,7 @@ import { generateEncodedPath } from '../../../../shared/encode_path_params'; import { KibanaLogic } from '../../../../shared/kibana'; import { COLLECTION_EXPLORER_PATH } from '../../../routes'; +import { getFlag } from '../../../utils/get_flag'; import { FilterBy } from '../../../utils/get_formula_by_filter'; import { AnalyticsCollectionExploreTableLogic } from '../analytics_collection_explore_table_logic'; @@ -40,9 +42,10 @@ import { ExploreTableItem, ExploreTables, SearchTermsTable, - TopClickedTable, - TopReferrersTable, + ClickedTable, + ReferrersTable, WorsePerformersTable, + LocationsTable, } from '../analytics_collection_explore_table_types'; import { FetchAnalyticsCollectionLogic } from '../fetch_analytics_collection_logic'; @@ -67,7 +70,7 @@ const tabsByFilter: Record> ], [FilterBy.Clicks]: [ { - id: ExploreTables.TopClicked, + id: ExploreTables.Clicked, name: i18n.translate( 'xpack.enterpriseSearch.analytics.collections.collectionsView.exploreTab.topClicked', { defaultMessage: 'Top clicked results' } @@ -76,7 +79,14 @@ const tabsByFilter: Record> ], [FilterBy.Sessions]: [ { - id: ExploreTables.TopReferrers, + id: ExploreTables.Locations, + name: i18n.translate( + 'xpack.enterpriseSearch.analytics.collections.collectionsView.exploreTab.topLocations', + { defaultMessage: 'Top locations' } + ), + }, + { + id: ExploreTables.Referrers, name: i18n.translate( 'xpack.enterpriseSearch.analytics.collections.collectionsView.exploreTab.topReferrers', { defaultMessage: 'Top referrers' } @@ -95,9 +105,10 @@ interface TableSetting { } const tableSettings: { + [ExploreTables.Clicked]: TableSetting; + [ExploreTables.Locations]: TableSetting; + [ExploreTables.Referrers]: TableSetting; [ExploreTables.SearchTerms]: TableSetting; - [ExploreTables.TopClicked]: TableSetting; - [ExploreTables.TopReferrers]: TableSetting; [ExploreTables.WorsePerformers]: TableSetting; } = { [ExploreTables.SearchTerms]: { @@ -158,7 +169,7 @@ const tableSettings: { }, }, }, - [ExploreTables.TopClicked]: { + [ExploreTables.Clicked]: { columns: [ { field: ExploreTableColumns.page, @@ -193,7 +204,7 @@ const tableSettings: { }, }, }, - [ExploreTables.TopReferrers]: { + [ExploreTables.Referrers]: { columns: [ { field: ExploreTableColumns.page, @@ -228,6 +239,46 @@ const tableSettings: { }, }, }, + [ExploreTables.Locations]: { + columns: [ + { + field: ExploreTableColumns.location, + name: i18n.translate( + 'xpack.enterpriseSearch.analytics.collections.collectionsView.exploreTable.location', + { defaultMessage: 'Location' } + ), + render: (euiTheme: UseEuiTheme['euiTheme']) => (value: string, data: LocationsTable) => + ( + + +

{getFlag(data.countryISOCode)}

+
+ +

{value}

+
+
+ ), + truncateText: true, + }, + { + align: 'right', + field: ExploreTableColumns.sessions, + name: i18n.translate( + 'xpack.enterpriseSearch.analytics.collections.collectionsView.exploreTable.session', + { defaultMessage: 'Session' } + ), + sortable: true, + truncateText: true, + }, + ], + sorting: { + readOnly: true, + sort: { + direction: 'desc', + field: ExploreTableColumns.sessions, + }, + }, + }, }; interface AnalyticsCollectionOverviewTableProps { diff --git a/x-pack/plugins/enterprise_search/public/applications/analytics/utils/get_flag.ts b/x-pack/plugins/enterprise_search/public/applications/analytics/utils/get_flag.ts new file mode 100644 index 0000000000000..d82eeb27cfbd6 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/analytics/utils/get_flag.ts @@ -0,0 +1,13 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export const getFlag = (countryCode: string): string | null => + countryCode && countryCode.length === 2 + ? countryCode + .toUpperCase() + .replace(/./g, (c) => String.fromCharCode(55356, 56741 + c.charCodeAt(0))) + : null; From 5f5aba3981b7ff4a8d7733e9d3515193193308ee Mon Sep 17 00:00:00 2001 From: Shahzad Date: Wed, 26 Apr 2023 15:33:49 +0200 Subject: [PATCH 11/73] [Synthetics] Apply status up/down and disabled filter to overview alerts/errors (#155824) --- .../synthetics_overview_status.ts | 1 + .../overview/overview/overview_alerts.tsx | 41 +++++++++++++++---- .../overview_errors/overview_errors.tsx | 13 +++--- .../status_rule/status_rule_executor.ts | 3 +- .../server/queries/query_monitor_status.ts | 1 + .../routes/overview_status/overview_status.ts | 2 + .../get_all_monitors.test.ts | 3 ++ .../synthetics_monitor/get_all_monitors.ts | 3 ++ 8 files changed, 52 insertions(+), 15 deletions(-) diff --git a/x-pack/plugins/synthetics/common/runtime_types/monitor_management/synthetics_overview_status.ts b/x-pack/plugins/synthetics/common/runtime_types/monitor_management/synthetics_overview_status.ts index ca9c85fb1a481..f89688b36fee4 100644 --- a/x-pack/plugins/synthetics/common/runtime_types/monitor_management/synthetics_overview_status.ts +++ b/x-pack/plugins/synthetics/common/runtime_types/monitor_management/synthetics_overview_status.ts @@ -59,6 +59,7 @@ export const OverviewStatusCodec = t.interface({ downConfigs: t.record(t.string, OverviewStatusMetaDataCodec), pendingConfigs: t.record(t.string, OverviewPendingStatusMetaDataCodec), enabledMonitorQueryIds: t.array(t.string), + disabledMonitorQueryIds: t.array(t.string), allIds: t.array(t.string), }); diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/overview/overview/overview_alerts.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/overview/overview/overview_alerts.tsx index 693cd0d9ed85b..7d8f6268f48fb 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/overview/overview/overview_alerts.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/overview/overview/overview_alerts.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import React from 'react'; +import React, { useMemo } from 'react'; import { EuiFlexGroup, EuiFlexItem, @@ -24,6 +24,33 @@ import { AlertsLink } from '../../../common/links/view_alerts'; import { useRefreshedRange, useGetUrlParams } from '../../../../hooks'; import { ClientPluginsStart } from '../../../../../../plugin'; +export const useMonitorQueryIds = () => { + const { status } = useSelector(selectOverviewStatus); + + const { statusFilter } = useGetUrlParams(); + return useMemo(() => { + let monitorIds = status?.enabledMonitorQueryIds ?? []; + switch (statusFilter) { + case 'up': + monitorIds = status + ? Object.entries(status.upConfigs).map(([id, config]) => config.monitorQueryId) + : []; + break; + case 'down': + monitorIds = status + ? Object.entries(status.downConfigs).map(([id, config]) => config.monitorQueryId) + : []; + break; + case 'disabled': + monitorIds = status?.disabledMonitorQueryIds ?? []; + break; + default: + break; + } + return monitorIds.length > 0 ? monitorIds : ['false-id']; + }, [status, statusFilter]); +}; + export const OverviewAlerts = () => { const { from, to } = useRefreshedRange(12, 'hours'); @@ -39,6 +66,8 @@ export const OverviewAlerts = () => { const loading = !status?.allIds || status?.allIds.length === 0; + const monitorIds = useMonitorQueryIds(); + return ( @@ -66,10 +95,7 @@ export const OverviewAlerts = () => { selectedMetricField: RECORDS_FIELD, reportDefinitions: { 'kibana.alert.rule.category': ['Synthetics monitor status'], - 'monitor.id': - status?.enabledMonitorQueryIds.length > 0 - ? status?.enabledMonitorQueryIds - : ['false-id'], + 'monitor.id': monitorIds, ...(locations?.length ? { 'observer.geo.name': locations } : {}), }, filters: [{ field: 'kibana.alert.status', values: ['active', 'recovered'] }], @@ -93,10 +119,7 @@ export const OverviewAlerts = () => { }, reportDefinitions: { 'kibana.alert.rule.category': ['Synthetics monitor status'], - 'monitor.id': - status?.enabledMonitorQueryIds.length > 0 - ? status?.enabledMonitorQueryIds - : ['false-id'], + 'monitor.id': monitorIds, ...(locations?.length ? { 'observer.geo.name': locations } : {}), }, dataType: 'alerts', diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/overview/overview/overview_errors/overview_errors.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/overview/overview/overview_errors/overview_errors.tsx index 8f85b8bd90d43..ea4b0f8282ebf 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/overview/overview/overview_errors/overview_errors.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/overview/overview/overview_errors/overview_errors.tsx @@ -16,6 +16,7 @@ import { import React from 'react'; import { useSelector } from 'react-redux'; import { i18n } from '@kbn/i18n'; +import { useMonitorQueryIds } from '../overview_alerts'; import { selectOverviewStatus } from '../../../../../state/overview_status'; import { OverviewErrorsSparklines } from './overview_errors_sparklines'; import { useRefreshedRange, useGetUrlParams } from '../../../../../hooks'; @@ -28,7 +29,9 @@ export function OverviewErrors() { const { from, to } = useRefreshedRange(6, 'hours'); - const params = useGetUrlParams(); + const { locations } = useGetUrlParams(); + + const monitorIds = useMonitorQueryIds(); return ( @@ -44,16 +47,16 @@ export function OverviewErrors() {
diff --git a/x-pack/plugins/synthetics/server/alert_rules/status_rule/status_rule_executor.ts b/x-pack/plugins/synthetics/server/alert_rules/status_rule/status_rule_executor.ts index 923177dc6b377..3b73245977dff 100644 --- a/x-pack/plugins/synthetics/server/alert_rules/status_rule/status_rule_executor.ts +++ b/x-pack/plugins/synthetics/server/alert_rules/status_rule/status_rule_executor.ts @@ -34,7 +34,8 @@ export interface StaleDownConfig extends OverviewStatusMetaData { isLocationRemoved?: boolean; } -export interface AlertOverviewStatus extends Omit { +export interface AlertOverviewStatus + extends Omit { staleDownConfigs: Record; } diff --git a/x-pack/plugins/synthetics/server/queries/query_monitor_status.ts b/x-pack/plugins/synthetics/server/queries/query_monitor_status.ts index a737e80e08069..dff791a6b535a 100644 --- a/x-pack/plugins/synthetics/server/queries/query_monitor_status.ts +++ b/x-pack/plugins/synthetics/server/queries/query_monitor_status.ts @@ -46,6 +46,7 @@ export async function queryMonitorStatus( | 'allMonitorsCount' | 'disabledMonitorsCount' | 'projectMonitorsCount' + | 'disabledMonitorQueryIds' | 'allIds' > > { 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 f8c55ba4fae39..35672937c0ffd 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 @@ -70,6 +70,7 @@ export async function getStatus(context: RouteContext, params: OverviewStatusQue const { enabledMonitorQueryIds, + disabledMonitorQueryIds, allIds, disabledCount, maxPeriod, @@ -112,6 +113,7 @@ export async function getStatus(context: RouteContext, params: OverviewStatusQue disabledMonitorsCount, projectMonitorsCount, enabledMonitorQueryIds, + disabledMonitorQueryIds, disabledCount, up, down, diff --git a/x-pack/plugins/synthetics/server/saved_objects/synthetics_monitor/get_all_monitors.test.ts b/x-pack/plugins/synthetics/server/saved_objects/synthetics_monitor/get_all_monitors.test.ts index 2c72ac660a588..8850f3b32c8df 100644 --- a/x-pack/plugins/synthetics/server/saved_objects/synthetics_monitor/get_all_monitors.test.ts +++ b/x-pack/plugins/synthetics/server/saved_objects/synthetics_monitor/get_all_monitors.test.ts @@ -59,6 +59,7 @@ describe('processMonitors', () => { 'aa925d91-40b0-4f8f-b695-bb9b53cd4e22', '7f796001-a795-4c0b-afdb-3ce74edea775', ], + disabledMonitorQueryIds: ['test-project-id-default'], listOfLocations: ['US Central QA', 'US Central Staging', 'North America - US Central'], maxPeriod: 600000, monitorLocationMap: { @@ -94,6 +95,7 @@ describe('processMonitors', () => { 'aa925d91-40b0-4f8f-b695-bb9b53cd4e22', '7f796001-a795-4c0b-afdb-3ce74edea775', ], + disabledMonitorQueryIds: ['test-project-id-default'], listOfLocations: [ 'US Central Staging', 'us_central_qa', @@ -172,6 +174,7 @@ describe('processMonitors', () => { 'aa925d91-40b0-4f8f-b695-bb9b53cd4e22', '7f796001-a795-4c0b-afdb-3ce74edea775', ], + disabledMonitorQueryIds: ['test-project-id-default'], listOfLocations: ['US Central Staging', 'US Central QA', 'North America - US Central'], maxPeriod: 600000, monitorLocationMap: { diff --git a/x-pack/plugins/synthetics/server/saved_objects/synthetics_monitor/get_all_monitors.ts b/x-pack/plugins/synthetics/server/saved_objects/synthetics_monitor/get_all_monitors.ts index 493c3a2889bdf..7670a742fa98a 100644 --- a/x-pack/plugins/synthetics/server/saved_objects/synthetics_monitor/get_all_monitors.ts +++ b/x-pack/plugins/synthetics/server/saved_objects/synthetics_monitor/get_all_monitors.ts @@ -73,6 +73,7 @@ export const processMonitors = async ( * latest ping for all enabled monitors. */ const enabledMonitorQueryIds: string[] = []; + const disabledMonitorQueryIds: string[] = []; let disabledCount = 0; let disabledMonitorsCount = 0; let maxPeriod = 0; @@ -116,6 +117,7 @@ export const processMonitors = async ( ); disabledCount += intersectingLocations.length; disabledMonitorsCount += 1; + disabledMonitorQueryIds.push(attrs[ConfigKey.MONITOR_QUERY_ID]); } else { const missingLabels = new Set(); @@ -152,6 +154,7 @@ export const processMonitors = async ( maxPeriod, allIds, enabledMonitorQueryIds, + disabledMonitorQueryIds, disabledCount, monitorLocationMap, disabledMonitorsCount, From fda5ee96b37f186378d94a7b6a15b295d9616168 Mon Sep 17 00:00:00 2001 From: Tomasz Ciecierski Date: Wed, 26 Apr 2023 15:34:06 +0200 Subject: [PATCH 12/73] [Defend Workflows] Osquery fixes (#155020) --- .../public/live_queries/form/index.tsx | 20 ++++++------------- x-pack/plugins/osquery/server/common/error.ts | 15 ++++++++++++++ x-pack/plugins/osquery/server/common/types.ts | 4 ++++ .../handlers/action/create_action_handler.ts | 3 ++- .../osquery/server/lib/fleet_integration.ts | 13 ++++++++++-- .../live_query/create_live_query_route.ts | 5 +++-- 6 files changed, 41 insertions(+), 19 deletions(-) create mode 100644 x-pack/plugins/osquery/server/common/error.ts diff --git a/x-pack/plugins/osquery/public/live_queries/form/index.tsx b/x-pack/plugins/osquery/public/live_queries/form/index.tsx index 7868c1bb3a471..d56b981b128c8 100644 --- a/x-pack/plugins/osquery/public/live_queries/form/index.tsx +++ b/x-pack/plugins/osquery/public/live_queries/form/index.tsx @@ -82,16 +82,8 @@ const LiveQueryFormComponent: React.FC = ({ ); const hooksForm = useHookForm(); - const { - handleSubmit, - watch, - setValue, - resetField, - clearErrors, - getFieldState, - register, - formState: { isSubmitting }, - } = hooksForm; + const { handleSubmit, watch, setValue, resetField, clearErrors, getFieldState, register } = + hooksForm; const canRunSingleQuery = useMemo( () => @@ -157,7 +149,7 @@ const LiveQueryFormComponent: React.FC = ({ saved_query_id: values.savedQueryId, query, alert_ids: values.alertIds, - pack_id: values?.packId?.length ? values?.packId[0] : undefined, + pack_id: queryType === 'pack' && values?.packId?.length ? values?.packId[0] : undefined, ecs_mapping: values.ecs_mapping, }, (value) => !isEmpty(value) @@ -165,7 +157,7 @@ const LiveQueryFormComponent: React.FC = ({ await mutateAsync(serializedData); }, - [alertAttachmentContext, mutateAsync] + [alertAttachmentContext, mutateAsync, queryType] ); const serializedData: SavedQuerySOFormData = useMemo( @@ -196,7 +188,7 @@ const LiveQueryFormComponent: React.FC = ({ = ({ resultsStatus, handleShowSaveQueryFlyout, enabled, - isSubmitting, + isLoading, handleSubmit, onSubmit, ] diff --git a/x-pack/plugins/osquery/server/common/error.ts b/x-pack/plugins/osquery/server/common/error.ts new file mode 100644 index 0000000000000..b48fd925dad62 --- /dev/null +++ b/x-pack/plugins/osquery/server/common/error.ts @@ -0,0 +1,15 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export class CustomHttpRequestError extends Error { + constructor(message: string, public readonly statusCode: number = 500) { + super(message); + // For debugging - capture name of subclasses + this.name = this.constructor.name; + this.message = message; + } +} diff --git a/x-pack/plugins/osquery/server/common/types.ts b/x-pack/plugins/osquery/server/common/types.ts index 522f1fa250ada..51dc4f59ed5b4 100644 --- a/x-pack/plugins/osquery/server/common/types.ts +++ b/x-pack/plugins/osquery/server/common/types.ts @@ -56,3 +56,7 @@ export interface SavedQuerySavedObjectAttributes { } export type SavedQuerySavedObject = SavedObject; + +export interface HTTPError extends Error { + statusCode: number; +} diff --git a/x-pack/plugins/osquery/server/handlers/action/create_action_handler.ts b/x-pack/plugins/osquery/server/handlers/action/create_action_handler.ts index b2f6ca09234eb..3c776723a2da2 100644 --- a/x-pack/plugins/osquery/server/handlers/action/create_action_handler.ts +++ b/x-pack/plugins/osquery/server/handlers/action/create_action_handler.ts @@ -21,6 +21,7 @@ import { convertSOQueriesToPack } from '../../routes/pack/utils'; import { ACTIONS_INDEX } from '../../../common/constants'; import { TELEMETRY_EBT_LIVE_QUERY_EVENT } from '../../lib/telemetry/constants'; import type { PackSavedObjectAttributes } from '../../common/types'; +import { CustomHttpRequestError } from '../../common/error'; interface Metadata { currentUser: string | undefined; @@ -55,7 +56,7 @@ export const createActionHandler = async ( }); if (!selectedAgents.length) { - throw new Error('No agents found for selection'); + throw new CustomHttpRequestError('No agents found for selection', 400); } let packSO; diff --git a/x-pack/plugins/osquery/server/lib/fleet_integration.ts b/x-pack/plugins/osquery/server/lib/fleet_integration.ts index f03afedc8628a..684334c1488b4 100644 --- a/x-pack/plugins/osquery/server/lib/fleet_integration.ts +++ b/x-pack/plugins/osquery/server/lib/fleet_integration.ts @@ -34,11 +34,20 @@ export const getPackagePolicyDeleteCallback = await Promise.all( map( foundPacks.saved_objects, - (pack: { id: string; references: SavedObjectReference[] }) => + (pack: { + id: string; + references: SavedObjectReference[]; + attributes: { shards: Array<{ key: string; value: string }> }; + }) => packsClient.update( packSavedObjectType, pack.id, - {}, + { + shards: filter( + pack.attributes.shards, + (shard) => shard.key !== deletedOsqueryManagerPolicy.policy_id + ), + }, { references: filter( pack.references, diff --git a/x-pack/plugins/osquery/server/routes/live_query/create_live_query_route.ts b/x-pack/plugins/osquery/server/routes/live_query/create_live_query_route.ts index 9d7ad88da88b6..05f857e320066 100644 --- a/x-pack/plugins/osquery/server/routes/live_query/create_live_query_route.ts +++ b/x-pack/plugins/osquery/server/routes/live_query/create_live_query_route.ts @@ -113,8 +113,9 @@ export const createLiveQueryRoute = (router: IRouter, osqueryContext: OsqueryApp body: { data: osqueryAction }, }); } catch (error) { - // TODO validate for 400 (when agents are not found for selection) - // return response.badRequest({ body: new Error('No agents found for selection') }); + if (error.statusCode === 400) { + return response.badRequest({ body: error }); + } return response.customError({ statusCode: 500, From a3f66bdacf3ea2e72a44cbf0ef177e461e218b87 Mon Sep 17 00:00:00 2001 From: Ying Mao Date: Wed, 26 Apr 2023 09:40:27 -0400 Subject: [PATCH 13/73] [Response Ops][Alerting] Adding functional tests for managing alerting rules when authenticated with an API key (#155787) Resolves https://github.com/elastic/kibana/issues/154584 --- .../group3/tests/alerting/index.ts | 1 + .../tests/alerting/user_managed_api_key.ts | 636 ++++++++++++++++++ 2 files changed, 637 insertions(+) create mode 100644 x-pack/test/alerting_api_integration/security_and_spaces/group3/tests/alerting/user_managed_api_key.ts diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/group3/tests/alerting/index.ts b/x-pack/test/alerting_api_integration/security_and_spaces/group3/tests/alerting/index.ts index 8f6cbe1a60c89..dcefc0a9b0239 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/group3/tests/alerting/index.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/group3/tests/alerting/index.ts @@ -28,6 +28,7 @@ export default function alertingTests({ loadTestFile, getService }: FtrProviderC loadTestFile(require.resolve('./get_flapping_settings')); loadTestFile(require.resolve('./run_soon')); loadTestFile(require.resolve('./update_flapping_settings')); + loadTestFile(require.resolve('./user_managed_api_key')); }); }); } diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/group3/tests/alerting/user_managed_api_key.ts b/x-pack/test/alerting_api_integration/security_and_spaces/group3/tests/alerting/user_managed_api_key.ts new file mode 100644 index 0000000000000..7a92b9e11d859 --- /dev/null +++ b/x-pack/test/alerting_api_integration/security_and_spaces/group3/tests/alerting/user_managed_api_key.ts @@ -0,0 +1,636 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license 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 { generateAPIKeyName } from '@kbn/alerting-plugin/server/rules_client/common'; +import { IValidatedEvent } from '@kbn/event-log-plugin/server'; +import { + checkAAD, + getEventLog, + getTestRuleData, + getUrlPrefix, + ObjectRemover, +} from '../../../../common/lib'; +import { FtrProviderContext } from '../../../../common/ftr_provider_context'; +import { SuperuserAtSpace1 } from '../../../scenarios'; + +// eslint-disable-next-line import/no-default-export +export default function userManagedApiKeyTest({ getService }: FtrProviderContext) { + const supertest = getService('supertest'); + const es = getService('es'); + const superTestWithoutAuth = getService('supertestWithoutAuth'); + const objectRemover = new ObjectRemover(supertest); + const retry = getService('retry'); + + describe('user managed api key', () => { + let apiKey: string; + + before(async () => { + // Create API key + const { body: createdApiKey } = await supertest + .post(`/internal/security/api_key`) + .set('kbn-xsrf', 'foo') + .send({ name: 'test user managed key' }) + .expect(200); + + apiKey = createdApiKey.encoded; + }); + + after(() => objectRemover.removeAll()); + + it('should successfully create rule using API key authorization', async () => { + const testRuleData = getTestRuleData({}); + const response = await superTestWithoutAuth + .post(`${getUrlPrefix(SuperuserAtSpace1.space.id)}/api/alerting/rule`) + .set('kbn-xsrf', 'foo') + .set('Authorization', `ApiKey ${apiKey}`) + .send(testRuleData); + + expect(response.status).to.eql(200); + const ruleId = response.body.id; + objectRemover.add(SuperuserAtSpace1.space.id, ruleId, 'rule', 'alerting'); + + expect(response.body.api_key_created_by_user).to.eql(true); + expect(apiKeyExists(testRuleData.rule_type_id, testRuleData.name)).to.eql(false); + + // Make sure rule runs successfully + const events = await retry.try(async () => { + return await getEventLog({ + getService, + spaceId: SuperuserAtSpace1.space.id, + type: 'alert', + id: ruleId, + provider: 'alerting', + actions: new Map([ + // make sure the counts of the # of events per type are as expected + ['execute', { gte: 1 }], + ]), + }); + }); + + const executeEvent = events.find( + (event: IValidatedEvent) => event?.event?.action === 'execute' + ); + expect(executeEvent?.event?.outcome).to.eql('success'); + }); + + describe('rule operations', () => { + it('should successfully update rule with user managed API key', async () => { + const ruleId = await createRule(apiKey, 'test_update1'); + objectRemover.add(SuperuserAtSpace1.space.id, ruleId, 'rule', 'alerting'); + const updatedData = { + name: 'updated_rule_user_managed', + tags: ['bar'], + params: { + foo: true, + }, + schedule: { interval: '12s' }, + actions: [], + throttle: '1m', + notify_when: 'onThrottleInterval', + }; + + const response = await superTestWithoutAuth + .put(`${getUrlPrefix(SuperuserAtSpace1.space.id)}/api/alerting/rule/${ruleId}`) + .set('kbn-xsrf', 'foo') + .set('Authorization', `ApiKey ${apiKey}`) + .send(updatedData); + + expect(response.status).to.eql(200); + expect(response.body).to.eql({ + ...updatedData, + id: ruleId, + rule_type_id: 'test.noop', + running: false, + consumer: 'alertsFixture', + created_by: 'elastic', + enabled: true, + updated_by: 'elastic', + api_key_owner: 'elastic', + api_key_created_by_user: true, + mute_all: false, + muted_alert_ids: [], + actions: [], + scheduled_task_id: ruleId, + created_at: response.body.created_at, + updated_at: response.body.updated_at, + execution_status: response.body.execution_status, + revision: 1, + ...(response.body.next_run ? { next_run: response.body.next_run } : {}), + ...(response.body.last_run ? { last_run: response.body.last_run } : {}), + }); + + // Ensure AAD isn't broken + await checkAAD({ + supertest, + spaceId: SuperuserAtSpace1.space.id, + type: 'alert', + id: ruleId, + }); + + // Ensure no API key was generated + expect(apiKeyExists('test.noop', updatedData.name)).to.eql(false); + }); + + it('should successfully update rule and regenerate API key', async () => { + const ruleId = await createRule(apiKey, 'test_update2'); + objectRemover.add(SuperuserAtSpace1.space.id, ruleId, 'rule', 'alerting'); + const updatedData = { + name: 'update_rule_regenerated', + tags: ['bar'], + params: { + foo: true, + }, + schedule: { interval: '12s' }, + actions: [], + throttle: '1m', + notify_when: 'onThrottleInterval', + }; + + const response = await supertest + .put(`${getUrlPrefix(SuperuserAtSpace1.space.id)}/api/alerting/rule/${ruleId}`) + .set('kbn-xsrf', 'foo') + .send(updatedData); + + expect(response.status).to.eql(200); + expect(response.body).to.eql({ + ...updatedData, + id: ruleId, + rule_type_id: 'test.noop', + running: false, + consumer: 'alertsFixture', + created_by: 'elastic', + enabled: true, + updated_by: 'elastic', + api_key_owner: 'elastic', + api_key_created_by_user: false, + mute_all: false, + muted_alert_ids: [], + actions: [], + scheduled_task_id: ruleId, + created_at: response.body.created_at, + updated_at: response.body.updated_at, + execution_status: response.body.execution_status, + revision: 1, + ...(response.body.next_run ? { next_run: response.body.next_run } : {}), + ...(response.body.last_run ? { last_run: response.body.last_run } : {}), + }); + + // Ensure AAD isn't broken + await checkAAD({ + supertest, + spaceId: SuperuserAtSpace1.space.id, + type: 'alert', + id: ruleId, + }); + + // Ensure an API key was generated + expect(apiKeyExists('test.noop', updatedData.name)).to.eql(true); + }); + + it('should successfully clone rule with user managed API key', async () => { + const ruleId = await createRule(apiKey, 'test_clone1'); + objectRemover.add(SuperuserAtSpace1.space.id, ruleId, 'rule', 'alerting'); + const response = await superTestWithoutAuth + .post( + `${getUrlPrefix(SuperuserAtSpace1.space.id)}/internal/alerting/rule/${ruleId}/_clone` + ) + .set('kbn-xsrf', 'foo') + .set('Authorization', `ApiKey ${apiKey}`) + .send(); + expect(response.status).to.eql(200); + objectRemover.add(SuperuserAtSpace1.space.id, response.body.id, 'rule', 'alerting'); + + expect(response.body).to.eql({ + id: response.body.id, + name: 'test_clone1 [Clone]', + tags: ['foo'], + actions: [], + enabled: true, + rule_type_id: 'test.noop', + running: false, + consumer: 'alertsFixture', + params: {}, + created_by: 'elastic', + schedule: { interval: '1m' }, + scheduled_task_id: response.body.scheduled_task_id, + created_at: response.body.created_at, + updated_at: response.body.updated_at, + throttle: '1m', + notify_when: 'onThrottleInterval', + updated_by: 'elastic', + api_key_created_by_user: true, + api_key_owner: 'elastic', + mute_all: false, + muted_alert_ids: [], + execution_status: response.body.execution_status, + revision: 0, + last_run: { + alerts_count: { + active: 0, + ignored: 0, + new: 0, + recovered: 0, + }, + outcome: 'succeeded', + outcome_msg: null, + outcome_order: 0, + warning: null, + }, + next_run: response.body.next_run, + }); + + // Ensure AAD isn't broken + await checkAAD({ + supertest, + spaceId: SuperuserAtSpace1.space.id, + type: 'alert', + id: response.body.id, + }); + + // Ensure no API key was generated + expect(apiKeyExists(response.body.rule_type_id, response.body.name)).to.eql(false); + }); + + it('should successfully clone rule and regenerate API key', async () => { + const ruleId = await createRule(apiKey, 'test_clone2'); + objectRemover.add(SuperuserAtSpace1.space.id, ruleId, 'rule', 'alerting'); + const response = await supertest + .post( + `${getUrlPrefix(SuperuserAtSpace1.space.id)}/internal/alerting/rule/${ruleId}/_clone` + ) + .set('kbn-xsrf', 'foo') + .send(); + expect(response.status).to.eql(200); + objectRemover.add(SuperuserAtSpace1.space.id, response.body.id, 'rule', 'alerting'); + + expect(response.body).to.eql({ + id: response.body.id, + name: 'test_clone2 [Clone]', + tags: ['foo'], + actions: [], + enabled: true, + rule_type_id: 'test.noop', + running: false, + consumer: 'alertsFixture', + params: {}, + created_by: 'elastic', + schedule: { interval: '1m' }, + scheduled_task_id: response.body.scheduled_task_id, + created_at: response.body.created_at, + updated_at: response.body.updated_at, + throttle: '1m', + notify_when: 'onThrottleInterval', + updated_by: 'elastic', + api_key_created_by_user: false, + api_key_owner: 'elastic', + mute_all: false, + muted_alert_ids: [], + execution_status: response.body.execution_status, + revision: 0, + last_run: { + alerts_count: { + active: 0, + ignored: 0, + new: 0, + recovered: 0, + }, + outcome: 'succeeded', + outcome_msg: null, + outcome_order: 0, + warning: null, + }, + next_run: response.body.next_run, + }); + + // Ensure AAD isn't broken + await checkAAD({ + supertest, + spaceId: SuperuserAtSpace1.space.id, + type: 'alert', + id: response.body.id, + }); + + // Ensure an API key was generated + expect(apiKeyExists(response.body.rule_type_id, response.body.name)).to.eql(true); + }); + + it('should successfully bulk edit rule with user managed API key', async () => { + const ruleId = await createRule(apiKey, 'test_bulk_edit1'); + objectRemover.add(SuperuserAtSpace1.space.id, ruleId, 'rule', 'alerting'); + const payload = { + ids: [ruleId], + operations: [ + { + operation: 'add', + field: 'tags', + value: ['another-tag'], + }, + ], + }; + + const response = await superTestWithoutAuth + .post(`${getUrlPrefix(SuperuserAtSpace1.space.id)}/internal/alerting/rules/_bulk_edit`) + .set('kbn-xsrf', 'foo') + .set('Authorization', `ApiKey ${apiKey}`) + .send(payload); + + expect(response.status).to.eql(200); + expect(response.body.rules[0].tags).to.eql(['foo', 'another-tag']); + expect(response.body.rules[0].api_key_created_by_user).to.eql(true); + + // Ensure AAD isn't broken + await checkAAD({ + supertest, + spaceId: SuperuserAtSpace1.space.id, + type: 'alert', + id: ruleId, + }); + + // Ensure no API key was generated + expect(apiKeyExists('test.noop', 'test_bulk_edit1')).to.eql(false); + }); + + it('should successfully bulk edit rule and regenerate API key', async () => { + const ruleId = await createRule(apiKey, 'test_bulk_edit2'); + objectRemover.add(SuperuserAtSpace1.space.id, ruleId, 'rule', 'alerting'); + const payload = { + ids: [ruleId], + operations: [ + { + operation: 'add', + field: 'tags', + value: ['another-tag'], + }, + ], + }; + + const response = await supertest + .post(`${getUrlPrefix(SuperuserAtSpace1.space.id)}/internal/alerting/rules/_bulk_edit`) + .set('kbn-xsrf', 'foo') + .send(payload); + + expect(response.status).to.eql(200); + expect(response.body.rules[0].tags).to.eql(['foo', 'another-tag']); + expect(response.body.rules[0].api_key_created_by_user).to.eql(false); + + // Ensure AAD isn't broken + await checkAAD({ + supertest, + spaceId: SuperuserAtSpace1.space.id, + type: 'alert', + id: ruleId, + }); + + // Ensure an API key was generated + expect(apiKeyExists('test.noop', 'test_bulk_edit2')).to.eql(true); + }); + + it('should successfully update api key for rule with user managed API key', async () => { + const ruleId = await createRule(apiKey, 'test_update_api_key1'); + objectRemover.add(SuperuserAtSpace1.space.id, ruleId, 'rule', 'alerting'); + const response = await superTestWithoutAuth + .post( + `${getUrlPrefix( + SuperuserAtSpace1.space.id + )}/internal/alerting/rule/${ruleId}/_update_api_key` + ) + .set('kbn-xsrf', 'foo') + .set('Authorization', `ApiKey ${apiKey}`); + expect(response.status).to.eql(204); + + // Ensure AAD isn't broken + await checkAAD({ + supertest, + spaceId: SuperuserAtSpace1.space.id, + type: 'alert', + id: ruleId, + }); + + // Ensure no API key was generated + expect(apiKeyExists('test.noop', 'test_update_api_key1')).to.eql(false); + }); + + it('should successfully update api key for rule and regenerate API key', async () => { + const ruleId = await createRule(apiKey, 'test_update_api_key2'); + objectRemover.add(SuperuserAtSpace1.space.id, ruleId, 'rule', 'alerting'); + const response = await supertest + .post( + `${getUrlPrefix( + SuperuserAtSpace1.space.id + )}/internal/alerting/rule/${ruleId}/_update_api_key` + ) + .set('kbn-xsrf', 'foo'); + expect(response.status).to.eql(204); + + // Ensure AAD isn't broken + await checkAAD({ + supertest, + spaceId: SuperuserAtSpace1.space.id, + type: 'alert', + id: ruleId, + }); + + // Ensure an API key was generated + expect(apiKeyExists('test.noop', 'test_update_api_key2')).to.eql(true); + }); + + it('should successfully enable rule with user managed API key', async () => { + const ruleId = await createRule(apiKey, 'test_enable1', false); + objectRemover.add(SuperuserAtSpace1.space.id, ruleId, 'rule', 'alerting'); + const response = await superTestWithoutAuth + .post(`${getUrlPrefix(SuperuserAtSpace1.space.id)}/api/alerting/rule/${ruleId}/_enable`) + .set('kbn-xsrf', 'foo') + .set('Authorization', `ApiKey ${apiKey}`); + expect(response.status).to.eql(204); + + // Ensure AAD isn't broken + await checkAAD({ + supertest, + spaceId: SuperuserAtSpace1.space.id, + type: 'alert', + id: ruleId, + }); + + // Ensure no API key was generated + expect(apiKeyExists('test.noop', 'test_enable1')).to.eql(false); + }); + + it('should successfully enable rule and generate API key', async () => { + const ruleId = await createRule(apiKey, 'test_enable2', false); + objectRemover.add(SuperuserAtSpace1.space.id, ruleId, 'rule', 'alerting'); + const response = await supertest + .post(`${getUrlPrefix(SuperuserAtSpace1.space.id)}/api/alerting/rule/${ruleId}/_enable`) + .set('kbn-xsrf', 'foo'); + expect(response.status).to.eql(204); + + // Ensure AAD isn't broken + await checkAAD({ + supertest, + spaceId: SuperuserAtSpace1.space.id, + type: 'alert', + id: ruleId, + }); + + // Ensure an API key was generated + expect(apiKeyExists('test.noop', 'test_enable2')).to.eql(true); + }); + + it('should successfully bulk enable rule with user managed API key', async () => { + const ruleId = await createRule(apiKey, 'test_bulk_enable1', false); + objectRemover.add(SuperuserAtSpace1.space.id, ruleId, 'rule', 'alerting'); + const response = await superTestWithoutAuth + .patch(`${getUrlPrefix(SuperuserAtSpace1.space.id)}/internal/alerting/rules/_bulk_enable`) + .set('kbn-xsrf', 'foo') + .set('Authorization', `ApiKey ${apiKey}`) + .send({ ids: [ruleId] }); + expect(response.status).to.eql(200); + expect(response.body.rules[0].enabled).to.eql(true); + expect(response.body.rules[0].apiKeyCreatedByUser).to.eql(true); + + // Ensure AAD isn't broken + await checkAAD({ + supertest, + spaceId: SuperuserAtSpace1.space.id, + type: 'alert', + id: ruleId, + }); + + // Ensure no API key was generated + expect(apiKeyExists('test.noop', 'test_bulk_enable1')).to.eql(false); + }); + + it('should successfully bulk enable rule and generate API key', async () => { + const ruleId = await createRule(apiKey, 'test_bulk_enable2', false); + objectRemover.add(SuperuserAtSpace1.space.id, ruleId, 'rule', 'alerting'); + const response = await supertest + .patch(`${getUrlPrefix(SuperuserAtSpace1.space.id)}/internal/alerting/rules/_bulk_enable`) + .set('kbn-xsrf', 'foo') + .send({ ids: [ruleId] }); + expect(response.status).to.eql(200); + expect(response.body.rules[0].enabled).to.eql(true); + expect(response.body.rules[0].apiKeyCreatedByUser).to.eql(false); + + // Ensure AAD isn't broken + await checkAAD({ + supertest, + spaceId: SuperuserAtSpace1.space.id, + type: 'alert', + id: ruleId, + }); + + // Ensure an API key was generated + expect(apiKeyExists('test.noop', 'test_bulk_enable2')).to.eql(true); + }); + + it('should successfully delete rule with user managed API key', async () => { + const ruleId = await createRule(apiKey, 'test_delete1'); + const response = await superTestWithoutAuth + .delete(`${getUrlPrefix(SuperuserAtSpace1.space.id)}/api/alerting/rule/${ruleId}`) + .set('kbn-xsrf', 'foo') + .set('Authorization', `ApiKey ${apiKey}`); + expect(response.statusCode).to.eql(204); + + const invalidateResponse = await es.security.invalidateApiKey({ + body: { ids: ['abc'], owner: false }, + }); + expect(invalidateResponse.previously_invalidated_api_keys).to.eql([]); + }); + + it('should successfully delete rule', async () => { + const ruleId = await createRule(apiKey, 'test_delete2'); + const response = await supertest + .delete(`${getUrlPrefix(SuperuserAtSpace1.space.id)}/api/alerting/rule/${ruleId}`) + .set('kbn-xsrf', 'foo'); + expect(response.status).to.eql(204); + + const invalidateResponse = await es.security.invalidateApiKey({ + body: { ids: ['abc'], owner: false }, + }); + expect(invalidateResponse.previously_invalidated_api_keys).to.eql([]); + }); + + it('should successfully bulk delete rule with user managed api key', async () => { + const ruleId = await createRule(apiKey, 'test_bulk_delete1'); + const response = await superTestWithoutAuth + .patch(`${getUrlPrefix(SuperuserAtSpace1.space.id)}/internal/alerting/rules/_bulk_delete`) + .set('kbn-xsrf', 'foo') + .set('Authorization', `ApiKey ${apiKey}`) + .send({ ids: [ruleId] }); + expect(response.statusCode).to.eql(200); + + const invalidateResponse = await es.security.invalidateApiKey({ + body: { ids: ['abc'], owner: false }, + }); + expect(invalidateResponse.previously_invalidated_api_keys).to.eql([]); + }); + + it('should successfully bulk delete rule', async () => { + const ruleId = await createRule(apiKey, 'test_bulk_delete'); + const response = await supertest + .patch(`${getUrlPrefix(SuperuserAtSpace1.space.id)}/internal/alerting/rules/_bulk_delete`) + .set('kbn-xsrf', 'foo') + .send({ ids: [ruleId] }); + expect(response.status).to.eql(200); + + const invalidateResponse = await es.security.invalidateApiKey({ + body: { ids: ['abc'], owner: false }, + }); + expect(invalidateResponse.previously_invalidated_api_keys).to.eql([]); + }); + }); + }); + + async function apiKeyExists(ruleTypeId: string, ruleName: string) { + // Typically an API key is created using the rule type id and the name so check + // that this does not exist + const generatedApiKeyName = generateAPIKeyName(ruleTypeId, ruleName); + + const { body: allApiKeys } = await supertest + .get(`/internal/security/api_key?isAdmin=true`) + .set('kbn-xsrf', 'foo') + .expect(200); + + return !!allApiKeys.apiKeys.find((key: { name: string }) => key.name === generatedApiKeyName); + } + + async function createRule(apiKey: string, ruleName: string, enabled: boolean = true) { + const testRuleData = getTestRuleData({}); + // Create rule and make sure it runs once successfully + const response = await superTestWithoutAuth + .post(`${getUrlPrefix(SuperuserAtSpace1.space.id)}/api/alerting/rule`) + .set('kbn-xsrf', 'foo') + .set('Authorization', `ApiKey ${apiKey}`) + .send({ ...testRuleData, name: ruleName, enabled }); + + expect(response.status).to.eql(200); + const ruleId = response.body.id; + + if (enabled) { + // Make sure rule runs successfully + const events = await retry.try(async () => { + return await getEventLog({ + getService, + spaceId: SuperuserAtSpace1.space.id, + type: 'alert', + id: ruleId, + provider: 'alerting', + actions: new Map([ + // make sure the counts of the # of events per type are as expected + ['execute', { gte: 1 }], + ]), + }); + }); + const executeEvent = events.find( + (event: IValidatedEvent) => event?.event?.action === 'execute' + ); + expect(executeEvent?.event?.outcome).to.eql('success'); + } + + return ruleId; + } +} From c0033a1b4b2223e6331c1797f34d75a1c964a54b Mon Sep 17 00:00:00 2001 From: Sean Story Date: Wed, 26 Apr 2023 08:44:20 -0500 Subject: [PATCH 14/73] Reset state of pipeline flyout on close or completion (#155760) ## Summary Fixes a bug where adding a second pipeline would pop you into the last page of the previously added pipeline flyout. ![can't create new pipeline](https://user-images.githubusercontent.com/5288246/234373486-fb2c6a6a-b2d4-4ed6-96ed-27a938675664.gif) ### 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) --- .../ml_inference/ml_inference_logic.ts | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) 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 516d65df089b5..2cc9a7eabea35 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 @@ -66,6 +66,7 @@ import { sortModels, sortSourceFields, } from '../../../shared/ml_inference/utils'; +import { PipelinesLogic } from '../pipelines_logic'; import { AddInferencePipelineFormErrors, @@ -227,6 +228,8 @@ export const MLInferenceLogic = kea< 'apiSuccess as attachApiSuccess', 'makeRequest as makeAttachPipelineRequest', ], + PipelinesLogic, + ['closeAddMlInferencePipelineModal as closeAddMlInferencePipelineModal'], ], values: [ CachedFetchIndexApiLogic, @@ -348,6 +351,20 @@ export const MLInferenceLogic = kea< selectedSourceFields: [], }; }, + closeAddMlInferencePipelineModal: () => ({ + configuration: { + ...EMPTY_PIPELINE_CONFIGURATION, + }, + indexName: '', + step: AddInferencePipelineSteps.Configuration, + }), + createApiSuccess: () => ({ + configuration: { + ...EMPTY_PIPELINE_CONFIGURATION, + }, + indexName: '', + step: AddInferencePipelineSteps.Configuration, + }), removeFieldFromMapping: (modal, { fieldName }) => { const { configuration: { fieldMappings }, From 0005df3ba403bd2ff9db05318d495a1aa484b5cc Mon Sep 17 00:00:00 2001 From: Julia Bardi <90178898+juliaElastic@users.noreply.github.com> Date: Wed, 26 Apr 2023 16:51:47 +0200 Subject: [PATCH 15/73] [Fleet] removed experimental from openapi README (#155847) ## Summary Small pr to remove Experimental from the Fleet openapi README. Related to https://github.com/elastic/kibana/issues/123150 --- x-pack/plugins/fleet/common/openapi/README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/x-pack/plugins/fleet/common/openapi/README.md b/x-pack/plugins/fleet/common/openapi/README.md index 3dc43c18785a0..e5241e3b27872 100644 --- a/x-pack/plugins/fleet/common/openapi/README.md +++ b/x-pack/plugins/fleet/common/openapi/README.md @@ -1,6 +1,4 @@ -# OpenAPI (Experimental) - -> **_NOTE:_** This spec is experimental and may be incomplete or change later. +# OpenAPI The current self-contained spec file, available [as JSON](https://raw.githubusercontent.com/elastic/kibana/master/x-pack/plugins/fleet/common/openapi/bundled.json) or [as YAML](https://raw.githubusercontent.com/elastic/kibana/master/x-pack/plugins/fleet/common/openapi/bundled.yaml), can be used for online tools like those found at https://openapi.tools/. @@ -8,6 +6,8 @@ For example, [click here to view the specification in the Swagger UI](https://pe A guide about the openApi specification can be found at [https://swagger.io/docs/specification/about/](https://swagger.io/docs/specification/about/). +Fleet API docs: https://www.elastic.co/guide/en/fleet/master/fleet-apis.html + ## The `openapi` folder - `entrypoint.yaml` is the overview file which links to the various files on disk. From a88f121764fbf8f8f80529d29697bebbef4af6af Mon Sep 17 00:00:00 2001 From: Mark Hopkin Date: Wed, 26 Apr 2023 15:56:51 +0100 Subject: [PATCH 16/73] [Fleet] Bugfix: dynamic_datastream test package has wrong version in directory name (#155855) ## Summary When running a local registry it fails to start because this package is invalid, I dont know why this doesn't break CI! ``` "reading packages from filesystem failed: loading package failed (path: /packages/test-packages/dynamic_datastream/0.2.0): version in manifest file is not consistent with path: inconsistent versions (path: 0.2.0, manifest: 1.2.0) accessing config" ``` I have just renamed the 0.2.0 directory to 1.2.0 Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../data_stream/test_logs/agent/stream/log.yml.hbs | 0 .../{0.2.0 => 1.2.0}/data_stream/test_logs/fields/ecs.yml | 0 .../{0.2.0 => 1.2.0}/data_stream/test_logs/fields/fields.yml | 0 .../{0.2.0 => 1.2.0}/data_stream/test_logs/manifest.yml | 0 .../data_stream/test_metrics/agent/stream/cpu.yml.hbs | 0 .../{0.2.0 => 1.2.0}/data_stream/test_metrics/fields/ecs.yml | 0 .../{0.2.0 => 1.2.0}/data_stream/test_metrics/fields/fields.yml | 0 .../{0.2.0 => 1.2.0}/data_stream/test_metrics/manifest.yml | 0 .../dynamic_datastream/{0.2.0 => 1.2.0}/docs/README.md | 0 .../dynamic_datastream/{0.2.0 => 1.2.0}/manifest.yml | 0 10 files changed, 0 insertions(+), 0 deletions(-) rename x-pack/test/fleet_api_integration/apis/fixtures/test_packages/dynamic_datastream/{0.2.0 => 1.2.0}/data_stream/test_logs/agent/stream/log.yml.hbs (100%) rename x-pack/test/fleet_api_integration/apis/fixtures/test_packages/dynamic_datastream/{0.2.0 => 1.2.0}/data_stream/test_logs/fields/ecs.yml (100%) rename x-pack/test/fleet_api_integration/apis/fixtures/test_packages/dynamic_datastream/{0.2.0 => 1.2.0}/data_stream/test_logs/fields/fields.yml (100%) rename x-pack/test/fleet_api_integration/apis/fixtures/test_packages/dynamic_datastream/{0.2.0 => 1.2.0}/data_stream/test_logs/manifest.yml (100%) rename x-pack/test/fleet_api_integration/apis/fixtures/test_packages/dynamic_datastream/{0.2.0 => 1.2.0}/data_stream/test_metrics/agent/stream/cpu.yml.hbs (100%) rename x-pack/test/fleet_api_integration/apis/fixtures/test_packages/dynamic_datastream/{0.2.0 => 1.2.0}/data_stream/test_metrics/fields/ecs.yml (100%) rename x-pack/test/fleet_api_integration/apis/fixtures/test_packages/dynamic_datastream/{0.2.0 => 1.2.0}/data_stream/test_metrics/fields/fields.yml (100%) rename x-pack/test/fleet_api_integration/apis/fixtures/test_packages/dynamic_datastream/{0.2.0 => 1.2.0}/data_stream/test_metrics/manifest.yml (100%) rename x-pack/test/fleet_api_integration/apis/fixtures/test_packages/dynamic_datastream/{0.2.0 => 1.2.0}/docs/README.md (100%) rename x-pack/test/fleet_api_integration/apis/fixtures/test_packages/dynamic_datastream/{0.2.0 => 1.2.0}/manifest.yml (100%) diff --git a/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/dynamic_datastream/0.2.0/data_stream/test_logs/agent/stream/log.yml.hbs b/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/dynamic_datastream/1.2.0/data_stream/test_logs/agent/stream/log.yml.hbs similarity index 100% rename from x-pack/test/fleet_api_integration/apis/fixtures/test_packages/dynamic_datastream/0.2.0/data_stream/test_logs/agent/stream/log.yml.hbs rename to x-pack/test/fleet_api_integration/apis/fixtures/test_packages/dynamic_datastream/1.2.0/data_stream/test_logs/agent/stream/log.yml.hbs diff --git a/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/dynamic_datastream/0.2.0/data_stream/test_logs/fields/ecs.yml b/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/dynamic_datastream/1.2.0/data_stream/test_logs/fields/ecs.yml similarity index 100% rename from x-pack/test/fleet_api_integration/apis/fixtures/test_packages/dynamic_datastream/0.2.0/data_stream/test_logs/fields/ecs.yml rename to x-pack/test/fleet_api_integration/apis/fixtures/test_packages/dynamic_datastream/1.2.0/data_stream/test_logs/fields/ecs.yml diff --git a/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/dynamic_datastream/0.2.0/data_stream/test_logs/fields/fields.yml b/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/dynamic_datastream/1.2.0/data_stream/test_logs/fields/fields.yml similarity index 100% rename from x-pack/test/fleet_api_integration/apis/fixtures/test_packages/dynamic_datastream/0.2.0/data_stream/test_logs/fields/fields.yml rename to x-pack/test/fleet_api_integration/apis/fixtures/test_packages/dynamic_datastream/1.2.0/data_stream/test_logs/fields/fields.yml diff --git a/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/dynamic_datastream/0.2.0/data_stream/test_logs/manifest.yml b/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/dynamic_datastream/1.2.0/data_stream/test_logs/manifest.yml similarity index 100% rename from x-pack/test/fleet_api_integration/apis/fixtures/test_packages/dynamic_datastream/0.2.0/data_stream/test_logs/manifest.yml rename to x-pack/test/fleet_api_integration/apis/fixtures/test_packages/dynamic_datastream/1.2.0/data_stream/test_logs/manifest.yml diff --git a/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/dynamic_datastream/0.2.0/data_stream/test_metrics/agent/stream/cpu.yml.hbs b/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/dynamic_datastream/1.2.0/data_stream/test_metrics/agent/stream/cpu.yml.hbs similarity index 100% rename from x-pack/test/fleet_api_integration/apis/fixtures/test_packages/dynamic_datastream/0.2.0/data_stream/test_metrics/agent/stream/cpu.yml.hbs rename to x-pack/test/fleet_api_integration/apis/fixtures/test_packages/dynamic_datastream/1.2.0/data_stream/test_metrics/agent/stream/cpu.yml.hbs diff --git a/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/dynamic_datastream/0.2.0/data_stream/test_metrics/fields/ecs.yml b/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/dynamic_datastream/1.2.0/data_stream/test_metrics/fields/ecs.yml similarity index 100% rename from x-pack/test/fleet_api_integration/apis/fixtures/test_packages/dynamic_datastream/0.2.0/data_stream/test_metrics/fields/ecs.yml rename to x-pack/test/fleet_api_integration/apis/fixtures/test_packages/dynamic_datastream/1.2.0/data_stream/test_metrics/fields/ecs.yml diff --git a/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/dynamic_datastream/0.2.0/data_stream/test_metrics/fields/fields.yml b/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/dynamic_datastream/1.2.0/data_stream/test_metrics/fields/fields.yml similarity index 100% rename from x-pack/test/fleet_api_integration/apis/fixtures/test_packages/dynamic_datastream/0.2.0/data_stream/test_metrics/fields/fields.yml rename to x-pack/test/fleet_api_integration/apis/fixtures/test_packages/dynamic_datastream/1.2.0/data_stream/test_metrics/fields/fields.yml diff --git a/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/dynamic_datastream/0.2.0/data_stream/test_metrics/manifest.yml b/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/dynamic_datastream/1.2.0/data_stream/test_metrics/manifest.yml similarity index 100% rename from x-pack/test/fleet_api_integration/apis/fixtures/test_packages/dynamic_datastream/0.2.0/data_stream/test_metrics/manifest.yml rename to x-pack/test/fleet_api_integration/apis/fixtures/test_packages/dynamic_datastream/1.2.0/data_stream/test_metrics/manifest.yml diff --git a/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/dynamic_datastream/0.2.0/docs/README.md b/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/dynamic_datastream/1.2.0/docs/README.md similarity index 100% rename from x-pack/test/fleet_api_integration/apis/fixtures/test_packages/dynamic_datastream/0.2.0/docs/README.md rename to x-pack/test/fleet_api_integration/apis/fixtures/test_packages/dynamic_datastream/1.2.0/docs/README.md diff --git a/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/dynamic_datastream/0.2.0/manifest.yml b/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/dynamic_datastream/1.2.0/manifest.yml similarity index 100% rename from x-pack/test/fleet_api_integration/apis/fixtures/test_packages/dynamic_datastream/0.2.0/manifest.yml rename to x-pack/test/fleet_api_integration/apis/fixtures/test_packages/dynamic_datastream/1.2.0/manifest.yml From 327b92fd5fd296126055fd6f33c899b1748583db Mon Sep 17 00:00:00 2001 From: Kurt Date: Wed, 26 Apr 2023 11:22:42 -0400 Subject: [PATCH 17/73] Update CODEOWNERS to proper team (#155865) ## Summary Changing `platform-security` to `kibana-security` for newly added packages --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> --- .github/CODEOWNERS | 6 +++--- .../core-user-settings-server-internal/kibana.jsonc | 2 +- .../core-user-settings-server-mocks/kibana.jsonc | 2 +- .../user-settings/core-user-settings-server/kibana.jsonc | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index dff2737a1cd4c..93f49f1277ac7 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -286,9 +286,9 @@ packages/core/usage-data/core-usage-data-base-server-internal @elastic/kibana-co packages/core/usage-data/core-usage-data-server @elastic/kibana-core packages/core/usage-data/core-usage-data-server-internal @elastic/kibana-core packages/core/usage-data/core-usage-data-server-mocks @elastic/kibana-core -packages/core/user-settings/core-user-settings-server @elastic/platform-security -packages/core/user-settings/core-user-settings-server-internal @elastic/platform-security -packages/core/user-settings/core-user-settings-server-mocks @elastic/platform-security +packages/core/user-settings/core-user-settings-server @elastic/kibana-security +packages/core/user-settings/core-user-settings-server-internal @elastic/kibana-security +packages/core/user-settings/core-user-settings-server-mocks @elastic/kibana-security x-pack/plugins/cross_cluster_replication @elastic/platform-deployment-management packages/kbn-crypto @elastic/kibana-security packages/kbn-crypto-browser @elastic/kibana-core diff --git a/packages/core/user-settings/core-user-settings-server-internal/kibana.jsonc b/packages/core/user-settings/core-user-settings-server-internal/kibana.jsonc index 48655c00cfee4..ff5d2a67af094 100644 --- a/packages/core/user-settings/core-user-settings-server-internal/kibana.jsonc +++ b/packages/core/user-settings/core-user-settings-server-internal/kibana.jsonc @@ -1,5 +1,5 @@ { "type": "shared-common", "id": "@kbn/core-user-settings-server-internal", - "owner": "@elastic/platform-security", + "owner": "@elastic/kibana-security", } diff --git a/packages/core/user-settings/core-user-settings-server-mocks/kibana.jsonc b/packages/core/user-settings/core-user-settings-server-mocks/kibana.jsonc index f3f598b16f68a..af71f0c99d734 100644 --- a/packages/core/user-settings/core-user-settings-server-mocks/kibana.jsonc +++ b/packages/core/user-settings/core-user-settings-server-mocks/kibana.jsonc @@ -1,5 +1,5 @@ { "type": "shared-common", "id": "@kbn/core-user-settings-server-mocks", - "owner": "@elastic/platform-security", + "owner": "@elastic/kibana-security", } diff --git a/packages/core/user-settings/core-user-settings-server/kibana.jsonc b/packages/core/user-settings/core-user-settings-server/kibana.jsonc index 5bf834b25ba3c..bcf4627a5c5d9 100644 --- a/packages/core/user-settings/core-user-settings-server/kibana.jsonc +++ b/packages/core/user-settings/core-user-settings-server/kibana.jsonc @@ -1,5 +1,5 @@ { "type": "shared-common", "id": "@kbn/core-user-settings-server", - "owner": "@elastic/platform-security", + "owner": "@elastic/kibana-security", } From 559d928705d1e5d9510229b745612a17346181a1 Mon Sep 17 00:00:00 2001 From: Shahzad Date: Wed, 26 Apr 2023 18:02:44 +0200 Subject: [PATCH 18/73] [Synthetics] Skip package installation on CI (#155854) --- .buildkite/disabled_jest_configs.json | 3 +-- .../server/synthetics_service/synthetics_service.ts | 4 ++++ x-pack/test/api_integration/config.ts | 3 --- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.buildkite/disabled_jest_configs.json b/.buildkite/disabled_jest_configs.json index 4b37f3d9be6b6..a64c34ae741b4 100644 --- a/.buildkite/disabled_jest_configs.json +++ b/.buildkite/disabled_jest_configs.json @@ -1,4 +1,3 @@ [ - "x-pack/plugins/watcher/jest.config.js", - "src/core/server/integration_tests/ui_settings/jest.integration.config.js" + "x-pack/plugins/watcher/jest.config.js" ] diff --git a/x-pack/plugins/synthetics/server/synthetics_service/synthetics_service.ts b/x-pack/plugins/synthetics/server/synthetics_service/synthetics_service.ts index 3e420bf478dec..69fc7ce0ee6dc 100644 --- a/x-pack/plugins/synthetics/server/synthetics_service/synthetics_service.ts +++ b/x-pack/plugins/synthetics/server/synthetics_service/synthetics_service.ts @@ -108,6 +108,10 @@ export class SyntheticsService { } public async setupIndexTemplates() { + if (process.env.CI && !this.config?.manifestUrl) { + // skip installation on CI + return; + } if (this.indexTemplateExists) { // if already installed, don't need to reinstall return; diff --git a/x-pack/test/api_integration/config.ts b/x-pack/test/api_integration/config.ts index 5766a9efdf982..e43c76d42adfa 100644 --- a/x-pack/test/api_integration/config.ts +++ b/x-pack/test/api_integration/config.ts @@ -30,9 +30,6 @@ export async function getApiIntegrationConfig({ readConfigFile }: FtrConfigProvi '--xpack.ruleRegistry.write.enabled=true', '--xpack.ruleRegistry.write.enabled=true', '--xpack.ruleRegistry.write.cache.enabled=false', - '--xpack.uptime.service.password=test', - '--xpack.uptime.service.username=localKibanaIntegrationTestsUser', - '--xpack.uptime.service.devUrl=mockDevUrl', '--monitoring_collection.opentelemetry.metrics.prometheus.enabled=true', ], }, From 53daa334f45a3855c84779ccadadf9ac8f48e493 Mon Sep 17 00:00:00 2001 From: Coen Warmer Date: Wed, 26 Apr 2023 18:05:02 +0200 Subject: [PATCH 19/73] Add Locator for Rules page (#155799) Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- x-pack/plugins/observability/common/index.ts | 1 + .../public/locators/rules.test.ts | 55 +++++++++++++++++++ .../observability/public/locators/rules.ts | 54 ++++++++++++++++++ .../slo_details/components/header_control.tsx | 32 +++++++---- .../pages/slo_details/slo_details.test.tsx | 26 +++++++++ .../pages/slos/components/slo_list_item.tsx | 21 +++++-- .../public/pages/slos/slos.test.tsx | 33 +++++++++++ x-pack/plugins/observability/public/plugin.ts | 5 ++ 8 files changed, 210 insertions(+), 17 deletions(-) create mode 100644 x-pack/plugins/observability/public/locators/rules.test.ts create mode 100644 x-pack/plugins/observability/public/locators/rules.ts diff --git a/x-pack/plugins/observability/common/index.ts b/x-pack/plugins/observability/common/index.ts index 7ff22e2c50264..628b33ef0a11f 100644 --- a/x-pack/plugins/observability/common/index.ts +++ b/x-pack/plugins/observability/common/index.ts @@ -64,6 +64,7 @@ export const syntheticsMonitorDetailLocatorID = 'SYNTHETICS_MONITOR_DETAIL_LOCAT export const syntheticsEditMonitorLocatorID = 'SYNTHETICS_EDIT_MONITOR_LOCATOR'; export const syntheticsSettingsLocatorID = 'SYNTHETICS_SETTINGS'; export const ruleDetailsLocatorID = 'RULE_DETAILS_LOCATOR'; +export const rulesLocatorID = 'RULES_LOCATOR'; export { NETWORK_TIMINGS_FIELDS, diff --git a/x-pack/plugins/observability/public/locators/rules.test.ts b/x-pack/plugins/observability/public/locators/rules.test.ts new file mode 100644 index 0000000000000..86d4303f054b9 --- /dev/null +++ b/x-pack/plugins/observability/public/locators/rules.test.ts @@ -0,0 +1,55 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { RulesLocatorDefinition, RULES_PATH } from './rules'; + +describe('RulesLocator', () => { + const locator = new RulesLocatorDefinition(); + + it('should return correct url when empty params are provided', async () => { + const location = await locator.getLocation({}); + expect(location.app).toEqual('observability'); + expect(location.path).toEqual( + `${RULES_PATH}?_a=(lastResponse:!(),params:(),search:'',status:!(),type:!())` + ); + }); + + it('should return correct url when lastResponse is provided', async () => { + const location = await locator.getLocation({ lastResponse: ['foo'] }); + expect(location.path).toEqual( + `${RULES_PATH}?_a=(lastResponse:!(foo),params:(),search:'',status:!(),type:!())` + ); + }); + + it('should return correct url when params is provided', async () => { + const location = await locator.getLocation({ params: { sloId: 'foo' } }); + expect(location.path).toEqual( + `${RULES_PATH}?_a=(lastResponse:!(),params:(sloId:foo),search:'',status:!(),type:!())` + ); + }); + + it('should return correct url when search is provided', async () => { + const location = await locator.getLocation({ search: 'foo' }); + expect(location.path).toEqual( + `${RULES_PATH}?_a=(lastResponse:!(),params:(),search:foo,status:!(),type:!())` + ); + }); + + it('should return correct url when status is provided', async () => { + const location = await locator.getLocation({ status: ['enabled'] }); + expect(location.path).toEqual( + `${RULES_PATH}?_a=(lastResponse:!(),params:(),search:'',status:!(enabled),type:!())` + ); + }); + + it('should return correct url when type is provided', async () => { + const location = await locator.getLocation({ type: ['foo'] }); + expect(location.path).toEqual( + `${RULES_PATH}?_a=(lastResponse:!(),params:(),search:'',status:!(),type:!(foo))` + ); + }); +}); diff --git a/x-pack/plugins/observability/public/locators/rules.ts b/x-pack/plugins/observability/public/locators/rules.ts new file mode 100644 index 0000000000000..533f4ccc98fd1 --- /dev/null +++ b/x-pack/plugins/observability/public/locators/rules.ts @@ -0,0 +1,54 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { setStateToKbnUrl } from '@kbn/kibana-utils-plugin/public'; +import type { SerializableRecord } from '@kbn/utility-types'; +import type { LocatorDefinition } from '@kbn/share-plugin/public'; +import type { RuleStatus } from '@kbn/triggers-actions-ui-plugin/public'; +import { rulesLocatorID } from '../../common'; + +// eslint-disable-next-line @typescript-eslint/consistent-type-definitions +export type RulesParams = { + lastResponse?: string[]; + params?: Record; + search?: string; + status?: RuleStatus[]; + type?: string[]; +}; + +export interface RulesLocatorParams extends RulesParams, SerializableRecord {} + +export const RULES_PATH = '/alerts/rules'; + +export class RulesLocatorDefinition implements LocatorDefinition { + public readonly id = rulesLocatorID; + + public readonly getLocation = async ({ + lastResponse = [], + params = {}, + search = '', + status = [], + type = [], + }: RulesLocatorParams) => { + return { + app: 'observability', + path: setStateToKbnUrl( + '_a', + { + lastResponse, + params, + search, + status, + type, + }, + { useHash: false, storeInHashQuery: false }, + RULES_PATH + ), + state: {}, + }; + }; +} diff --git a/x-pack/plugins/observability/public/pages/slo_details/components/header_control.tsx b/x-pack/plugins/observability/public/pages/slo_details/components/header_control.tsx index 99ce1ba03f9b6..0bac038c43088 100644 --- a/x-pack/plugins/observability/public/pages/slo_details/components/header_control.tsx +++ b/x-pack/plugins/observability/public/pages/slo_details/components/header_control.tsx @@ -10,14 +10,14 @@ import React, { useState } from 'react'; import { i18n } from '@kbn/i18n'; import { SLOWithSummaryResponse } from '@kbn/slo-schema'; +import { useCapabilities } from '../../../hooks/slo/use_capabilities'; +import { useKibana } from '../../../utils/kibana_react'; import { isApmIndicatorType } from '../../../utils/slo/indicator'; import { convertSliApmParamsToApmAppDeeplinkUrl } from '../../../utils/slo/convert_sli_apm_params_to_apm_app_deeplink_url'; import { SLO_BURN_RATE_RULE_ID } from '../../../../common/constants'; -import { sloFeatureId } from '../../../../common'; +import { rulesLocatorID, sloFeatureId } from '../../../../common'; import { paths } from '../../../config/paths'; -import { useKibana } from '../../../utils/kibana_react'; -import { ObservabilityAppServices } from '../../../application/types'; -import { useCapabilities } from '../../../hooks/slo/use_capabilities'; +import type { RulesParams } from '../../../locators/rules'; export interface Props { slo: SLOWithSummaryResponse | undefined; @@ -28,8 +28,11 @@ export function HeaderControl({ isLoading, slo }: Props) { const { application: { navigateToUrl }, http: { basePath }, + share: { + url: { locators }, + }, triggersActionsUi: { getAddRuleFlyout: AddRuleFlyout }, - } = useKibana().services; + } = useKibana().services; const { hasWriteCapabilities } = useCapabilities(); const [isPopoverOpen, setIsPopoverOpen] = useState(false); const [isRuleFlyoutVisible, setRuleFlyoutVisibility] = useState(false); @@ -52,12 +55,19 @@ export function HeaderControl({ isLoading, slo }: Props) { setRuleFlyoutVisibility(true); }; - const handleNavigateToRules = () => { - navigateToUrl( - basePath.prepend( - `${paths.observability.rules}?_a=(lastResponse:!(),search:%27%27,params:(sloId:%27${slo?.id}%27),status:!(),type:!())` - ) - ); + const handleNavigateToRules = async () => { + const locator = locators.get(rulesLocatorID); + + if (slo?.id) { + locator?.navigate( + { + params: { sloId: slo.id }, + }, + { + replace: true, + } + ); + } }; const handleNavigateToApm = () => { diff --git a/x-pack/plugins/observability/public/pages/slo_details/slo_details.test.tsx b/x-pack/plugins/observability/public/pages/slo_details/slo_details.test.tsx index 4028b80203591..9cd8836c6cdf7 100644 --- a/x-pack/plugins/observability/public/pages/slo_details/slo_details.test.tsx +++ b/x-pack/plugins/observability/public/pages/slo_details/slo_details.test.tsx @@ -49,6 +49,7 @@ const useCapabilitiesMock = useCapabilities as jest.Mock; const mockNavigate = jest.fn(); const mockBasePathPrepend = jest.fn(); +const mockLocator = jest.fn(); const mockKibana = () => { useKibanaMock.mockReturnValue({ @@ -60,6 +61,13 @@ const mockKibana = () => { prepend: mockBasePathPrepend, }, }, + share: { + url: { + locators: { + get: mockLocator, + }, + }, + }, triggersActionsUi: { getAddRuleFlyout: jest.fn(() => (
mocked component
@@ -182,6 +190,24 @@ describe('SLO Details Page', () => { expect(screen.queryByTestId('sloDetailsHeaderControlPopoverCreateRule')).toBeTruthy(); }); + it("renders a 'Manage rules' button under actions menu", async () => { + const slo = buildSlo(); + useParamsMock.mockReturnValue(slo.id); + useFetchSloDetailsMock.mockReturnValue({ isLoading: false, slo }); + useLicenseMock.mockReturnValue({ hasAtLeast: () => true }); + + render(); + + fireEvent.click(screen.getByTestId('o11yHeaderControlActionsButton')); + + const manageRulesButton = screen.queryByTestId('sloDetailsHeaderControlPopoverManageRules'); + expect(manageRulesButton).toBeTruthy(); + + fireEvent.click(manageRulesButton!); + + expect(mockLocator).toBeCalled(); + }); + it('renders the Overview tab by default', async () => { const slo = buildSlo(); useParamsMock.mockReturnValue(slo.id); 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 049481486eb9e..0e58573b740c2 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 @@ -34,10 +34,11 @@ import { transformValuesToCreateSLOInput, } from '../../slo_edit/helpers/process_slo_form_values'; import { SLO_BURN_RATE_RULE_ID } from '../../../../common/constants'; -import { sloFeatureId } from '../../../../common'; +import { rulesLocatorID, 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'; +import type { RulesParams } from '../../../locators/rules'; export interface SloListItemProps { slo: SLOWithSummaryResponse; @@ -57,6 +58,9 @@ export function SloListItem({ const { application: { navigateToUrl }, http: { basePath }, + share: { + url: { locators }, + }, triggersActionsUi: { getAddRuleFlyout: AddRuleFlyout }, } = useKibana().services; const { hasWriteCapabilities } = useCapabilities(); @@ -92,11 +96,16 @@ export function SloListItem({ queryClient.invalidateQueries(['fetchRulesForSlo']); }; - const handleNavigateToRules = () => { - navigateToUrl( - basePath.prepend( - `${paths.observability.rules}?_a=(lastResponse:!(),search:%27%27,params:(sloId:%27${slo?.id}%27),status:!(),type:!())` - ) + const handleNavigateToRules = async () => { + const locator = locators.get(rulesLocatorID); + + locator?.navigate( + { + params: { sloId: slo.id }, + }, + { + replace: true, + } ); }; diff --git a/x-pack/plugins/observability/public/pages/slos/slos.test.tsx b/x-pack/plugins/observability/public/pages/slos/slos.test.tsx index a3e01f0b00458..12a6f2762de58 100644 --- a/x-pack/plugins/observability/public/pages/slos/slos.test.tsx +++ b/x-pack/plugins/observability/public/pages/slos/slos.test.tsx @@ -60,6 +60,7 @@ useDeleteSloMock.mockReturnValue({ mutate: mockDeleteSlo }); const mockNavigate = jest.fn(); const mockAddSuccess = jest.fn(); const mockAddError = jest.fn(); +const mockLocator = jest.fn(); const mockGetAddRuleFlyout = jest.fn().mockReturnValue(() =>
Add rule flyout
); const mockKibana = () => { @@ -78,6 +79,13 @@ const mockKibana = () => { addError: mockAddError, }, }, + share: { + url: { + locators: { + get: mockLocator, + }, + }, + }, triggersActionsUi: { getAddRuleFlyout: mockGetAddRuleFlyout }, uiSettings: { get: (settings: string) => { @@ -226,6 +234,31 @@ describe('SLOs Page', () => { expect(mockGetAddRuleFlyout).toBeCalled(); }); + it('allows managing rules for an SLO', async () => { + useFetchSloListMock.mockReturnValue({ isLoading: false, sloList }); + + useFetchHistoricalSummaryMock.mockReturnValue({ + isLoading: false, + sloHistoricalSummaryResponse: historicalSummaryData, + }); + + await act(async () => { + render(); + }); + + screen.getAllByLabelText('Actions').at(0)?.click(); + + await waitForEuiPopoverOpen(); + + const button = screen.getByTestId('sloActionsManageRules'); + + expect(button).toBeTruthy(); + + button.click(); + + expect(mockLocator).toBeCalled(); + }); + it('allows deleting an SLO', async () => { useFetchSloListMock.mockReturnValue({ isLoading: false, sloList }); diff --git a/x-pack/plugins/observability/public/plugin.ts b/x-pack/plugins/observability/public/plugin.ts index 190c646bebbcc..3ea0c8b797c40 100644 --- a/x-pack/plugins/observability/public/plugin.ts +++ b/x-pack/plugins/observability/public/plugin.ts @@ -50,6 +50,7 @@ import { LicensingPluginStart } from '@kbn/licensing-plugin/public'; import { UnifiedSearchPublicPluginStart } from '@kbn/unified-search-plugin/public'; import { ExploratoryViewPublicStart } from '@kbn/exploratory-view-plugin/public'; import { RuleDetailsLocatorDefinition } from './locators/rule_details'; +import { RulesLocatorDefinition } from './locators/rules'; import { observabilityAppId, observabilityFeatureId, casesPath } from '../common'; import { registerDataHandler } from './data_handler'; import { @@ -190,6 +191,9 @@ export class Plugin this.observabilityRuleTypeRegistry = createObservabilityRuleTypeRegistry( pluginsSetup.triggersActionsUi.ruleTypeRegistry ); + + const locator = pluginsSetup.share.url.locators.create(new RulesLocatorDefinition()); + pluginsSetup.share.url.locators.create(new RuleDetailsLocatorDefinition()); const mount = async (params: AppMountParameters) => { @@ -310,6 +314,7 @@ export class Plugin dashboard: { register: registerDataHandler }, observabilityRuleTypeRegistry: this.observabilityRuleTypeRegistry, useRulesLink: createUseRulesLink(), + rulesLocator: locator, }; } From 8f597207a222f02b1c7664bc555a9f6e744bc4aa Mon Sep 17 00:00:00 2001 From: Ievgen Sorokopud Date: Wed, 26 Apr 2023 18:16:41 +0200 Subject: [PATCH 20/73] [Security Solution][Alerts] Format alerts for per-alert action context variables (#155829) ## Summary Closes [#155812](https://github.com/elastic/kibana/issues/155812) In https://github.com/elastic/kibana/pull/155384, detection rules were switched to support per-alert actions. When passing the context variable, it was suggested that we should be calling formatAlert to format the alert for notifications, however doing that causes some test failures because formatAlert is fairly heavyweight and bunch of tests were timing out. Thanks to @marshallmain we have this much faster `expandDottedObject` that solves the issue with the very slow `formatAlert`. --- .../create_persistence_rule_type_wrapper.ts | 4 ++-- .../common/utils/expand_dotted.test.ts | 12 +++++++++++ .../common/utils/expand_dotted.ts | 20 ++++++------------- 3 files changed, 20 insertions(+), 16 deletions(-) diff --git a/x-pack/plugins/rule_registry/server/utils/create_persistence_rule_type_wrapper.ts b/x-pack/plugins/rule_registry/server/utils/create_persistence_rule_type_wrapper.ts index cb4d9d734bd02..a69e3b657aeb2 100644 --- a/x-pack/plugins/rule_registry/server/utils/create_persistence_rule_type_wrapper.ts +++ b/x-pack/plugins/rule_registry/server/utils/create_persistence_rule_type_wrapper.ts @@ -195,7 +195,7 @@ export const createPersistenceRuleTypeWrapper: CreatePersistenceRuleTypeWrapper start: Date.parse(alert[TIMESTAMP]), end: Date.parse(alert[TIMESTAMP]), }), - alerts: [alert], + alerts: [formatAlert?.(alert) ?? alert], }) ); @@ -387,7 +387,7 @@ export const createPersistenceRuleTypeWrapper: CreatePersistenceRuleTypeWrapper start: Date.parse(alert[TIMESTAMP]), end: Date.parse(alert[TIMESTAMP]), }), - alerts: [alert], + alerts: [formatAlert?.(alert) ?? alert], }) ); diff --git a/x-pack/plugins/security_solution/common/utils/expand_dotted.test.ts b/x-pack/plugins/security_solution/common/utils/expand_dotted.test.ts index 018220e400937..d3f227274e7f1 100644 --- a/x-pack/plugins/security_solution/common/utils/expand_dotted.test.ts +++ b/x-pack/plugins/security_solution/common/utils/expand_dotted.test.ts @@ -70,6 +70,18 @@ describe('Expand Dotted', () => { }); }); + it('overwrites earlier fields when later fields conflict', () => { + const simpleDottedObj = { + 'kibana.test.1': 'the spice must flow', + 'kibana.test': 2, + }; + expect(expandDottedObject(simpleDottedObj)).toEqual({ + kibana: { + test: 2, + }, + }); + }); + it('expands non dotted field without changing it other than reference', () => { const simpleDottedObj = { test: { value: '123' }, diff --git a/x-pack/plugins/security_solution/common/utils/expand_dotted.ts b/x-pack/plugins/security_solution/common/utils/expand_dotted.ts index f90f589486ff5..4aa56b021244b 100644 --- a/x-pack/plugins/security_solution/common/utils/expand_dotted.ts +++ b/x-pack/plugins/security_solution/common/utils/expand_dotted.ts @@ -5,16 +5,7 @@ * 2.0. */ -import { merge } from '@kbn/std'; - -const expandDottedField = (dottedFieldName: string, val: unknown): object => { - const parts = dottedFieldName.split('.'); - if (parts.length === 1) { - return { [parts[0]]: val }; - } else { - return { [parts[0]]: expandDottedField(parts.slice(1).join('.'), val) }; - } -}; +import { setWith } from 'lodash'; /* * Expands an object with "dotted" fields to a nested object with unflattened fields. @@ -48,8 +39,9 @@ export const expandDottedObject = (dottedObj: object) => { if (Array.isArray(dottedObj)) { return dottedObj; } - return Object.entries(dottedObj).reduce( - (acc, [key, val]) => merge(acc, expandDottedField(key, val)), - {} - ); + const returnObj = {}; + Object.entries(dottedObj).forEach(([key, value]) => { + setWith(returnObj, key, value, Object); + }); + return returnObj; }; From 947b75741ca1c88db3f56b53f7dd76aea8de4079 Mon Sep 17 00:00:00 2001 From: Nathan Reese Date: Wed, 26 Apr 2023 10:25:41 -0600 Subject: [PATCH 21/73] [maps] prepare for spatial join (#155575) PR does some refactoring required to support [spatial joins](https://github.com/elastic/kibana/issues/154605) * separate `ITermJoinSource` into a generic interface `IJoinSource` * Update `InnerJoin` to use `IJoinSource` * Consolidate join sources into `join_sources` folder * Rename `JoinTooltipProperty` -> `TermJoinTooltipProperty` * Convert any effected file to TS --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> --- .../layer_descriptor_types.ts | 4 +- .../source_descriptor_types.ts | 2 +- ...{inner_join.test.js => inner_join.test.ts} | 89 ++++++++----------- .../maps/public/classes/joins/inner_join.ts | 19 ++-- .../layers/build_vector_request_meta.ts | 4 +- .../perform_inner_joins.ts | 22 +++-- .../mvt_vector_layer.test.tsx | 6 +- .../layers/vector_layer/vector_layer.tsx | 30 +++++-- .../es_term_source/es_term_source.test.ts} | 18 ++-- .../es_term_source/es_term_source.ts | 26 +++--- .../es_term_source/index.ts | 0 .../index.ts | 6 +- .../{ => join_sources}/table_source/index.ts | 0 .../table_source/table_source.test.ts | 4 +- .../table_source/table_source.ts | 18 ++-- .../types.ts} | 11 ++- .../index.ts | 2 +- .../term_join_key_label.tsx} | 11 ++- .../term_join_tooltip_property.tsx} | 21 +++-- 19 files changed, 155 insertions(+), 138 deletions(-) rename x-pack/plugins/maps/public/classes/joins/{inner_join.test.js => inner_join.test.ts} (68%) rename x-pack/plugins/maps/public/classes/sources/{es_term_source/es_term_source.test.js => join_sources/es_term_source/es_term_source.test.ts} (86%) rename x-pack/plugins/maps/public/classes/sources/{ => join_sources}/es_term_source/es_term_source.ts (88%) rename x-pack/plugins/maps/public/classes/sources/{ => join_sources}/es_term_source/index.ts (100%) rename x-pack/plugins/maps/public/classes/sources/{term_join_source => join_sources}/index.ts (55%) rename x-pack/plugins/maps/public/classes/sources/{ => join_sources}/table_source/index.ts (100%) rename x-pack/plugins/maps/public/classes/sources/{ => join_sources}/table_source/table_source.test.ts (97%) rename x-pack/plugins/maps/public/classes/sources/{ => join_sources}/table_source/table_source.ts (92%) rename x-pack/plugins/maps/public/classes/sources/{term_join_source/term_join_source.ts => join_sources/types.ts} (87%) rename x-pack/plugins/maps/public/classes/tooltips/{join_tooltip_property => term_join_tooltip_property}/index.ts (77%) rename x-pack/plugins/maps/public/classes/tooltips/{join_tooltip_property/join_key_label.tsx => term_join_tooltip_property/term_join_key_label.tsx} (84%) rename x-pack/plugins/maps/public/classes/tooltips/{join_tooltip_property/join_tooltip_property.tsx => term_join_tooltip_property/term_join_tooltip_property.tsx} (74%) diff --git a/x-pack/plugins/maps/common/descriptor_types/layer_descriptor_types.ts b/x-pack/plugins/maps/common/descriptor_types/layer_descriptor_types.ts index a547ef9c6d93a..b14efda88cad4 100644 --- a/x-pack/plugins/maps/common/descriptor_types/layer_descriptor_types.ts +++ b/x-pack/plugins/maps/common/descriptor_types/layer_descriptor_types.ts @@ -16,7 +16,7 @@ import { VectorStyleDescriptor, } from './style_property_descriptor_types'; import { DataRequestDescriptor } from './data_request_descriptor_types'; -import { AbstractSourceDescriptor, TermJoinSourceDescriptor } from './source_descriptor_types'; +import { AbstractSourceDescriptor, JoinSourceDescriptor } from './source_descriptor_types'; import { LAYER_TYPE } from '../constants'; export type Attribution = { @@ -26,7 +26,7 @@ export type Attribution = { export type JoinDescriptor = { leftField?: string; - right: TermJoinSourceDescriptor; + right: JoinSourceDescriptor; }; export type TileMetaFeature = Feature & { diff --git a/x-pack/plugins/maps/common/descriptor_types/source_descriptor_types.ts b/x-pack/plugins/maps/common/descriptor_types/source_descriptor_types.ts index c76bc8f6a6e17..1dabde9bc0a64 100644 --- a/x-pack/plugins/maps/common/descriptor_types/source_descriptor_types.ts +++ b/x-pack/plugins/maps/common/descriptor_types/source_descriptor_types.ts @@ -183,4 +183,4 @@ export type TableSourceDescriptor = { term: string; }; -export type TermJoinSourceDescriptor = ESTermSourceDescriptor | TableSourceDescriptor; +export type JoinSourceDescriptor = ESTermSourceDescriptor | TableSourceDescriptor; diff --git a/x-pack/plugins/maps/public/classes/joins/inner_join.test.js b/x-pack/plugins/maps/public/classes/joins/inner_join.test.ts similarity index 68% rename from x-pack/plugins/maps/public/classes/joins/inner_join.test.js rename to x-pack/plugins/maps/public/classes/joins/inner_join.test.ts index b01d977972a68..160ced8f46b9d 100644 --- a/x-pack/plugins/maps/public/classes/joins/inner_join.test.js +++ b/x-pack/plugins/maps/public/classes/joins/inner_join.test.ts @@ -5,8 +5,15 @@ * 2.0. */ -import { createJoinTermSource, InnerJoin } from './inner_join'; -import { SOURCE_TYPES } from '../../../common/constants'; +import type { Feature } from 'geojson'; +import type { + ESTermSourceDescriptor, + JoinSourceDescriptor, +} from '../../../common/descriptor_types'; +import type { IVectorSource } from '../sources/vector_source'; +import type { IField } from '../fields/field'; +import { createJoinSource, InnerJoin } from './inner_join'; +import { AGG_TYPE, SOURCE_TYPES } from '../../../common/constants'; jest.mock('../../kibana_services', () => {}); jest.mock('../layers/vector_layer', () => {}); @@ -16,20 +23,20 @@ const rightSource = { id: 'd3625663-5b34-4d50-a784-0d743f676a0c', indexPatternId: '90943e30-9a47-11e8-b64d-95841ca0b247', term: 'geo.dest', - metrics: [{ type: 'count' }], -}; + metrics: [{ type: AGG_TYPE.COUNT }], +} as ESTermSourceDescriptor; const mockSource = { - createField({ fieldName: name }) { + createField({ fieldName }: { fieldName: string }) { return { getName() { - return name; + return fieldName; }, - }; + } as unknown as IField; }, -}; +} as unknown as IVectorSource; -const leftJoin = new InnerJoin( +const iso2LeftJoin = new InnerJoin( { leftField: 'iso2', right: rightSource, @@ -38,27 +45,27 @@ const leftJoin = new InnerJoin( ); const COUNT_PROPERTY_NAME = '__kbnjoin__count__d3625663-5b34-4d50-a784-0d743f676a0c'; -describe('createJoinTermSource', () => { +describe('createJoinSource', () => { test('Should return undefined when descriptor is not provided', () => { - expect(createJoinTermSource(undefined)).toBe(undefined); + expect(createJoinSource(undefined)).toBe(undefined); }); test('Should return undefined with unmatched source type', () => { expect( - createJoinTermSource({ + createJoinSource({ type: SOURCE_TYPES.WMS, - }) + } as unknown as Partial) ).toBe(undefined); }); describe('EsTermSource', () => { test('Should return EsTermSource', () => { - expect(createJoinTermSource(rightSource).constructor.name).toBe('ESTermSource'); + expect(createJoinSource(rightSource)?.constructor.name).toBe('ESTermSource'); }); test('Should return undefined when indexPatternId is undefined', () => { expect( - createJoinTermSource({ + createJoinSource({ ...rightSource, indexPatternId: undefined, }) @@ -67,7 +74,7 @@ describe('createJoinTermSource', () => { test('Should return undefined when term is undefined', () => { expect( - createJoinTermSource({ + createJoinSource({ ...rightSource, term: undefined, }) @@ -78,9 +85,9 @@ describe('createJoinTermSource', () => { describe('TableSource', () => { test('Should return TableSource', () => { expect( - createJoinTermSource({ + createJoinSource({ type: SOURCE_TYPES.TABLE_SOURCE, - }).constructor.name + })?.constructor.name ).toBe('TableSource'); }); }); @@ -92,15 +99,11 @@ describe('joinPropertiesToFeature', () => { properties: { iso2: 'CN', }, - }; + } as unknown as Feature; const propertiesMap = new Map(); propertiesMap.set('CN', { [COUNT_PROPERTY_NAME]: 61 }); - leftJoin.joinPropertiesToFeature(feature, propertiesMap, [ - { - propertyKey: COUNT_PROPERTY_NAME, - }, - ]); + iso2LeftJoin.joinPropertiesToFeature(feature, propertiesMap); expect(feature.properties).toEqual({ iso2: 'CN', [COUNT_PROPERTY_NAME]: 61, @@ -114,21 +117,17 @@ describe('joinPropertiesToFeature', () => { [COUNT_PROPERTY_NAME]: 61, [`__kbn__dynamic__${COUNT_PROPERTY_NAME}__fillColor`]: 1, }, - }; + } as unknown as Feature; const propertiesMap = new Map(); - leftJoin.joinPropertiesToFeature(feature, propertiesMap, [ - { - propertyKey: COUNT_PROPERTY_NAME, - }, - ]); + iso2LeftJoin.joinPropertiesToFeature(feature, propertiesMap); expect(feature.properties).toEqual({ iso2: 'CN', }); }); test('Should coerce to string before joining', () => { - const leftJoin = new InnerJoin( + const zipCodeLeftJoin = new InnerJoin( { leftField: 'zipcode', right: rightSource, @@ -140,15 +139,11 @@ describe('joinPropertiesToFeature', () => { properties: { zipcode: 40204, }, - }; + } as unknown as Feature; const propertiesMap = new Map(); propertiesMap.set('40204', { [COUNT_PROPERTY_NAME]: 61 }); - leftJoin.joinPropertiesToFeature(feature, propertiesMap, [ - { - propertyKey: COUNT_PROPERTY_NAME, - }, - ]); + zipCodeLeftJoin.joinPropertiesToFeature(feature, propertiesMap); expect(feature.properties).toEqual({ zipcode: 40204, [COUNT_PROPERTY_NAME]: 61, @@ -157,26 +152,22 @@ describe('joinPropertiesToFeature', () => { test('Should handle undefined values', () => { const feature = { - //this feature does not have the iso2 field + // this feature does not have the iso2 field properties: { zipcode: 40204, }, - }; + } as unknown as Feature; const propertiesMap = new Map(); propertiesMap.set('40204', { [COUNT_PROPERTY_NAME]: 61 }); - leftJoin.joinPropertiesToFeature(feature, propertiesMap, [ - { - propertyKey: COUNT_PROPERTY_NAME, - }, - ]); + iso2LeftJoin.joinPropertiesToFeature(feature, propertiesMap); expect(feature.properties).toEqual({ zipcode: 40204, }); }); test('Should handle falsy values', () => { - const leftJoin = new InnerJoin( + const codeLeftJoin = new InnerJoin( { leftField: 'code', right: rightSource, @@ -188,15 +179,11 @@ describe('joinPropertiesToFeature', () => { properties: { code: 0, }, - }; + } as unknown as Feature; const propertiesMap = new Map(); propertiesMap.set('0', { [COUNT_PROPERTY_NAME]: 61 }); - leftJoin.joinPropertiesToFeature(feature, propertiesMap, [ - { - propertyKey: COUNT_PROPERTY_NAME, - }, - ]); + codeLeftJoin.joinPropertiesToFeature(feature, propertiesMap); expect(feature.properties).toEqual({ code: 0, [COUNT_PROPERTY_NAME]: 61, diff --git a/x-pack/plugins/maps/public/classes/joins/inner_join.ts b/x-pack/plugins/maps/public/classes/joins/inner_join.ts index a754650cdef80..6ac9a674efc8d 100644 --- a/x-pack/plugins/maps/public/classes/joins/inner_join.ts +++ b/x-pack/plugins/maps/public/classes/joins/inner_join.ts @@ -8,7 +8,6 @@ import type { KibanaExecutionContext } from '@kbn/core/public'; import type { Query } from '@kbn/es-query'; import { Feature, GeoJsonProperties } from 'geojson'; -import { ESTermSource } from '../sources/es_term_source'; import { getComputedFieldNamePrefix } from '../styles/vector/style_util'; import { FORMATTERS_DATA_REQUEST_ID_SUFFIX, @@ -18,18 +17,18 @@ import { import { ESTermSourceDescriptor, JoinDescriptor, + JoinSourceDescriptor, TableSourceDescriptor, - TermJoinSourceDescriptor, } from '../../../common/descriptor_types'; import { IVectorSource } from '../sources/vector_source'; import { IField } from '../fields/field'; import { PropertiesMap } from '../../../common/elasticsearch_util'; -import { ITermJoinSource } from '../sources/term_join_source'; -import { TableSource } from '../sources/table_source'; +import { IJoinSource } from '../sources/join_sources'; +import { ESTermSource, TableSource } from '../sources/join_sources'; -export function createJoinTermSource( - descriptor: Partial | undefined -): ITermJoinSource | undefined { +export function createJoinSource( + descriptor: Partial | undefined +): IJoinSource | undefined { if (!descriptor) { return; } @@ -47,12 +46,12 @@ export function createJoinTermSource( export class InnerJoin { private readonly _descriptor: JoinDescriptor; - private readonly _rightSource?: ITermJoinSource; + private readonly _rightSource?: IJoinSource; private readonly _leftField?: IField; constructor(joinDescriptor: JoinDescriptor, leftSource: IVectorSource) { this._descriptor = joinDescriptor; - this._rightSource = createJoinTermSource(this._descriptor.right); + this._rightSource = createJoinSource(this._descriptor.right); this._leftField = joinDescriptor.leftField ? leftSource.createField({ fieldName: joinDescriptor.leftField }) : undefined; @@ -127,7 +126,7 @@ export class InnerJoin { return joinKey === undefined || joinKey === null ? null : joinKey.toString(); } - getRightJoinSource(): ITermJoinSource { + getRightJoinSource(): IJoinSource { if (!this._rightSource) { throw new Error('Cannot get rightSource from InnerJoin with incomplete config'); } diff --git a/x-pack/plugins/maps/public/classes/layers/build_vector_request_meta.ts b/x-pack/plugins/maps/public/classes/layers/build_vector_request_meta.ts index e0df7ab8d6dd5..adb53e76c060a 100644 --- a/x-pack/plugins/maps/public/classes/layers/build_vector_request_meta.ts +++ b/x-pack/plugins/maps/public/classes/layers/build_vector_request_meta.ts @@ -9,10 +9,10 @@ import _ from 'lodash'; import type { Query } from '@kbn/data-plugin/common'; import { DataFilters, VectorSourceRequestMeta } from '../../../common/descriptor_types'; import { IVectorSource } from '../sources/vector_source'; -import { ITermJoinSource } from '../sources/term_join_source'; +import { IJoinSource } from '../sources/join_sources'; export function buildVectorRequestMeta( - source: IVectorSource | ITermJoinSource, + source: IVectorSource | IJoinSource, fieldNames: string[], dataFilters: DataFilters, sourceQuery: Query | null | undefined, diff --git a/x-pack/plugins/maps/public/classes/layers/vector_layer/geojson_vector_layer/perform_inner_joins.ts b/x-pack/plugins/maps/public/classes/layers/vector_layer/geojson_vector_layer/perform_inner_joins.ts index 8640dbc5954aa..8a4f3e575d1c9 100644 --- a/x-pack/plugins/maps/public/classes/layers/vector_layer/geojson_vector_layer/perform_inner_joins.ts +++ b/x-pack/plugins/maps/public/classes/layers/vector_layer/geojson_vector_layer/perform_inner_joins.ts @@ -10,6 +10,7 @@ import { i18n } from '@kbn/i18n'; import { FEATURE_VISIBLE_PROPERTY_NAME } from '../../../../../common/constants'; import { DataRequestContext } from '../../../../actions'; import { JoinState } from '../types'; +import { isTermJoinSource, type ITermJoinSource } from '../../../sources/join_sources'; interface SourceResult { refreshed: boolean; @@ -77,13 +78,22 @@ export async function performInnerJoins( updateSourceData({ ...sourceResult.featureCollection }); } - const joinStatusesWithoutAnyMatches = joinStatuses.filter((joinStatus) => { + // + // term joins are easy to misconfigure. + // Users often are unaware of left values and right values and whether they allign for joining + // Provide messaging that helps users debug term joins with no matches + // + const termJoinStatusesWithoutAnyMatches = joinStatuses.filter((joinStatus) => { + if (!isTermJoinSource(joinStatus.joinState.join.getRightJoinSource())) { + return false; + } + const hasTerms = joinStatus.joinState.propertiesMap && joinStatus.joinState.propertiesMap.size > 0; return !joinStatus.joinedWithAtLeastOneFeature && hasTerms; }); - if (joinStatusesWithoutAnyMatches.length) { + if (termJoinStatusesWithoutAnyMatches.length) { function prettyPrintArray(array: unknown[]) { return array.length <= 5 ? array.join(',') @@ -94,12 +104,10 @@ export async function performInnerJoins( }); } - const joinStatus = joinStatusesWithoutAnyMatches[0]; + const joinStatus = termJoinStatusesWithoutAnyMatches[0]; const leftFieldName = await joinStatus.joinState.join.getLeftField().getLabel(); - const rightFieldName = await joinStatus.joinState.join - .getRightJoinSource() - .getTermField() - .getLabel(); + const termJoinSource = joinStatus.joinState.join.getRightJoinSource() as ITermJoinSource; + const rightFieldName = await termJoinSource.getTermField().getLabel(); const reason = joinStatus.keys.length === 0 ? i18n.translate('xpack.maps.vectorLayer.joinError.noLeftFieldValuesMsg', { diff --git a/x-pack/plugins/maps/public/classes/layers/vector_layer/mvt_vector_layer/mvt_vector_layer.test.tsx b/x-pack/plugins/maps/public/classes/layers/vector_layer/mvt_vector_layer/mvt_vector_layer.test.tsx index 82bb15c19ffca..dab086b86b472 100644 --- a/x-pack/plugins/maps/public/classes/layers/vector_layer/mvt_vector_layer/mvt_vector_layer.test.tsx +++ b/x-pack/plugins/maps/public/classes/layers/vector_layer/mvt_vector_layer/mvt_vector_layer.test.tsx @@ -25,7 +25,7 @@ import { } from '../../../../../common/descriptor_types'; import { LAYER_TYPE, SOURCE_TYPES } from '../../../../../common/constants'; import { MvtVectorLayer } from './mvt_vector_layer'; -import { ITermJoinSource } from '../../../sources/term_join_source'; +import { IJoinSource } from '../../../sources/join_sources'; const defaultConfig = { urlTemplate: 'https://example.com/{x}/{y}/{z}.pbf', @@ -178,7 +178,7 @@ describe('isLayerLoading', () => { return 'join_source_a0b0da65-5e1a-4967-9dbe-74f24391afe2'; }, getRightJoinSource: () => { - return {} as unknown as ITermJoinSource; + return {} as unknown as IJoinSource; }, } as unknown as InnerJoin, ], @@ -217,7 +217,7 @@ describe('isLayerLoading', () => { return 'join_source_a0b0da65-5e1a-4967-9dbe-74f24391afe2'; }, getRightJoinSource: () => { - return {} as unknown as ITermJoinSource; + return {} as unknown as IJoinSource; }, } as unknown as InnerJoin, ], diff --git a/x-pack/plugins/maps/public/classes/layers/vector_layer/vector_layer.tsx b/x-pack/plugins/maps/public/classes/layers/vector_layer/vector_layer.tsx index 366c9cde6eee6..a18f525e66ae0 100644 --- a/x-pack/plugins/maps/public/classes/layers/vector_layer/vector_layer.tsx +++ b/x-pack/plugins/maps/public/classes/layers/vector_layer/vector_layer.tsx @@ -27,7 +27,7 @@ import { STYLE_TYPE, VECTOR_STYLES, } from '../../../../common/constants'; -import { JoinTooltipProperty } from '../../tooltips/join_tooltip_property'; +import { TermJoinTooltipProperty } from '../../tooltips/term_join_tooltip_property'; import { DataRequestAbortError } from '../../util/data_request'; import { canSkipStyleMetaUpdate, canSkipFormattersUpdate } from '../../util/can_skip_fetch'; import { @@ -57,7 +57,8 @@ import { DataRequestContext } from '../../../actions'; import { ITooltipProperty } from '../../tooltips/tooltip_property'; import { IDynamicStyleProperty } from '../../styles/vector/properties/dynamic_style_property'; import { IESSource } from '../../sources/es_source'; -import { ITermJoinSource } from '../../sources/term_join_source'; +import type { IJoinSource, ITermJoinSource } from '../../sources/join_sources'; +import { isTermJoinSource } from '../../sources/join_sources'; import type { IESAggSource } from '../../sources/es_agg_source'; import { buildVectorRequestMeta } from '../build_vector_request_meta'; import { getJoinAggKey } from '../../../../common/get_agg_key'; @@ -430,7 +431,7 @@ export class AbstractVectorLayer extends AbstractLayer implements IVectorLayer { }: { dataRequestId: string; dynamicStyleProps: Array>; - source: IVectorSource | ITermJoinSource; + source: IVectorSource | IJoinSource; sourceQuery?: Query; style: IVectorStyle; } & DataRequestContext) { @@ -511,7 +512,7 @@ export class AbstractVectorLayer extends AbstractLayer implements IVectorLayer { }: { dataRequestId: string; fields: IField[]; - source: IVectorSource | ITermJoinSource; + source: IVectorSource | IJoinSource; } & DataRequestContext) { if (fields.length === 0) { return; @@ -983,17 +984,28 @@ export class AbstractVectorLayer extends AbstractLayer implements IVectorLayer { return this.getId() === mbSourceId; } + /* + * Replaces source property tooltips with join property tooltips + * join property tooltips allow tooltips to + * 1) Create filter from right source context + * 2) Display tooltip with right source context + */ _addJoinsToSourceTooltips(tooltipsFromSource: ITooltipProperty[]) { for (let i = 0; i < tooltipsFromSource.length; i++) { const tooltipProperty = tooltipsFromSource[i]; - const matchingJoins = []; + const matchingTermJoins: ITermJoinSource[] = []; for (let j = 0; j < this.getJoins().length; j++) { - if (this.getJoins()[j].getLeftField().getName() === tooltipProperty.getPropertyKey()) { - matchingJoins.push(this.getJoins()[j]); + const join = this.getJoins()[j]; + const joinRightSource = join.getRightJoinSource(); + if ( + isTermJoinSource(joinRightSource) && + this.getJoins()[j].getLeftField().getName() === tooltipProperty.getPropertyKey() + ) { + matchingTermJoins.push(joinRightSource as ITermJoinSource); } } - if (matchingJoins.length) { - tooltipsFromSource[i] = new JoinTooltipProperty(tooltipProperty, matchingJoins); + if (matchingTermJoins.length) { + tooltipsFromSource[i] = new TermJoinTooltipProperty(tooltipProperty, matchingTermJoins); } } } diff --git a/x-pack/plugins/maps/public/classes/sources/es_term_source/es_term_source.test.js b/x-pack/plugins/maps/public/classes/sources/join_sources/es_term_source/es_term_source.test.ts similarity index 86% rename from x-pack/plugins/maps/public/classes/sources/es_term_source/es_term_source.test.js rename to x-pack/plugins/maps/public/classes/sources/join_sources/es_term_source/es_term_source.test.ts index 173ee5bff725f..dc93153c7c411 100644 --- a/x-pack/plugins/maps/public/classes/sources/es_term_source/es_term_source.test.js +++ b/x-pack/plugins/maps/public/classes/sources/join_sources/es_term_source/es_term_source.test.ts @@ -5,25 +5,27 @@ * 2.0. */ +import { AGG_TYPE } from '../../../../../common/constants'; +import type { BucketProperties, PropertiesMap } from '../../../../../common/elasticsearch_util'; import { ESTermSource, extractPropertiesMap } from './es_term_source'; -jest.mock('../../layers/vector_layer', () => {}); +jest.mock('../../../layers/vector_layer', () => {}); const termFieldName = 'myTermField'; const sumFieldName = 'myFieldGettingSummed'; const metricExamples = [ { - type: 'sum', + type: AGG_TYPE.SUM, field: sumFieldName, label: 'my custom label', }, { // metric config is invalid beause field is missing - type: 'max', + type: AGG_TYPE.MAX, }, { // metric config is valid because "count" metric does not need to provide field - type: 'count', + type: AGG_TYPE.COUNT, label: '', // should ignore empty label fields }, ]; @@ -81,7 +83,7 @@ describe('extractPropertiesMap', () => { }; const countPropName = '__kbnjoin__count__1234'; - let propertiesMap; + let propertiesMap: PropertiesMap = new Map(); beforeAll(() => { propertiesMap = extractPropertiesMap(responseWithNumberTypes, countPropName); }); @@ -93,17 +95,17 @@ describe('extractPropertiesMap', () => { it('should extract count property', () => { const properties = propertiesMap.get('109'); - expect(properties[countPropName]).toBe(1130); + expect(properties?.[countPropName]).toBe(1130); }); it('should extract min property', () => { const properties = propertiesMap.get('109'); - expect(properties[minPropName]).toBe(36); + expect(properties?.[minPropName]).toBe(36); }); it('should extract property with value of "0"', () => { const properties = propertiesMap.get('62'); - expect(properties[minPropName]).toBe(0); + expect(properties?.[minPropName]).toBe(0); }); }); diff --git a/x-pack/plugins/maps/public/classes/sources/es_term_source/es_term_source.ts b/x-pack/plugins/maps/public/classes/sources/join_sources/es_term_source/es_term_source.ts similarity index 88% rename from x-pack/plugins/maps/public/classes/sources/es_term_source/es_term_source.ts rename to x-pack/plugins/maps/public/classes/sources/join_sources/es_term_source/es_term_source.ts index 4c7793e1b01cb..54ca553035574 100644 --- a/x-pack/plugins/maps/public/classes/sources/es_term_source/es_term_source.ts +++ b/x-pack/plugins/maps/public/classes/sources/join_sources/es_term_source/es_term_source.ts @@ -14,26 +14,26 @@ import { DEFAULT_MAX_BUCKETS_LIMIT, FIELD_ORIGIN, SOURCE_TYPES, -} from '../../../../common/constants'; -import { getJoinAggKey } from '../../../../common/get_agg_key'; -import { ESDocField } from '../../fields/es_doc_field'; -import { AbstractESAggSource } from '../es_agg_source'; +} from '../../../../../common/constants'; +import { getJoinAggKey } from '../../../../../common/get_agg_key'; +import { ESDocField } from '../../../fields/es_doc_field'; +import { AbstractESAggSource } from '../../es_agg_source'; import { getField, addFieldToDSL, extractPropertiesFromBucket, BucketProperties, -} from '../../../../common/elasticsearch_util'; +} from '../../../../../common/elasticsearch_util'; import { ESTermSourceDescriptor, VectorSourceRequestMeta, -} from '../../../../common/descriptor_types'; -import { PropertiesMap } from '../../../../common/elasticsearch_util'; -import { isValidStringConfig } from '../../util/valid_string_config'; -import { ITermJoinSource } from '../term_join_source'; -import type { IESAggSource } from '../es_agg_source'; -import { IField } from '../../fields/field'; -import { mergeExecutionContext } from '../execution_context_utils'; +} from '../../../../../common/descriptor_types'; +import { PropertiesMap } from '../../../../../common/elasticsearch_util'; +import { isValidStringConfig } from '../../../util/valid_string_config'; +import { ITermJoinSource } from '../types'; +import type { IESAggSource } from '../../es_agg_source'; +import { IField } from '../../../fields/field'; +import { mergeExecutionContext } from '../../execution_context_utils'; const TERMS_AGG_NAME = 'join'; const TERMS_BUCKET_KEYS_TO_IGNORE = ['key', 'doc_count']; @@ -71,7 +71,7 @@ export class ESTermSource extends AbstractESAggSource implements ITermJoinSource private readonly _termField: ESDocField; readonly _descriptor: ESTermSourceDescriptor; - constructor(descriptor: ESTermSourceDescriptor) { + constructor(descriptor: Partial) { const sourceDescriptor = ESTermSource.createDescriptor(descriptor); super(sourceDescriptor); this._descriptor = sourceDescriptor; diff --git a/x-pack/plugins/maps/public/classes/sources/es_term_source/index.ts b/x-pack/plugins/maps/public/classes/sources/join_sources/es_term_source/index.ts similarity index 100% rename from x-pack/plugins/maps/public/classes/sources/es_term_source/index.ts rename to x-pack/plugins/maps/public/classes/sources/join_sources/es_term_source/index.ts diff --git a/x-pack/plugins/maps/public/classes/sources/term_join_source/index.ts b/x-pack/plugins/maps/public/classes/sources/join_sources/index.ts similarity index 55% rename from x-pack/plugins/maps/public/classes/sources/term_join_source/index.ts rename to x-pack/plugins/maps/public/classes/sources/join_sources/index.ts index 78f7705104f73..c916b2befca41 100644 --- a/x-pack/plugins/maps/public/classes/sources/term_join_source/index.ts +++ b/x-pack/plugins/maps/public/classes/sources/join_sources/index.ts @@ -5,4 +5,8 @@ * 2.0. */ -export type { ITermJoinSource } from './term_join_source'; +export type { IJoinSource, ITermJoinSource } from './types'; + +export { isTermJoinSource } from './types'; +export { ESTermSource } from './es_term_source'; +export { TableSource } from './table_source'; diff --git a/x-pack/plugins/maps/public/classes/sources/table_source/index.ts b/x-pack/plugins/maps/public/classes/sources/join_sources/table_source/index.ts similarity index 100% rename from x-pack/plugins/maps/public/classes/sources/table_source/index.ts rename to x-pack/plugins/maps/public/classes/sources/join_sources/table_source/index.ts diff --git a/x-pack/plugins/maps/public/classes/sources/table_source/table_source.test.ts b/x-pack/plugins/maps/public/classes/sources/join_sources/table_source/table_source.test.ts similarity index 97% rename from x-pack/plugins/maps/public/classes/sources/table_source/table_source.test.ts rename to x-pack/plugins/maps/public/classes/sources/join_sources/table_source/table_source.test.ts index e43d3de268d31..df9c11a99daa4 100644 --- a/x-pack/plugins/maps/public/classes/sources/table_source/table_source.test.ts +++ b/x-pack/plugins/maps/public/classes/sources/join_sources/table_source/table_source.test.ts @@ -6,8 +6,8 @@ */ import { TableSource } from './table_source'; -import { FIELD_ORIGIN } from '../../../../common/constants'; -import { VectorSourceRequestMeta } from '../../../../common/descriptor_types'; +import { FIELD_ORIGIN } from '../../../../../common/constants'; +import { VectorSourceRequestMeta } from '../../../../../common/descriptor_types'; describe('TableSource', () => { describe('getName', () => { diff --git a/x-pack/plugins/maps/public/classes/sources/table_source/table_source.ts b/x-pack/plugins/maps/public/classes/sources/join_sources/table_source/table_source.ts similarity index 92% rename from x-pack/plugins/maps/public/classes/sources/table_source/table_source.ts rename to x-pack/plugins/maps/public/classes/sources/join_sources/table_source/table_source.ts index 0bfdbc90be847..ed3015dca17e4 100644 --- a/x-pack/plugins/maps/public/classes/sources/table_source/table_source.ts +++ b/x-pack/plugins/maps/public/classes/sources/join_sources/table_source/table_source.ts @@ -8,25 +8,25 @@ import { v4 as uuidv4 } from 'uuid'; import { GeoJsonProperties } from 'geojson'; import type { Query } from '@kbn/data-plugin/common'; -import { FIELD_ORIGIN, SOURCE_TYPES, VECTOR_SHAPE_TYPE } from '../../../../common/constants'; +import { FIELD_ORIGIN, SOURCE_TYPES, VECTOR_SHAPE_TYPE } from '../../../../../common/constants'; import { MapExtent, TableSourceDescriptor, VectorSourceRequestMeta, -} from '../../../../common/descriptor_types'; -import { ITermJoinSource } from '../term_join_source'; -import { BucketProperties, PropertiesMap } from '../../../../common/elasticsearch_util'; -import { IField } from '../../fields/field'; +} from '../../../../../common/descriptor_types'; +import { ITermJoinSource } from '../types'; +import { BucketProperties, PropertiesMap } from '../../../../../common/elasticsearch_util'; +import { IField } from '../../../fields/field'; import { AbstractVectorSource, BoundsRequestMeta, GeoJsonWithMeta, IVectorSource, SourceStatus, -} from '../vector_source'; -import { DataRequest } from '../../util/data_request'; -import { InlineField } from '../../fields/inline_field'; -import { ITooltipProperty, TooltipProperty } from '../../tooltips/tooltip_property'; +} from '../../vector_source'; +import { DataRequest } from '../../../util/data_request'; +import { InlineField } from '../../../fields/inline_field'; +import { ITooltipProperty, TooltipProperty } from '../../../tooltips/tooltip_property'; export class TableSource extends AbstractVectorSource implements ITermJoinSource, IVectorSource { static type = SOURCE_TYPES.TABLE_SOURCE; diff --git a/x-pack/plugins/maps/public/classes/sources/term_join_source/term_join_source.ts b/x-pack/plugins/maps/public/classes/sources/join_sources/types.ts similarity index 87% rename from x-pack/plugins/maps/public/classes/sources/term_join_source/term_join_source.ts rename to x-pack/plugins/maps/public/classes/sources/join_sources/types.ts index 30e834fdf11f5..05ec53cc14e6a 100644 --- a/x-pack/plugins/maps/public/classes/sources/term_join_source/term_join_source.ts +++ b/x-pack/plugins/maps/public/classes/sources/join_sources/types.ts @@ -15,9 +15,8 @@ import { PropertiesMap } from '../../../../common/elasticsearch_util'; import { ITooltipProperty } from '../../tooltips/tooltip_property'; import { ISource } from '../source'; -export interface ITermJoinSource extends ISource { +export interface IJoinSource extends ISource { hasCompleteConfig(): boolean; - getTermField(): IField; getWhereQuery(): Query | undefined; getPropertiesMap( requestMeta: VectorSourceRequestMeta, @@ -41,3 +40,11 @@ export interface ITermJoinSource extends ISource { ): Promise; getFieldByName(fieldName: string): IField | null; } + +export interface ITermJoinSource extends IJoinSource { + getTermField(): IField; +} + +export function isTermJoinSource(joinSource: IJoinSource) { + return 'getTermField' in (joinSource as ITermJoinSource); +} diff --git a/x-pack/plugins/maps/public/classes/tooltips/join_tooltip_property/index.ts b/x-pack/plugins/maps/public/classes/tooltips/term_join_tooltip_property/index.ts similarity index 77% rename from x-pack/plugins/maps/public/classes/tooltips/join_tooltip_property/index.ts rename to x-pack/plugins/maps/public/classes/tooltips/term_join_tooltip_property/index.ts index 4e48d96b032fe..d28cdbbf58d08 100644 --- a/x-pack/plugins/maps/public/classes/tooltips/join_tooltip_property/index.ts +++ b/x-pack/plugins/maps/public/classes/tooltips/term_join_tooltip_property/index.ts @@ -5,4 +5,4 @@ * 2.0. */ -export { JoinTooltipProperty } from './join_tooltip_property'; +export { TermJoinTooltipProperty } from './term_join_tooltip_property'; diff --git a/x-pack/plugins/maps/public/classes/tooltips/join_tooltip_property/join_key_label.tsx b/x-pack/plugins/maps/public/classes/tooltips/term_join_tooltip_property/term_join_key_label.tsx similarity index 84% rename from x-pack/plugins/maps/public/classes/tooltips/join_tooltip_property/join_key_label.tsx rename to x-pack/plugins/maps/public/classes/tooltips/term_join_tooltip_property/term_join_key_label.tsx index 624b7515c04c6..822a966814138 100644 --- a/x-pack/plugins/maps/public/classes/tooltips/join_tooltip_property/join_key_label.tsx +++ b/x-pack/plugins/maps/public/classes/tooltips/term_join_tooltip_property/term_join_key_label.tsx @@ -16,18 +16,18 @@ import React, { Component } from 'react'; import { asyncMap } from '@kbn/std'; import { i18n } from '@kbn/i18n'; import { EuiIcon, EuiToolTip } from '@elastic/eui'; -import { InnerJoin } from '../../joins/inner_join'; +import type { ITermJoinSource } from '../../sources/join_sources'; interface Props { leftFieldName: string; - innerJoins: InnerJoin[]; + termJoins: ITermJoinSource[]; } interface State { rightSourceLabels: string[]; } -export class JoinKeyLabel extends Component { +export class TermJoinKeyLabel extends Component { private _isMounted = false; state: State = { rightSourceLabels: [] }; @@ -42,9 +42,8 @@ export class JoinKeyLabel extends Component { } async _loadRightSourceLabels() { - const rightSourceLabels = await asyncMap(this.props.innerJoins, async (innerJoin) => { - const rightSource = innerJoin.getRightJoinSource(); - const termField = rightSource.getTermField(); + const rightSourceLabels = await asyncMap(this.props.termJoins, async (termJoin) => { + const termField = termJoin.getTermField(); return `'${await termField.getLabel()}'`; }); diff --git a/x-pack/plugins/maps/public/classes/tooltips/join_tooltip_property/join_tooltip_property.tsx b/x-pack/plugins/maps/public/classes/tooltips/term_join_tooltip_property/term_join_tooltip_property.tsx similarity index 74% rename from x-pack/plugins/maps/public/classes/tooltips/join_tooltip_property/join_tooltip_property.tsx rename to x-pack/plugins/maps/public/classes/tooltips/term_join_tooltip_property/term_join_tooltip_property.tsx index c6ca5e9b3d5f9..efc15e5a012a2 100644 --- a/x-pack/plugins/maps/public/classes/tooltips/join_tooltip_property/join_tooltip_property.tsx +++ b/x-pack/plugins/maps/public/classes/tooltips/term_join_tooltip_property/term_join_tooltip_property.tsx @@ -8,16 +8,16 @@ import React, { ReactNode } from 'react'; import { Filter } from '@kbn/es-query'; import { ITooltipProperty } from '../tooltip_property'; -import { InnerJoin } from '../../joins/inner_join'; -import { JoinKeyLabel } from './join_key_label'; +import { TermJoinKeyLabel } from './term_join_key_label'; +import type { ITermJoinSource } from '../../sources/join_sources'; -export class JoinTooltipProperty implements ITooltipProperty { +export class TermJoinTooltipProperty implements ITooltipProperty { private readonly _tooltipProperty: ITooltipProperty; - private readonly _innerJoins: InnerJoin[]; + private readonly _termJoins: ITermJoinSource[]; - constructor(tooltipProperty: ITooltipProperty, innerJoins: InnerJoin[]) { + constructor(tooltipProperty: ITooltipProperty, termJoins: ITermJoinSource[]) { this._tooltipProperty = tooltipProperty; - this._innerJoins = innerJoins; + this._termJoins = termJoins; } isFilterable(): boolean { @@ -30,9 +30,9 @@ export class JoinTooltipProperty implements ITooltipProperty { getPropertyName(): ReactNode { return ( - ); } @@ -50,9 +50,8 @@ export class JoinTooltipProperty implements ITooltipProperty { // only create filters for right sources. // do not create filters for left source. - for (let i = 0; i < this._innerJoins.length; i++) { - const rightSource = this._innerJoins[i].getRightJoinSource(); - const termField = rightSource.getTermField(); + for (let i = 0; i < this._termJoins.length; i++) { + const termField = this._termJoins[i].getTermField(); try { const esTooltipProperty = await termField.createTooltipProperty( this._tooltipProperty.getRawValue() From 4211e03a5f7651afeea492e44116eb614b54f00c Mon Sep 17 00:00:00 2001 From: Dzmitry Lemechko Date: Wed, 26 Apr 2023 18:33:54 +0200 Subject: [PATCH 22/73] [ftr] fix scripts/functional_tests to respect cli flags (#155734) ## Summary This [failure](https://buildkite.com/elastic/kibana-on-merge/builds/29091#01879988-ecd2-4e4f-bfb4-108939f145a1) clearly shows that `--bail` flag is ignored when passed to `scripts/functional_tests.js` script, and since `scripts/functional_tests.js --help` list these flags I think we need to fix it: ``` --include-tag Tags that suites must include to be run, can be included multiple times --exclude-tag Tags that suites must NOT include to be run, can be included multiple times --include Files that must included to be run, can be included multiple times --exclude Files that must NOT be included to be run, can be included multiple times --grep Pattern to select which tests to run --bail Stop the test run at the first failure --dry-run Report tests without executing them --updateBaselines Replace baseline screenshots with whatever is generated from the test --updateSnapshots Replace inline and file snapshots with whatever is generated from the test ``` I was able to reproduce it locally: 1. Break [test/functional/apps/console/_console.ts](test/functional/apps/console/_console.ts) by adding `expect(1).to.be(2);` in the first `it` function 2. Run `node scripts/functional_tests.js --bail --config test/functional/apps/console/config.ts` Actual: Tests continue to run after failure Expected: Stop tests after first failure It turned out `scripts/functional_test_runner.js` respects the flags so I just copied the logic from [packages/kbn-test/src/functional_test_runner/cli.ts](https://github.com/elastic/kibana/blob/main/packages/kbn-test/src/functional_test_runner/cli.ts#L41-L63) Let me know if you think we need to add jest tests. Tested: ``` node scripts/functional_tests.js --bail --config test/functional/apps/console/config.ts --grep "multiple requests output" ``` --- .../src/functional_tests/run_tests/flags.ts | 8 +++---- .../functional_tests/run_tests/run_tests.ts | 23 ++++++++++++++++++- 2 files changed, 26 insertions(+), 5 deletions(-) diff --git a/packages/kbn-test/src/functional_tests/run_tests/flags.ts b/packages/kbn-test/src/functional_tests/run_tests/flags.ts index 5d7fffc2a965b..9f91bf2728cbe 100644 --- a/packages/kbn-test/src/functional_tests/run_tests/flags.ts +++ b/packages/kbn-test/src/functional_tests/run_tests/flags.ts @@ -78,12 +78,12 @@ export function parseFlags(flags: FlagsReader) { installDir: flags.path('kibana-install-dir'), grep: flags.string('grep'), suiteTags: { - include: flags.arrayOfStrings('include-tag'), - exclude: flags.arrayOfStrings('exclude-tag'), + include: flags.arrayOfStrings('include-tag') ?? [], + exclude: flags.arrayOfStrings('exclude-tag') ?? [], }, suiteFilters: { - include: flags.arrayOfPaths('include'), - exclude: flags.arrayOfPaths('exclude'), + include: flags.arrayOfPaths('include') ?? [], + exclude: flags.arrayOfPaths('exclude') ?? [], }, }; } diff --git a/packages/kbn-test/src/functional_tests/run_tests/run_tests.ts b/packages/kbn-test/src/functional_tests/run_tests/run_tests.ts index e936264d8bf04..b8edfeadbdf08 100644 --- a/packages/kbn-test/src/functional_tests/run_tests/run_tests.ts +++ b/packages/kbn-test/src/functional_tests/run_tests/run_tests.ts @@ -36,6 +36,27 @@ export async function runTests(log: ToolingLog, options: RunTestsOptions) { log.warning('❗️❗️❗️'); } + const settingOverrides = { + mochaOpts: { + bail: options.bail, + dryRun: options.dryRun, + grep: options.grep, + }, + kbnTestServer: { + installDir: options.installDir, + }, + suiteFiles: { + include: options.suiteFilters.include, + exclude: options.suiteFilters.exclude, + }, + suiteTags: { + include: options.suiteTags.include, + exclude: options.suiteTags.exclude, + }, + updateBaselines: options.updateBaselines, + updateSnapshots: options.updateSnapshots, + }; + for (const [i, path] of options.configs.entries()) { await log.indent(0, async () => { if (options.configs.length > 1) { @@ -43,7 +64,7 @@ export async function runTests(log: ToolingLog, options: RunTestsOptions) { log.write(`--- [${progress}] Running ${Path.relative(REPO_ROOT, path)}`); } - const config = await readConfigFile(log, options.esVersion, path); + const config = await readConfigFile(log, options.esVersion, path, settingOverrides); const hasTests = await checkForEnabledTestsInFtrConfig({ config, From 3dfaf432f0abda60d824c853149910d1e484af1f Mon Sep 17 00:00:00 2001 From: Marco Antonio Ghiani Date: Wed, 26 Apr 2023 18:43:36 +0200 Subject: [PATCH 23/73] [Infrastructure UI] Fix hosts sticky search bar position (#155896) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 📓 Summary The sticky search bar was miscalculating the top position because it was referring to the wrong root element. --------- Co-authored-by: Marco Antonio Ghiani Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> --- .../metrics/hosts/components/search_bar/unified_search_bar.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/x-pack/plugins/infra/public/pages/metrics/hosts/components/search_bar/unified_search_bar.tsx b/x-pack/plugins/infra/public/pages/metrics/hosts/components/search_bar/unified_search_bar.tsx index ef515cc018839..2ae8d8743b37d 100644 --- a/x-pack/plugins/infra/public/pages/metrics/hosts/components/search_bar/unified_search_bar.tsx +++ b/x-pack/plugins/infra/public/pages/metrics/hosts/components/search_bar/unified_search_bar.tsx @@ -16,7 +16,6 @@ import { EuiFlexItem, } from '@elastic/eui'; import { css } from '@emotion/react'; -import { METRICS_APP_DATA_TEST_SUBJ } from '../../../../../apps/metrics_app'; import { useKibanaContextForPlugin } from '../../../../../hooks/use_kibana'; import { useUnifiedSearchContext } from '../../hooks/use_unified_search'; import { ControlsContent } from './controls_content'; @@ -101,7 +100,7 @@ const StickyContainer = (props: { children: React.ReactNode }) => { const { euiTheme } = useEuiTheme(); const top = useMemo(() => { - const wrapper = document.querySelector(`[data-test-subj="${METRICS_APP_DATA_TEST_SUBJ}"]`); + const wrapper = document.querySelector(`[data-test-subj="kibanaChrome"]`); if (!wrapper) { return `calc(${euiTheme.size.xxxl} * 2)`; } From 5c1912ccc59f78685c573fdeea5049e12f494ba0 Mon Sep 17 00:00:00 2001 From: Alexi Doak <109488926+doakalexi@users.noreply.github.com> Date: Wed, 26 Apr 2023 10:03:06 -0700 Subject: [PATCH 24/73] [ResponseOps][Window Maintenance] Update licensing on the front end for maintenance windows (#155664) Resolves https://github.com/elastic/kibana/issues/153974 ## Summary Adds a platinum license check for maintenance windows on the front end. When you navigate to maintenance windows the following prompt will render: Screen Shot 2023-04-24 at 12 50 55 PM ### 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 --------- Co-authored-by: Lisa Cawley --- .../application/maintenance_windows.tsx | 39 +++++++---- .../public/hooks/use_license.test.tsx | 55 +++++++++++++++ .../alerting/public/hooks/use_license.tsx | 34 +++++++++ .../alerting/public/lib/test_utils.tsx | 20 +++++- .../components/license_prompt.test.tsx | 27 ++++++++ .../components/license_prompt.tsx | 69 +++++++++++++++++++ .../pages/maintenance_windows/index.tsx | 11 ++- .../pages/maintenance_windows/translations.ts | 28 ++++++++ 8 files changed, 265 insertions(+), 18 deletions(-) create mode 100644 x-pack/plugins/alerting/public/hooks/use_license.test.tsx create mode 100644 x-pack/plugins/alerting/public/hooks/use_license.tsx create mode 100644 x-pack/plugins/alerting/public/pages/maintenance_windows/components/license_prompt.test.tsx create mode 100644 x-pack/plugins/alerting/public/pages/maintenance_windows/components/license_prompt.tsx diff --git a/x-pack/plugins/alerting/public/application/maintenance_windows.tsx b/x-pack/plugins/alerting/public/application/maintenance_windows.tsx index 4005ee739a12b..58cc533d78a28 100644 --- a/x-pack/plugins/alerting/public/application/maintenance_windows.tsx +++ b/x-pack/plugins/alerting/public/application/maintenance_windows.tsx @@ -7,7 +7,7 @@ import React, { Suspense } from 'react'; import ReactDOM from 'react-dom'; -import { Redirect, Router, Switch } from 'react-router-dom'; +import { Router, Switch } from 'react-router-dom'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { Route } from '@kbn/shared-ux-router'; import { CoreStart } from '@kbn/core/public'; @@ -18,6 +18,7 @@ import { ManagementAppMountParams } from '@kbn/management-plugin/public'; import { EuiLoadingSpinner } from '@elastic/eui'; import { AlertingPluginStart } from '../plugin'; import { paths } from '../config'; +import { useLicense } from '../hooks/use_license'; const MaintenanceWindowsLazy: React.FC = React.lazy(() => import('../pages/maintenance_windows')); const MaintenanceWindowsCreateLazy: React.FC = React.lazy( @@ -28,27 +29,39 @@ const MaintenanceWindowsEditLazy: React.FC = React.lazy( ); const App = React.memo(() => { + const { isAtLeastPlatinum } = useLicense(); + const hasLicense = isAtLeastPlatinum(); + return ( - <> - - - }> - - - - + + {hasLicense ? ( + }> - + ) : null} + {hasLicense ? ( + }> - - - + ) : null} + + }> + + + + ); }); App.displayName = 'App'; diff --git a/x-pack/plugins/alerting/public/hooks/use_license.test.tsx b/x-pack/plugins/alerting/public/hooks/use_license.test.tsx new file mode 100644 index 0000000000000..0611a6ba86aec --- /dev/null +++ b/x-pack/plugins/alerting/public/hooks/use_license.test.tsx @@ -0,0 +1,55 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { licensingMock } from '@kbn/licensing-plugin/public/mocks'; +import { renderHook } from '@testing-library/react-hooks'; +import { useLicense } from './use_license'; +import { AppMockRenderer, createAppMockRenderer } from '../lib/test_utils'; + +let appMockRenderer: AppMockRenderer; + +describe('useLicense', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + describe('isAtLeastPlatinum', () => { + it('returns true on a valid platinum license', () => { + const license = licensingMock.createLicense({ + license: { type: 'platinum' }, + }); + appMockRenderer = createAppMockRenderer({ license }); + + const { result } = renderHook( + () => { + return useLicense(); + }, + { + wrapper: appMockRenderer.AppWrapper, + } + ); + + expect(result.current.isAtLeastPlatinum()).toBeTruthy(); + }); + + it('returns false on a valid gold license', () => { + const license = licensingMock.createLicense({ + license: { type: 'gold' }, + }); + appMockRenderer = createAppMockRenderer({ license }); + + const { result } = renderHook( + () => { + return useLicense(); + }, + { wrapper: appMockRenderer.AppWrapper } + ); + + expect(result.current.isAtLeastPlatinum()).toBeFalsy(); + }); + }); +}); diff --git a/x-pack/plugins/alerting/public/hooks/use_license.tsx b/x-pack/plugins/alerting/public/hooks/use_license.tsx new file mode 100644 index 0000000000000..b9d299776e348 --- /dev/null +++ b/x-pack/plugins/alerting/public/hooks/use_license.tsx @@ -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 { ILicense, LicenseType } from '@kbn/licensing-plugin/public'; +import { useCallback } from 'react'; +import useObservable from 'react-use/lib/useObservable'; +import { Observable } from 'rxjs'; +import { useKibana } from '../utils/kibana_react'; + +interface UseLicenseReturnValue { + isAtLeastPlatinum: () => boolean; +} + +export const useLicense = (): UseLicenseReturnValue => { + const { licensing } = useKibana().services; + const license = useObservable(licensing?.license$ ?? new Observable(), null); + + const isAtLeast = useCallback( + (level: LicenseType): boolean => { + return !!license && license.isAvailable && license.isActive && license.hasAtLeast(level); + }, + [license] + ); + + const isAtLeastPlatinum = useCallback(() => isAtLeast('platinum'), [isAtLeast]); + + return { + isAtLeastPlatinum, + }; +}; diff --git a/x-pack/plugins/alerting/public/lib/test_utils.tsx b/x-pack/plugins/alerting/public/lib/test_utils.tsx index 2dfd1f37066bf..56485d7c88ad1 100644 --- a/x-pack/plugins/alerting/public/lib/test_utils.tsx +++ b/x-pack/plugins/alerting/public/lib/test_utils.tsx @@ -6,7 +6,7 @@ */ import React from 'react'; -import { of } from 'rxjs'; +import { of, BehaviorSubject } from 'rxjs'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { I18nProvider } from '@kbn/i18n-react'; import { KibanaContextProvider, KibanaThemeProvider } from '@kbn/kibana-react-plugin/public'; @@ -14,11 +14,17 @@ import { render as reactRender, RenderOptions, RenderResult } from '@testing-lib import { CoreStart } from '@kbn/core/public'; import { coreMock } from '@kbn/core/public/mocks'; import { euiDarkVars } from '@kbn/ui-theme'; +import type { ILicense } from '@kbn/licensing-plugin/public'; +import { licensingMock } from '@kbn/licensing-plugin/public/mocks'; /* eslint-disable no-console */ type UiRender = (ui: React.ReactElement, options?: RenderOptions) => RenderResult; +interface AppMockRendererArgs { + license?: ILicense | null; +} + export interface AppMockRenderer { render: UiRender; coreStart: CoreStart; @@ -26,9 +32,11 @@ export interface AppMockRenderer { AppWrapper: React.FC<{ children: React.ReactElement }>; } -export const createAppMockRenderer = (): AppMockRenderer => { +export const createAppMockRenderer = ({ license }: AppMockRendererArgs = {}): AppMockRenderer => { const theme$ = of({ eui: euiDarkVars, darkMode: true }); + const licensingPluginMock = licensingMock.createStart(); + const queryClient = new QueryClient({ defaultOptions: { queries: { @@ -46,7 +54,13 @@ export const createAppMockRenderer = (): AppMockRenderer => { }, }); const core = coreMock.createStart(); - const services = { ...core }; + const services = { + ...core, + licensing: + license != null + ? { ...licensingPluginMock, license$: new BehaviorSubject(license) } + : licensingPluginMock, + }; const AppWrapper: React.FC<{ children: React.ReactElement }> = React.memo(({ children }) => ( diff --git a/x-pack/plugins/alerting/public/pages/maintenance_windows/components/license_prompt.test.tsx b/x-pack/plugins/alerting/public/pages/maintenance_windows/components/license_prompt.test.tsx new file mode 100644 index 0000000000000..3a5ecc97ae1dc --- /dev/null +++ b/x-pack/plugins/alerting/public/pages/maintenance_windows/components/license_prompt.test.tsx @@ -0,0 +1,27 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { AppMockRenderer, createAppMockRenderer } from '../../../lib/test_utils'; +import { LicensePrompt } from './license_prompt'; + +describe('LicensePrompt', () => { + let appMockRenderer: AppMockRenderer; + + beforeEach(() => { + jest.clearAllMocks(); + appMockRenderer = createAppMockRenderer(); + }); + + test('it renders', () => { + const result = appMockRenderer.render(); + + expect(result.getByTestId('license-prompt-title')).toBeInTheDocument(); + expect(result.getByTestId('license-prompt-upgrade')).toBeInTheDocument(); + expect(result.getByTestId('license-prompt-trial')).toBeInTheDocument(); + }); +}); diff --git a/x-pack/plugins/alerting/public/pages/maintenance_windows/components/license_prompt.tsx b/x-pack/plugins/alerting/public/pages/maintenance_windows/components/license_prompt.tsx new file mode 100644 index 0000000000000..61de0593e387b --- /dev/null +++ b/x-pack/plugins/alerting/public/pages/maintenance_windows/components/license_prompt.tsx @@ -0,0 +1,69 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { + EuiButton, + EuiButtonEmpty, + EuiFlexGroup, + EuiFlexItem, + EuiPageTemplate, + EuiText, +} from '@elastic/eui'; +import * as i18n from '../translations'; +import { useKibana } from '../../../utils/kibana_react'; + +const title =

{i18n.UPGRADE_TO_PLATINUM}

; + +export const LicensePrompt = React.memo(() => { + const { application } = useKibana().services; + + return ( + + + +

{i18n.UPGRADE_TO_PLATINUM_SUBTITLE}

+
+
+ + + + + {i18n.UPGRADE_SUBSCRIPTION} + + , + + + + {i18n.START_TRIAL} + + , + + + + + } + /> + ); +}); +LicensePrompt.displayName = 'LicensePrompt'; diff --git a/x-pack/plugins/alerting/public/pages/maintenance_windows/index.tsx b/x-pack/plugins/alerting/public/pages/maintenance_windows/index.tsx index fa9b54122562d..ac6d0b5534b9a 100644 --- a/x-pack/plugins/alerting/public/pages/maintenance_windows/index.tsx +++ b/x-pack/plugins/alerting/public/pages/maintenance_windows/index.tsx @@ -26,9 +26,13 @@ import { MaintenanceWindowsList } from './components/maintenance_windows_list'; import { useFindMaintenanceWindows } from '../../hooks/use_find_maintenance_windows'; import { CenterJustifiedSpinner } from './components/center_justified_spinner'; import { ExperimentalBadge } from './components/page_header'; +import { useLicense } from '../../hooks/use_license'; +import { LicensePrompt } from './components/license_prompt'; export const MaintenanceWindowsPage = React.memo(() => { const { docLinks } = useKibana().services; + const { isAtLeastPlatinum } = useLicense(); + const { navigateToCreateMaintenanceWindow } = useCreateMaintenanceWindowNavigation(); const { isLoading, maintenanceWindows, refetch } = useFindMaintenanceWindows(); @@ -42,6 +46,7 @@ export const MaintenanceWindowsPage = React.memo(() => { const refreshData = useCallback(() => refetch(), [refetch]); const showEmptyPrompt = !isLoading && maintenanceWindows.length === 0; + const hasLicense = isAtLeastPlatinum(); if (isLoading) { return ; @@ -66,7 +71,7 @@ export const MaintenanceWindowsPage = React.memo(() => {

{i18n.MAINTENANCE_WINDOWS_DESCRIPTION}

- {!showEmptyPrompt ? ( + {!showEmptyPrompt && hasLicense ? ( {i18n.CREATE_NEW_BUTTON} @@ -74,7 +79,9 @@ export const MaintenanceWindowsPage = React.memo(() => { ) : null} - {showEmptyPrompt ? ( + {!hasLicense ? ( + + ) : showEmptyPrompt ? ( ) : ( <> diff --git a/x-pack/plugins/alerting/public/pages/maintenance_windows/translations.ts b/x-pack/plugins/alerting/public/pages/maintenance_windows/translations.ts index 65f24411a2a1a..7e0c2fa484f7a 100644 --- a/x-pack/plugins/alerting/public/pages/maintenance_windows/translations.ts +++ b/x-pack/plugins/alerting/public/pages/maintenance_windows/translations.ts @@ -568,3 +568,31 @@ export const EXPERIMENTAL_DESCRIPTION = i18n.translate( export const UPCOMING = i18n.translate('xpack.alerting.maintenanceWindows.upcoming', { defaultMessage: 'Upcoming', }); + +export const UPGRADE_TO_PLATINUM = i18n.translate( + 'xpack.alerting.maintenanceWindows.licenseCallout.updgradeToPlatinumTitle', + { + defaultMessage: 'Maintenance Windows are a subscription feature.', + } +); + +export const UPGRADE_TO_PLATINUM_SUBTITLE = i18n.translate( + 'xpack.alerting.maintenanceWindows.licenseCallout.upgradeToPlatinumSubtitle', + { + defaultMessage: 'Select an option to unlock it.', + } +); + +export const UPGRADE_SUBSCRIPTION = i18n.translate( + 'xpack.alerting.maintenanceWindows.licenseCallout.upgradeSubscription', + { + defaultMessage: 'Upgrade subscription', + } +); + +export const START_TRIAL = i18n.translate( + 'xpack.alerting.maintenanceWindows.licenseCallout.startTrial', + { + defaultMessage: 'Start trial', + } +); From ab4ae2e67c41283c5bedf4695f5420c0feb49904 Mon Sep 17 00:00:00 2001 From: James Gowdy Date: Wed, 26 Apr 2023 18:09:07 +0100 Subject: [PATCH 25/73] [ML] Fixing shared trained models types (#155822) Update to https://github.com/elastic/kibana/pull/154974, types were missing from update. Also shares the `putTrainedModel` function. --- .../routes/enterprise_search/indices.test.ts | 12 +++++++ .../plugins/ml/server/lib/ml_client/types.ts | 5 ++- .../providers/trained_models.ts | 33 +++++++++++++++++-- 3 files changed, 47 insertions(+), 3 deletions(-) diff --git a/x-pack/plugins/enterprise_search/server/routes/enterprise_search/indices.test.ts b/x-pack/plugins/enterprise_search/server/routes/enterprise_search/indices.test.ts index 92977df6021d6..4875c7872a20c 100644 --- a/x-pack/plugins/enterprise_search/server/routes/enterprise_search/indices.test.ts +++ b/x-pack/plugins/enterprise_search/server/routes/enterprise_search/indices.test.ts @@ -166,6 +166,12 @@ describe('Enterprise Search Managed Indices', () => { mockTrainedModelsProvider = { getTrainedModels: jest.fn(), getTrainedModelsStats: jest.fn(), + startTrainedModelDeployment: jest.fn(), + stopTrainedModelDeployment: jest.fn(), + inferTrainedModel: jest.fn(), + deleteTrainedModel: jest.fn(), + updateTrainedModelDeployment: jest.fn(), + putTrainedModel: jest.fn(), } as MlTrainedModels; mockMl = { @@ -1060,6 +1066,12 @@ describe('Enterprise Search Managed Indices', () => { mockTrainedModelsProvider = { getTrainedModels: jest.fn(), getTrainedModelsStats: jest.fn(), + startTrainedModelDeployment: jest.fn(), + stopTrainedModelDeployment: jest.fn(), + inferTrainedModel: jest.fn(), + deleteTrainedModel: jest.fn(), + updateTrainedModelDeployment: jest.fn(), + putTrainedModel: jest.fn(), } as MlTrainedModels; mockMl = { diff --git a/x-pack/plugins/ml/server/lib/ml_client/types.ts b/x-pack/plugins/ml/server/lib/ml_client/types.ts index bba5e9b49a0ac..88d1475b23ff7 100644 --- a/x-pack/plugins/ml/server/lib/ml_client/types.ts +++ b/x-pack/plugins/ml/server/lib/ml_client/types.ts @@ -13,12 +13,15 @@ export interface UpdateTrainedModelDeploymentRequest { model_id: string; number_of_allocations: number; } +export interface UpdateTrainedModelDeploymentResponse { + acknowledge: boolean; +} export interface MlClient extends OrigMlClient { anomalySearch: ReturnType['anomalySearch']; updateTrainedModelDeployment: ( payload: UpdateTrainedModelDeploymentRequest - ) => Promise<{ acknowledge: boolean }>; + ) => Promise; } export type MlClientParams = diff --git a/x-pack/plugins/ml/server/shared_services/providers/trained_models.ts b/x-pack/plugins/ml/server/shared_services/providers/trained_models.ts index 808b1a2f0d4a4..ca2b08702ce72 100644 --- a/x-pack/plugins/ml/server/shared_services/providers/trained_models.ts +++ b/x-pack/plugins/ml/server/shared_services/providers/trained_models.ts @@ -7,7 +7,10 @@ import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import type { KibanaRequest, SavedObjectsClientContract } from '@kbn/core/server'; -import { UpdateTrainedModelDeploymentRequest } from '../../lib/ml_client/types'; +import type { + UpdateTrainedModelDeploymentRequest, + UpdateTrainedModelDeploymentResponse, +} from '../../lib/ml_client/types'; import type { GetGuards } from '../shared_services'; export interface TrainedModelsProvider { @@ -21,6 +24,24 @@ export interface TrainedModelsProvider { getTrainedModelsStats( params: estypes.MlGetTrainedModelsStatsRequest ): Promise; + startTrainedModelDeployment( + params: estypes.MlStartTrainedModelDeploymentRequest + ): Promise; + stopTrainedModelDeployment( + params: estypes.MlStopTrainedModelDeploymentRequest + ): Promise; + inferTrainedModel( + params: estypes.MlInferTrainedModelRequest + ): Promise; + deleteTrainedModel( + params: estypes.MlDeleteTrainedModelRequest + ): Promise; + updateTrainedModelDeployment( + params: UpdateTrainedModelDeploymentRequest + ): Promise; + putTrainedModel( + params: estypes.MlPutTrainedModelRequest + ): Promise; }; } @@ -80,11 +101,19 @@ export function getTrainedModelsProvider(getGuards: GetGuards): TrainedModelsPro async updateTrainedModelDeployment(params: UpdateTrainedModelDeploymentRequest) { return await guards .isFullLicense() - .hasMlCapabilities(['canStartStopTrainedModels']) + .hasMlCapabilities(['canCreateTrainedModels']) .ok(async ({ mlClient }) => { return mlClient.updateTrainedModelDeployment(params); }); }, + async putTrainedModel(params: estypes.MlPutTrainedModelRequest) { + return await guards + .isFullLicense() + .hasMlCapabilities(['canCreateTrainedModels']) + .ok(async ({ mlClient }) => { + return mlClient.putTrainedModel(params); + }); + }, }; }, }; From 1dcf1f86a856a7251df7bd06b47820ed64d033b8 Mon Sep 17 00:00:00 2001 From: Coen Warmer Date: Wed, 26 Apr 2023 19:20:43 +0200 Subject: [PATCH 26/73] [SLO][SLO Detail] Make SLO Detail > Header Actions menu and SLO List > SLO List item > Actions menu more consistent (#155868) Co-authored-by: Kevin Delemme Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> --- .../slo/feedback_button/feedback_button.tsx | 9 +- .../slo_details/components/header_control.tsx | 126 +++++++++++++++--- .../pages/slo_details/slo_details.test.tsx | 31 +++++ .../public/pages/slo_details/slo_details.tsx | 13 +- .../badges/slo_indicator_type_badge.tsx | 4 +- .../slo_delete_confirmation_modal.tsx | 3 + .../pages/slos/components/slo_list_item.tsx | 8 +- .../observability/public/pages/slos/slos.tsx | 4 +- 8 files changed, 163 insertions(+), 35 deletions(-) diff --git a/x-pack/plugins/observability/public/components/slo/feedback_button/feedback_button.tsx b/x-pack/plugins/observability/public/components/slo/feedback_button/feedback_button.tsx index f72e4b008f390..dbd45bf74dee4 100644 --- a/x-pack/plugins/observability/public/components/slo/feedback_button/feedback_button.tsx +++ b/x-pack/plugins/observability/public/components/slo/feedback_button/feedback_button.tsx @@ -5,16 +5,21 @@ * 2.0. */ +import React from 'react'; import { EuiButton } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import React from 'react'; const SLO_FEEDBACK_LINK = 'https://ela.st/slo-feedback'; -export function FeedbackButton() { +interface Props { + disabled?: boolean; +} + +export function FeedbackButton({ disabled }: Props) { return ( (false); + const [isDeleteConfirmationModalOpen, setDeleteConfirmationModalOpen] = useState(false); + + const { mutateAsync: cloneSlo } = useCloneSlo(); + const isDeleting = Boolean(useIsMutating(['deleteSlo', slo?.id])); const handleActionsClick = () => setIsPopoverOpen((value) => !value); const closePopover = () => setIsPopoverOpen(false); @@ -95,6 +108,40 @@ export function HeaderControl({ isLoading, slo }: Props) { } }; + const handleClone = async () => { + if (slo) { + setIsPopoverOpen(false); + + const newSlo = transformValuesToCreateSLOInput( + transformSloResponseToCreateSloInput({ ...slo, name: `[Copy] ${slo.name}` })! + ); + + await cloneSlo({ slo: newSlo, idToCopyFrom: slo.id }); + + toasts.addSuccess( + i18n.translate('xpack.observability.slo.sloDetails.headerControl.cloneSuccess', { + defaultMessage: 'Successfully created {name}', + values: { name: newSlo.name }, + }) + ); + + navigateToUrl(basePath.prepend(paths.observability.slos)); + } + }; + + const handleDelete = () => { + setDeleteConfirmationModalOpen(true); + setIsPopoverOpen(false); + }; + + const handleDeleteCancel = () => { + setDeleteConfirmationModalOpen(false); + }; + + const handleDeleteSuccess = () => { + navigateToUrl(basePath.prepend(paths.observability.slos)); + }; + return ( <> {i18n.translate('xpack.observability.slo.sloDetails.headerControl.actions', { defaultMessage: 'Actions', @@ -118,11 +165,12 @@ export function HeaderControl({ isLoading, slo }: Props) { closePopover={closePopover} > @@ -133,6 +181,7 @@ export function HeaderControl({ isLoading, slo }: Props) { @@ -146,6 +195,7 @@ export function HeaderControl({ isLoading, slo }: Props) { @@ -153,24 +203,50 @@ export function HeaderControl({ isLoading, slo }: Props) { defaultMessage: 'Manage rules', })} , - ].concat( - !!slo && isApmIndicatorType(slo.indicator.type) - ? [ - - {i18n.translate( - 'xpack.observability.slos.sloDetails.headerControl.exploreInApm', - { - defaultMessage: 'Explore in APM', - } - )} - , - ] - : [] - )} + ] + .concat( + !!slo && isApmIndicatorType(slo.indicator.type) ? ( + + {i18n.translate( + 'xpack.observability.slos.sloDetails.headerControl.exploreInApm', + { + defaultMessage: 'Service details', + } + )} + + ) : ( + [] + ) + ) + .concat( + + {i18n.translate('xpack.observability.slo.slo.item.actions.clone', { + defaultMessage: 'Clone', + })} + , + + {i18n.translate('xpack.observability.slo.slo.item.actions.delete', { + defaultMessage: 'Delete', + })} + + )} /> @@ -183,6 +259,14 @@ export function HeaderControl({ isLoading, slo }: Props) { initialValues={{ name: `${slo.name} burn rate`, params: { sloId: slo.id } }} /> ) : null} + + {slo && isDeleteConfirmationModalOpen ? ( + + ) : null} ); } diff --git a/x-pack/plugins/observability/public/pages/slo_details/slo_details.test.tsx b/x-pack/plugins/observability/public/pages/slo_details/slo_details.test.tsx index 9cd8836c6cdf7..1dc86a2901a44 100644 --- a/x-pack/plugins/observability/public/pages/slo_details/slo_details.test.tsx +++ b/x-pack/plugins/observability/public/pages/slo_details/slo_details.test.tsx @@ -61,6 +61,12 @@ const mockKibana = () => { prepend: mockBasePathPrepend, }, }, + notifications: { + toasts: { + addSuccess: jest.fn(), + addError: jest.fn(), + }, + }, share: { url: { locators: { @@ -199,6 +205,31 @@ describe('SLO Details Page', () => { render(); fireEvent.click(screen.getByTestId('o11yHeaderControlActionsButton')); + expect(screen.queryByTestId('sloDetailsHeaderControlPopoverManageRules')).toBeTruthy(); + }); + + it("renders a 'Clone' button under actions menu", async () => { + const slo = buildSlo(); + useParamsMock.mockReturnValue(slo.id); + useFetchSloDetailsMock.mockReturnValue({ isLoading: false, slo }); + useLicenseMock.mockReturnValue({ hasAtLeast: () => true }); + + render(); + + fireEvent.click(screen.getByTestId('o11yHeaderControlActionsButton')); + expect(screen.queryByTestId('sloDetailsHeaderControlPopoverClone')).toBeTruthy(); + }); + + it("renders a 'Delete' button under actions menu", async () => { + const slo = buildSlo(); + useParamsMock.mockReturnValue(slo.id); + useFetchSloDetailsMock.mockReturnValue({ isLoading: false, slo }); + useLicenseMock.mockReturnValue({ hasAtLeast: () => true }); + + render(); + + fireEvent.click(screen.getByTestId('o11yHeaderControlActionsButton')); + expect(screen.queryByTestId('sloDetailsHeaderControlPopoverDelete')).toBeTruthy(); const manageRulesButton = screen.queryByTestId('sloDetailsHeaderControlPopoverManageRules'); expect(manageRulesButton).toBeTruthy(); diff --git a/x-pack/plugins/observability/public/pages/slo_details/slo_details.tsx b/x-pack/plugins/observability/public/pages/slo_details/slo_details.tsx index 48401ca29f94e..29a7f573e3c14 100644 --- a/x-pack/plugins/observability/public/pages/slo_details/slo_details.tsx +++ b/x-pack/plugins/observability/public/pages/slo_details/slo_details.tsx @@ -7,6 +7,7 @@ import React, { useState } from 'react'; import { useParams } from 'react-router-dom'; +import { useIsMutating } from '@tanstack/react-query'; import { EuiBreadcrumbProps } from '@elastic/eui/src/components/breadcrumbs/breadcrumb'; import { EuiLoadingSpinner } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; @@ -44,6 +45,8 @@ export function SloDetailsPage() { const { isLoading, slo } = useFetchSloDetails({ sloId, shouldRefetch: isAutoRefreshing }); + const isCloningOrDeleting = Boolean(useIsMutating()); + useBreadcrumbs(getBreadcrumbs(basePath, slo)); const isSloNotFound = !isLoading && slo === undefined; @@ -55,6 +58,8 @@ export function SloDetailsPage() { navigateToUrl(basePath.prepend(paths.observability.slos)); } + const isPerformingAction = isLoading || isCloningOrDeleting; + const handleToggleAutoRefresh = () => { setIsAutoRefreshing(!isAutoRefreshing); }; @@ -62,15 +67,15 @@ export function SloDetailsPage() { return ( , + pageTitle: , rightSideItems: [ - , + , , - , + , ], bottomBorder: false, }} diff --git a/x-pack/plugins/observability/public/pages/slos/components/badges/slo_indicator_type_badge.tsx b/x-pack/plugins/observability/public/pages/slos/components/badges/slo_indicator_type_badge.tsx index 87f005e2fa5c0..ad73af3d73bfa 100644 --- a/x-pack/plugins/observability/public/pages/slos/components/badges/slo_indicator_type_badge.tsx +++ b/x-pack/plugins/observability/public/pages/slos/components/badges/slo_indicator_type_badge.tsx @@ -63,7 +63,7 @@ export function SloIndicatorTypeBadge({ slo }: Props) { @@ -73,7 +73,7 @@ export function SloIndicatorTypeBadge({ slo }: Props) { onClickAriaLabel={i18n.translate( 'xpack.observability.slo.indicatorTypeBadge.exploreInApm', { - defaultMessage: 'Explore {service} in APM', + defaultMessage: 'View {service} details', values: { service: slo.indicator.params.service }, } )} diff --git a/x-pack/plugins/observability/public/pages/slos/components/slo_delete_confirmation_modal.tsx b/x-pack/plugins/observability/public/pages/slos/components/slo_delete_confirmation_modal.tsx index 4a292bef8c14b..b1b2d341a9bd8 100644 --- a/x-pack/plugins/observability/public/pages/slos/components/slo_delete_confirmation_modal.tsx +++ b/x-pack/plugins/observability/public/pages/slos/components/slo_delete_confirmation_modal.tsx @@ -15,11 +15,13 @@ import { useDeleteSlo } from '../../../hooks/slo/use_delete_slo'; export interface SloDeleteConfirmationModalProps { slo: SLOWithSummaryResponse; onCancel: () => void; + onSuccess?: () => void; } export function SloDeleteConfirmationModal({ slo: { id, name }, onCancel, + onSuccess, }: SloDeleteConfirmationModalProps) { const { notifications: { toasts }, @@ -31,6 +33,7 @@ export function SloDeleteConfirmationModal({ if (isSuccess) { toasts.addSuccess(getDeleteSuccesfulMessage(name)); + onSuccess?.(); } if (isError) { 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 0e58573b740c2..3fe0f5d31f543 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 @@ -184,12 +184,12 @@ export function SloListItem({ onClick={handleClickActions} /> } - panelPaddingSize="none" + panelPaddingSize="m" closePopover={handleClickActions} isOpen={isActionsPopoverOpen} > {i18n.translate('xpack.observability.slo.slo.item.actions.createRule', { - defaultMessage: 'Create new Alert rule', + defaultMessage: 'Create new alert rule', })} , Date: Wed, 26 Apr 2023 18:38:17 +0100 Subject: [PATCH 27/73] skip failing version bump suite (#155914) --- .../test/functional/apps/ingest_pipelines/ingest_pipelines.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/x-pack/test/functional/apps/ingest_pipelines/ingest_pipelines.ts b/x-pack/test/functional/apps/ingest_pipelines/ingest_pipelines.ts index baae09f47c530..2aaee38790403 100644 --- a/x-pack/test/functional/apps/ingest_pipelines/ingest_pipelines.ts +++ b/x-pack/test/functional/apps/ingest_pipelines/ingest_pipelines.ts @@ -26,7 +26,8 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { const es = getService('es'); const security = getService('security'); - describe('Ingest Pipelines', function () { + // FAILING VERSION BUMP: https://github.com/elastic/kibana/issues/155914 + describe.skip('Ingest Pipelines', function () { this.tags('smoke'); before(async () => { await security.testUser.setRoles(['ingest_pipelines_user']); From 6690c445e3302b9fe04237a1dc77c5b19f81931b Mon Sep 17 00:00:00 2001 From: Clint Andrew Hall Date: Wed, 26 Apr 2023 13:44:31 -0400 Subject: [PATCH 28/73] [serverless] Add ability to disable certain plugins for Serverless. (#155583) > Derived from https://github.com/elastic/kibana/pull/153274 ## Summary This PR extracts configuration settings for enabling/disabling plugins in Serverless projects based on current requirements. It seemed prudent to create an independent PR to K.I.S.S, rather than include in PRs with more ornate changes, (e.g. https://github.com/elastic/kibana/pull/155582) --- config/serverless.yml | 15 +++++++++++++++ x-pack/plugins/apm/server/index.ts | 1 + .../cross_cluster_replication/server/config.ts | 5 +++++ .../__mocks__/kea_logic/kibana_logic.mock.ts | 8 ++++---- .../applications/shared/kibana/kibana_logic.ts | 2 +- x-pack/plugins/enterprise_search/server/index.ts | 1 + x-pack/plugins/fleet/common/authz.ts | 2 +- x-pack/plugins/fleet/server/config.ts | 1 + .../index_lifecycle_management/server/config.ts | 5 +++++ .../plugins/license_management/server/config.ts | 5 +++++ x-pack/plugins/observability/server/index.ts | 1 + x-pack/plugins/remote_clusters/server/config.ts | 5 +++++ x-pack/plugins/rollup/server/config.ts | 5 +++++ .../security_solution/server/config.mock.ts | 1 + x-pack/plugins/security_solution/server/config.ts | 1 + x-pack/plugins/snapshot_restore/server/config.ts | 5 +++++ x-pack/plugins/synthetics/common/config.ts | 1 + x-pack/plugins/synthetics/kibana.jsonc | 4 +++- .../get_service_locations.test.ts | 3 +++ .../synthetics_service/synthetics_service.test.ts | 8 +++++++- x-pack/plugins/upgrade_assistant/server/config.ts | 8 ++++++++ x-pack/plugins/watcher/server/index.ts | 8 ++++++++ 22 files changed, 87 insertions(+), 8 deletions(-) diff --git a/config/serverless.yml b/config/serverless.yml index d38863072d8a0..e65b15f064328 100644 --- a/config/serverless.yml +++ b/config/serverless.yml @@ -1,2 +1,17 @@ +newsfeed.enabled: false xpack.fleet.enableExperimental: ['fleetServerStandalone'] xpack.fleet.internal.disableILMPolicies: true + +# Management team plugins +xpack.upgrade_assistant.enabled: false +xpack.rollup.enabled: false +xpack.watcher.enabled: false +xpack.ccr.enabled: false +xpack.ilm.enabled: false +xpack.remote_clusters.enabled: false +xpack.snapshot_restore.enabled: false +xpack.license_management.enabled: false + +# Other disabled plugins +xpack.canvas.enabled: false +xpack.reporting.enabled: false diff --git a/x-pack/plugins/apm/server/index.ts b/x-pack/plugins/apm/server/index.ts index bfd7f1c19e376..d13bc2e708efe 100644 --- a/x-pack/plugins/apm/server/index.ts +++ b/x-pack/plugins/apm/server/index.ts @@ -56,6 +56,7 @@ const configSchema = schema.object({ latestAgentVersionsUrl: schema.string({ defaultValue: 'https://apm-agent-versions.elastic.co/versions.json', }), + enabled: schema.boolean({ defaultValue: true }), }); // plugin config diff --git a/x-pack/plugins/cross_cluster_replication/server/config.ts b/x-pack/plugins/cross_cluster_replication/server/config.ts index bac5f917f22a6..4cba6d0707abb 100644 --- a/x-pack/plugins/cross_cluster_replication/server/config.ts +++ b/x-pack/plugins/cross_cluster_replication/server/config.ts @@ -22,6 +22,11 @@ const schemaLatest = schema.object( ui: schema.object({ enabled: schema.boolean({ defaultValue: true }), }), + /** + * Disables the plugin. + * Added back in 8.8. + */ + enabled: schema.boolean({ defaultValue: true }), }, { defaultValue: undefined } ); diff --git a/x-pack/plugins/enterprise_search/public/applications/__mocks__/kea_logic/kibana_logic.mock.ts b/x-pack/plugins/enterprise_search/public/applications/__mocks__/kea_logic/kibana_logic.mock.ts index 5cfd5e7029459..9c1b0575694c4 100644 --- a/x-pack/plugins/enterprise_search/public/applications/__mocks__/kea_logic/kibana_logic.mock.ts +++ b/x-pack/plugins/enterprise_search/public/applications/__mocks__/kea_logic/kibana_logic.mock.ts @@ -24,13 +24,13 @@ export const mockKibanaValues = { ), } as unknown as ApplicationStart, capabilities: {} as Capabilities, - config: { host: 'http://localhost:3002' }, charts: chartPluginMock.createStartContract(), cloud: { ...cloudMock.createSetup(), - isCloudEnabled: false, deployment_url: 'https://cloud.elastic.co/deployments/some-id', + isCloudEnabled: false, }, + config: { host: 'http://localhost:3002' }, data: dataPluginMock.createStartContract(), guidedOnboarding: {}, history: mockHistory, @@ -50,12 +50,12 @@ export const mockKibanaValues = { hasNativeConnectors: true, hasWebCrawler: true, }, - uiSettings: uiSettingsServiceMock.createStartContract(), + renderHeaderActions: jest.fn(), security: securityMock.createStart(), setBreadcrumbs: jest.fn(), setChromeIsVisible: jest.fn(), setDocTitle: jest.fn(), - renderHeaderActions: jest.fn(), + uiSettings: uiSettingsServiceMock.createStartContract(), }; jest.mock('../../shared/kibana', () => ({ diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/kibana/kibana_logic.ts b/x-pack/plugins/enterprise_search/public/applications/shared/kibana/kibana_logic.ts index 2a400ce2d0269..b7d264ba43e99 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/kibana/kibana_logic.ts +++ b/x-pack/plugins/enterprise_search/public/applications/shared/kibana/kibana_logic.ts @@ -67,9 +67,9 @@ export const KibanaLogic = kea>({ reducers: ({ props }) => ({ application: [props.application || {}, {}], capabilities: [props.capabilities || {}, {}], - config: [props.config || {}, {}], charts: [props.charts, {}], cloud: [props.cloud || {}, {}], + config: [props.config || {}, {}], data: [props.data, {}], guidedOnboarding: [props.guidedOnboarding, {}], history: [props.history, {}], diff --git a/x-pack/plugins/enterprise_search/server/index.ts b/x-pack/plugins/enterprise_search/server/index.ts index 19019841976d4..b4c86696c4858 100644 --- a/x-pack/plugins/enterprise_search/server/index.ts +++ b/x-pack/plugins/enterprise_search/server/index.ts @@ -19,6 +19,7 @@ export const configSchema = schema.object({ accessCheckTimeoutWarning: schema.number({ defaultValue: 300 }), canDeployEntSearch: schema.boolean({ defaultValue: true }), customHeaders: schema.maybe(schema.object({}, { unknowns: 'allow' })), + enabled: schema.boolean({ defaultValue: true }), hasConnectors: schema.boolean({ defaultValue: true }), hasDefaultIngestPipeline: schema.boolean({ defaultValue: true }), hasNativeConnectors: schema.boolean({ defaultValue: true }), diff --git a/x-pack/plugins/fleet/common/authz.ts b/x-pack/plugins/fleet/common/authz.ts index 83d337c00368b..3fbfd614d8038 100644 --- a/x-pack/plugins/fleet/common/authz.ts +++ b/x-pack/plugins/fleet/common/authz.ts @@ -107,7 +107,7 @@ export function calculatePackagePrivilegesFromCapabilities( return { ...acc, [privilege]: { - executePackageAction: capabilities.siem[privilegeName] || false, + executePackageAction: (capabilities.siem && capabilities.siem[privilegeName]) || false, }, }; }, diff --git a/x-pack/plugins/fleet/server/config.ts b/x-pack/plugins/fleet/server/config.ts index 267d9873aaa2d..a370c0825664c 100644 --- a/x-pack/plugins/fleet/server/config.ts +++ b/x-pack/plugins/fleet/server/config.ts @@ -166,6 +166,7 @@ export const config: PluginConfigDescriptor = { }), }) ), + enabled: schema.boolean({ defaultValue: true }), }), }; diff --git a/x-pack/plugins/index_lifecycle_management/server/config.ts b/x-pack/plugins/index_lifecycle_management/server/config.ts index 737cc6a472c7a..7fdec20bbb050 100644 --- a/x-pack/plugins/index_lifecycle_management/server/config.ts +++ b/x-pack/plugins/index_lifecycle_management/server/config.ts @@ -24,6 +24,11 @@ const schemaLatest = schema.object( }), // Cloud requires the ability to hide internal node attributes from users. filteredNodeAttributes: schema.arrayOf(schema.string(), { defaultValue: [] }), + /** + * Disables the plugin. + * Added back in 8.8. + */ + enabled: schema.boolean({ defaultValue: true }), }, { defaultValue: undefined } ); diff --git a/x-pack/plugins/license_management/server/config.ts b/x-pack/plugins/license_management/server/config.ts index 42beba0ea5c09..23449bc19e793 100644 --- a/x-pack/plugins/license_management/server/config.ts +++ b/x-pack/plugins/license_management/server/config.ts @@ -22,6 +22,11 @@ const schemaLatest = schema.object( ui: schema.object({ enabled: schema.boolean({ defaultValue: true }), }), + /** + * Disables the plugin. + * Added back in 8.8. + */ + enabled: schema.boolean({ defaultValue: true }), }, { defaultValue: undefined } ); diff --git a/x-pack/plugins/observability/server/index.ts b/x-pack/plugins/observability/server/index.ts index fea9b0ab14195..33ef9d2c62d4d 100644 --- a/x-pack/plugins/observability/server/index.ts +++ b/x-pack/plugins/observability/server/index.ts @@ -41,6 +41,7 @@ const configSchema = schema.object({ }), }), }), + enabled: schema.boolean({ defaultValue: true }), }); export const config: PluginConfigDescriptor = { diff --git a/x-pack/plugins/remote_clusters/server/config.ts b/x-pack/plugins/remote_clusters/server/config.ts index 32db006e8171a..4f6c56191cd89 100644 --- a/x-pack/plugins/remote_clusters/server/config.ts +++ b/x-pack/plugins/remote_clusters/server/config.ts @@ -22,6 +22,11 @@ const schemaLatest = schema.object( ui: schema.object({ enabled: schema.boolean({ defaultValue: true }), }), + /** + * Disables the plugin. + * Added back in 8.8. + */ + enabled: schema.boolean({ defaultValue: true }), }, { defaultValue: undefined } ); diff --git a/x-pack/plugins/rollup/server/config.ts b/x-pack/plugins/rollup/server/config.ts index 235202a23db24..953cd4b283f97 100644 --- a/x-pack/plugins/rollup/server/config.ts +++ b/x-pack/plugins/rollup/server/config.ts @@ -22,6 +22,11 @@ const schemaLatest = schema.object( ui: schema.object({ enabled: schema.boolean({ defaultValue: true }), }), + /** + * Disables the plugin. + * Added back in 8.8. + */ + enabled: schema.boolean({ defaultValue: true }), }, { defaultValue: undefined } ); diff --git a/x-pack/plugins/security_solution/server/config.mock.ts b/x-pack/plugins/security_solution/server/config.mock.ts index c1faa6f401a1d..6fcaa94629643 100644 --- a/x-pack/plugins/security_solution/server/config.mock.ts +++ b/x-pack/plugins/security_solution/server/config.mock.ts @@ -31,6 +31,7 @@ export const createMockConfig = (): ConfigType => { alertIgnoreFields: [], experimentalFeatures: parseExperimentalConfigValue(enableExperimental), + enabled: true, }; }; diff --git a/x-pack/plugins/security_solution/server/config.ts b/x-pack/plugins/security_solution/server/config.ts index 26f1be4f014b3..a0283858590cb 100644 --- a/x-pack/plugins/security_solution/server/config.ts +++ b/x-pack/plugins/security_solution/server/config.ts @@ -122,6 +122,7 @@ export const configSchema = schema.object({ * the package is not already installed. */ prebuiltRulesPackageVersion: schema.maybe(schema.string()), + enabled: schema.boolean({ defaultValue: true }), }); export type ConfigSchema = TypeOf; diff --git a/x-pack/plugins/snapshot_restore/server/config.ts b/x-pack/plugins/snapshot_restore/server/config.ts index d259b6674391a..e2452e5b58e54 100644 --- a/x-pack/plugins/snapshot_restore/server/config.ts +++ b/x-pack/plugins/snapshot_restore/server/config.ts @@ -25,6 +25,11 @@ const schemaLatest = schema.object( slm_ui: schema.object({ enabled: schema.boolean({ defaultValue: true }), }), + /** + * Disables the plugin. + * Added back in 8.8. + */ + enabled: schema.boolean({ defaultValue: true }), }, { defaultValue: undefined } ); diff --git a/x-pack/plugins/synthetics/common/config.ts b/x-pack/plugins/synthetics/common/config.ts index c9c48e5878391..9da43f8bf9a08 100644 --- a/x-pack/plugins/synthetics/common/config.ts +++ b/x-pack/plugins/synthetics/common/config.ts @@ -23,6 +23,7 @@ const serviceConfig = schema.object({ const uptimeConfig = schema.object({ index: schema.maybe(schema.string()), service: schema.maybe(serviceConfig), + enabled: schema.boolean({ defaultValue: true }), }); export const config: PluginConfigDescriptor = { diff --git a/x-pack/plugins/synthetics/kibana.jsonc b/x-pack/plugins/synthetics/kibana.jsonc index a70202e3126a1..1cc3b45f4637d 100644 --- a/x-pack/plugins/synthetics/kibana.jsonc +++ b/x-pack/plugins/synthetics/kibana.jsonc @@ -12,6 +12,8 @@ "actions", "alerting", "cases", + "data", + "fleet", "embeddable", "discover", "dataViews", @@ -44,4 +46,4 @@ "indexLifecycleManagement" ] } -} +} \ No newline at end of file diff --git a/x-pack/plugins/synthetics/server/synthetics_service/get_service_locations.test.ts b/x-pack/plugins/synthetics/server/synthetics_service/get_service_locations.test.ts index 1fed640bcb4e8..58faf6ba14877 100644 --- a/x-pack/plugins/synthetics/server/synthetics_service/get_service_locations.test.ts +++ b/x-pack/plugins/synthetics/server/synthetics_service/get_service_locations.test.ts @@ -50,6 +50,7 @@ describe('getServiceLocations', function () { manifestUrl: 'http://local.dev', showExperimentalLocations: false, }, + enabled: true, }, // @ts-ignore logger: { @@ -101,6 +102,7 @@ describe('getServiceLocations', function () { manifestUrl: 'http://local.dev', showExperimentalLocations: false, }, + enabled: true, }, // @ts-ignore logger: { @@ -138,6 +140,7 @@ describe('getServiceLocations', function () { manifestUrl: 'http://local.dev', showExperimentalLocations: true, }, + enabled: true, }, // @ts-ignore logger: { diff --git a/x-pack/plugins/synthetics/server/synthetics_service/synthetics_service.test.ts b/x-pack/plugins/synthetics/server/synthetics_service/synthetics_service.test.ts index 35e31add55aa0..86933fc51e4fd 100644 --- a/x-pack/plugins/synthetics/server/synthetics_service/synthetics_service.test.ts +++ b/x-pack/plugins/synthetics/server/synthetics_service/synthetics_service.test.ts @@ -80,6 +80,7 @@ describe('SyntheticsService', () => { password: '12345', manifestUrl: 'http://localhost:8080/api/manifest', }, + enabled: true, }, coreStart: mockCoreStart, encryptedSavedObjects: mockEncryptedSO(), @@ -101,7 +102,11 @@ describe('SyntheticsService', () => { }; }); serverMock.config = { - service: { devUrl: 'http://localhost', manifestUrl: 'https://test-manifest.com' }, + service: { + devUrl: 'http://localhost', + manifestUrl: 'https://test-manifest.com', + }, + enabled: true, }; if (serverMock.savedObjectsClient) { serverMock.savedObjectsClient.find = jest.fn().mockResolvedValue({ @@ -165,6 +170,7 @@ describe('SyntheticsService', () => { username: 'dev', password: '12345', }, + enabled: true, }; const service = new SyntheticsService(serverMock); diff --git a/x-pack/plugins/upgrade_assistant/server/config.ts b/x-pack/plugins/upgrade_assistant/server/config.ts index 6202a6680708a..bf872f50b5222 100644 --- a/x-pack/plugins/upgrade_assistant/server/config.ts +++ b/x-pack/plugins/upgrade_assistant/server/config.ts @@ -12,6 +12,11 @@ import { PluginConfigDescriptor } from '@kbn/core/server'; // even for minor releases. // ------------------------------- const configSchema = schema.object({ + /** + * Disables the plugin. + */ + enabled: schema.boolean({ defaultValue: true }), + featureSet: schema.object({ /** * Ml Snapshot should only be enabled for major version upgrades. Currently this @@ -39,6 +44,9 @@ const configSchema = schema.object({ */ reindexCorrectiveActions: schema.boolean({ defaultValue: false }), }), + /** + * This config allows to hide the UI without disabling the plugin. + */ ui: schema.object({ enabled: schema.boolean({ defaultValue: true }), }), diff --git a/x-pack/plugins/watcher/server/index.ts b/x-pack/plugins/watcher/server/index.ts index 0aba44ed82838..36453f571f162 100644 --- a/x-pack/plugins/watcher/server/index.ts +++ b/x-pack/plugins/watcher/server/index.ts @@ -6,6 +6,14 @@ */ import { PluginInitializerContext } from '@kbn/core/server'; +import { schema } from '@kbn/config-schema'; + import { WatcherServerPlugin } from './plugin'; export const plugin = (ctx: PluginInitializerContext) => new WatcherServerPlugin(ctx); + +export const config = { + schema: schema.object({ + enabled: schema.boolean({ defaultValue: true }), + }), +}; From 4ff9a6f60ab77d2fda110eeec1c752fda9e26fc3 Mon Sep 17 00:00:00 2001 From: Shahzad Date: Wed, 26 Apr 2023 19:59:40 +0200 Subject: [PATCH 29/73] [Synthetics] Handle invalid license state (#155851) --- .../plugins/synthetics/common/constants/ui.ts | 4 ++ .../apps/synthetics/hooks/use_enablement.ts | 4 +- .../hooks/use_synthetics_priviliges.tsx | 64 ++++++++++++++++++- .../legacy_uptime/lib/domains/license.ts | 11 +++- 4 files changed, 77 insertions(+), 6 deletions(-) diff --git a/x-pack/plugins/synthetics/common/constants/ui.ts b/x-pack/plugins/synthetics/common/constants/ui.ts index 40c1d26c58cbc..d014b8b8ea6ff 100644 --- a/x-pack/plugins/synthetics/common/constants/ui.ts +++ b/x-pack/plugins/synthetics/common/constants/ui.ts @@ -105,3 +105,7 @@ export const FILTER_FIELDS = { }; export const SYNTHETICS_INDEX_PATTERN = 'synthetics-*'; + +export const LICENSE_NOT_ACTIVE_ERROR = 'License not active'; +export const LICENSE_MISSING_ERROR = 'Missing license information'; +export const LICENSE_NOT_SUPPORTED_ERROR = 'License not supported'; diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/hooks/use_enablement.ts b/x-pack/plugins/synthetics/public/apps/synthetics/hooks/use_enablement.ts index fe726d0cbe3d2..057d15218ac2e 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/hooks/use_enablement.ts +++ b/x-pack/plugins/synthetics/public/apps/synthetics/hooks/use_enablement.ts @@ -15,10 +15,10 @@ export function useEnablement() { const { loading, error, enablement } = useSelector(selectSyntheticsEnablement); useEffect(() => { - if (!enablement && !loading) { + if (!enablement && !loading && !error) { dispatch(getSyntheticsEnablement()); } - }, [dispatch, enablement, loading]); + }, [dispatch, enablement, error, loading]); return { enablement: { diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/hooks/use_synthetics_priviliges.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/hooks/use_synthetics_priviliges.tsx index b1f1d57a33fd7..26f77151010e9 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/hooks/use_synthetics_priviliges.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/hooks/use_synthetics_priviliges.tsx @@ -12,13 +12,20 @@ import { EuiFlexGroup, EuiFlexItem, EuiIcon, + EuiButton, EuiMarkdownFormat, } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n-react'; import { css } from '@emotion/react'; import { i18n } from '@kbn/i18n'; import { selectOverviewStatus } from '../state/overview_status'; -import { SYNTHETICS_INDEX_PATTERN } from '../../../../common/constants'; +import { + LICENSE_MISSING_ERROR, + LICENSE_NOT_ACTIVE_ERROR, + LICENSE_NOT_SUPPORTED_ERROR, + SYNTHETICS_INDEX_PATTERN, +} from '../../../../common/constants'; +import { useSyntheticsSettingsContext } from '../contexts'; export const useSyntheticsPrivileges = () => { const { error } = useSelector(selectOverviewStatus); @@ -36,6 +43,24 @@ export const useSyntheticsPrivileges = () => { ); } + if ( + error?.body?.message && + [LICENSE_NOT_ACTIVE_ERROR, LICENSE_MISSING_ERROR, LICENSE_NOT_SUPPORTED_ERROR].includes( + error?.body?.message + ) + ) { + return ( + + + + + + ); + } }; const Unprivileged = ({ unprivilegedIndices }: { unprivilegedIndices: string[] }) => ( @@ -77,3 +102,40 @@ const Unprivileged = ({ unprivilegedIndices }: { unprivilegedIndices: string[] } } /> ); + +const LicenseExpired = () => { + const { basePath } = useSyntheticsSettingsContext(); + return ( + + + + } + body={ +

+ +

+ } + actions={[ + + {i18n.translate('xpack.synthetics.invalidLicense.licenseManagementLink', { + defaultMessage: 'Manage your license', + })} + , + ]} + /> + ); +}; diff --git a/x-pack/plugins/synthetics/server/legacy_uptime/lib/domains/license.ts b/x-pack/plugins/synthetics/server/legacy_uptime/lib/domains/license.ts index d921766529e8f..6a55b0459c632 100644 --- a/x-pack/plugins/synthetics/server/legacy_uptime/lib/domains/license.ts +++ b/x-pack/plugins/synthetics/server/legacy_uptime/lib/domains/license.ts @@ -6,6 +6,11 @@ */ import { ILicense } from '@kbn/licensing-plugin/server'; +import { + LICENSE_MISSING_ERROR, + LICENSE_NOT_ACTIVE_ERROR, + LICENSE_NOT_SUPPORTED_ERROR, +} from '../../../../common/constants'; export interface UMLicenseStatusResponse { statusCode: number; @@ -18,19 +23,19 @@ export type UMLicenseCheck = ( export const licenseCheck: UMLicenseCheck = (license) => { if (license === undefined) { return { - message: 'Missing license information', + message: LICENSE_MISSING_ERROR, statusCode: 400, }; } if (!license.hasAtLeast('basic')) { return { - message: 'License not supported', + message: LICENSE_NOT_SUPPORTED_ERROR, statusCode: 401, }; } if (license.isActive === false) { return { - message: 'License not active', + message: LICENSE_NOT_ACTIVE_ERROR, statusCode: 403, }; } From cbcaab419bd84c54a67a707618f9665bb188d88b Mon Sep 17 00:00:00 2001 From: Antonio Date: Wed, 26 Apr 2023 20:10:22 +0200 Subject: [PATCH 30/73] [Cases ]Add e2e tests for case files view (#155634) ## Summary One of the leftover issues from https://github.com/elastic/kibana/pull/154436 was adding a few e2e tests. This PR adds e2e tests for: - Attaching a file to a case - Deleting a file attached to a case - Opening and closing the file preview in the Cases detail view - Guarantee the File User Activity is rendered correctly ## Flaky Test Runner https://buildkite.com/elastic/kibana-flaky-test-suite-runner/builds/2177#0187bd93-edc9-4af0-85c4-5df3f55c418c Flaky tests: (`x-pack/test/functional_with_es_ssl/apps/cases/group1/config.ts` x 50) --------- Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../public/components/files/files_table.tsx | 10 ++- .../test/functional/services/cases/files.ts | 68 ++++++++++++++++++ .../test/functional/services/cases/index.ts | 2 + .../apps/cases/group1/elastic_logo.png | Bin 0 -> 34043 bytes .../apps/cases/group1/view_case.ts | 61 ++++++++++++++++ 5 files changed, 140 insertions(+), 1 deletion(-) create mode 100644 x-pack/test/functional/services/cases/files.ts create mode 100644 x-pack/test/functional_with_es_ssl/apps/cases/group1/elastic_logo.png diff --git a/x-pack/plugins/cases/public/components/files/files_table.tsx b/x-pack/plugins/cases/public/components/files/files_table.tsx index 6433d90a91d44..18a9e7f196661 100644 --- a/x-pack/plugins/cases/public/components/files/files_table.tsx +++ b/x-pack/plugins/cases/public/components/files/files_table.tsx @@ -4,7 +4,7 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import React, { useState } from 'react'; +import React, { useCallback, useState } from 'react'; import type { Pagination, EuiBasicTableProps } from '@elastic/eui'; import type { FileJSON } from '@kbn/shared-ux-file-types'; @@ -46,6 +46,13 @@ export const FilesTable = ({ caseId, items, pagination, onChange, isLoading }: F showPreview(); }; + const filesTableRowProps = useCallback( + (file: FileJSON) => ({ + 'data-test-subj': `cases-files-table-row-${file.id}`, + }), + [] + ); + const columns = useFilesTableColumns({ caseId, showPreview: displayPreview }); return isLoading ? ( @@ -72,6 +79,7 @@ export const FilesTable = ({ caseId, items, pagination, onChange, isLoading }: F onChange={onChange} data-test-subj="cases-files-table" noItemsMessage={} + rowProps={filesTableRowProps} /> {isPreviewVisible && selectedFile !== undefined && ( diff --git a/x-pack/test/functional/services/cases/files.ts b/x-pack/test/functional/services/cases/files.ts new file mode 100644 index 0000000000000..14c922c3f21ea --- /dev/null +++ b/x-pack/test/functional/services/cases/files.ts @@ -0,0 +1,68 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { FtrProviderContext } from '../../ftr_provider_context'; + +export function CasesFilesTableServiceProvider({ getService, getPageObject }: FtrProviderContext) { + const common = getPageObject('common'); + const testSubjects = getService('testSubjects'); + const find = getService('find'); + const browser = getService('browser'); + + const assertFileExists = (index: number, totalFiles: number) => { + if (index > totalFiles - 1) { + throw new Error('Cannot get file from table. Index is greater than the length of all rows'); + } + }; + + return { + async addFile(fileInputPath: string) { + // click the AddFile button + await testSubjects.click('cases-files-add'); + await find.byCssSelector('[aria-label="Upload a file"]'); + + // upload a file + await common.setFileInputPath(fileInputPath); + await testSubjects.click('uploadButton'); + }, + + async searchByFileName(fileName: string) { + const searchField = await testSubjects.find('cases-files-search'); + + searchField.clearValue(); + + await searchField.type(fileName); + await searchField.pressKeys(browser.keys.ENTER); + }, + + async deleteFile(index: number = 0) { + const row = await this.getFileByIndex(index); + + (await row.findByCssSelector('[data-test-subj="cases-files-delete-button"]')).click(); + + await testSubjects.click('confirmModalConfirmButton'); + }, + + async openFilePreview(index: number = 0) { + const row = await this.getFileByIndex(index); + + (await row.findByCssSelector('[data-test-subj="cases-files-name-link"]')).click(); + }, + + async emptyOrFail() { + await testSubjects.existOrFail('cases-files-table-empty'); + }, + + async getFileByIndex(index: number) { + const rows = await find.allByCssSelector('[data-test-subj*="cases-files-table-row-"', 100); + + assertFileExists(index, rows.length); + + return rows[index] ?? null; + }, + }; +} diff --git a/x-pack/test/functional/services/cases/index.ts b/x-pack/test/functional/services/cases/index.ts index 8ecabdac8c4c5..27df56d546090 100644 --- a/x-pack/test/functional/services/cases/index.ts +++ b/x-pack/test/functional/services/cases/index.ts @@ -9,6 +9,7 @@ import { FtrProviderContext } from '../../ftr_provider_context'; import { CasesAPIServiceProvider } from './api'; import { CasesCommonServiceProvider } from './common'; import { CasesCreateViewServiceProvider } from './create'; +import { CasesFilesTableServiceProvider } from './files'; import { CasesTableServiceProvider } from './list'; import { CasesNavigationProvider } from './navigation'; import { CasesSingleViewServiceProvider } from './single_case_view'; @@ -21,6 +22,7 @@ export function CasesServiceProvider(context: FtrProviderContext) { api: CasesAPIServiceProvider(context), common: casesCommon, casesTable: CasesTableServiceProvider(context, casesCommon), + casesFilesTable: CasesFilesTableServiceProvider(context), create: CasesCreateViewServiceProvider(context, casesCommon), navigation: CasesNavigationProvider(context), singleCase: CasesSingleViewServiceProvider(context), diff --git a/x-pack/test/functional_with_es_ssl/apps/cases/group1/elastic_logo.png b/x-pack/test/functional_with_es_ssl/apps/cases/group1/elastic_logo.png new file mode 100644 index 0000000000000000000000000000000000000000..085012eac378818bf04ffde92a9950243942003d GIT binary patch literal 34043 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 literal 0 HcmV?d00001 diff --git a/x-pack/test/functional_with_es_ssl/apps/cases/group1/view_case.ts b/x-pack/test/functional_with_es_ssl/apps/cases/group1/view_case.ts index 233157e57e518..8c9f7a4450f83 100644 --- a/x-pack/test/functional_with_es_ssl/apps/cases/group1/view_case.ts +++ b/x-pack/test/functional_with_es_ssl/apps/cases/group1/view_case.ts @@ -618,6 +618,67 @@ export default ({ getPageObject, getService }: FtrProviderContext) => { await testSubjects.click('case-view-tab-title-alerts'); await testSubjects.existOrFail('case-view-tab-content-alerts'); }); + + it("shows the 'files' tab when clicked", async () => { + await testSubjects.click('case-view-tab-title-files'); + await testSubjects.existOrFail('case-view-tab-content-files'); + }); + }); + + describe('Files', () => { + createOneCaseBeforeDeleteAllAfter(getPageObject, getService); + + it('adds a file to the case', async () => { + // navigate to files tab + await testSubjects.click('case-view-tab-title-files'); + await testSubjects.existOrFail('case-view-tab-content-files'); + + await cases.casesFilesTable.addFile(require.resolve('./elastic_logo.png')); + + // make sure the uploaded file is displayed on the table + await find.byButtonText('elastic_logo.png'); + }); + + it('search by file name', async () => { + await cases.casesFilesTable.searchByFileName('foobar'); + + await cases.casesFilesTable.emptyOrFail(); + + await cases.casesFilesTable.searchByFileName('elastic'); + + await find.byButtonText('elastic_logo.png'); + }); + + it('displays the file preview correctly', async () => { + await cases.casesFilesTable.openFilePreview(0); + + await testSubjects.existOrFail('cases-files-image-preview'); + }); + + it('pressing escape key closes the file preview', async () => { + await testSubjects.existOrFail('cases-files-image-preview'); + + await browser.pressKeys(browser.keys.ESCAPE); + + await testSubjects.missingOrFail('cases-files-image-preview'); + }); + + it('files added to a case can be deleted', async () => { + await cases.casesFilesTable.deleteFile(0); + + await cases.casesFilesTable.emptyOrFail(); + }); + + describe('Files User Activity', () => { + it('file user action is displayed correctly', async () => { + await cases.casesFilesTable.addFile(require.resolve('./elastic_logo.png')); + + await testSubjects.click('case-view-tab-title-activity'); + await testSubjects.existOrFail('case-view-tab-content-activity'); + + await find.byButtonText('elastic_logo.png'); + }); + }); }); }); }; From b3f65f79e5017b70fe26e5aa1c2ee1085e68c138 Mon Sep 17 00:00:00 2001 From: Hannah Mudge Date: Wed, 26 Apr 2023 12:19:46 -0600 Subject: [PATCH 31/73] [Controls] Fix sorting of numeric keyword fields (#155207) Closes https://github.com/elastic/kibana/issues/155073 ## Summary ### Before Previously, the options list suggestions were stored as a dictionary (i.e. an object of key+value pairs) - while this worked for most fields, unbeknownst to us, Javascript tries to sort numeric keys (regardless of if they are of type `string` or `number`) based on their value. This meant that, as part of the parsing process when using an options list control for a numeric `keyword` field, the results returned by the ES query were **always** sorted in ascending numeric order regardless of the sorting method that was picked (note that this is especially obvious once you "load more", which is what I did for the following screenshots): | | Ascending | Descending | |--------------|-----------|------------| | Alphabetical | | | | Doc count | | | ### After This PR converts the options list suggestions to be stored as an **array** of key/value pairs in order to preserve the order returned from Elasticsearch - now, you get the expected string-sorted ordering when using numeric `keyword` fields in an options list control: | | Ascending | Descending | |--------------|-----------|------------| | Alphabetical | | | | Doc count | | | Notice in the above that we are now using **string sorting** for the numeric values when alphabetical sorting is selected, which means you aren't getting the expected "numeric" sorting - so for example, when sorted ascending, `"6" > "52"` because it is only comparing the first character and `"6" > "5"`. This will be handled much better once [numeric field support](https://github.com/elastic/kibana/issues/126795) is added to options lists. ### 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] This was checked for [cross-browser compatibility](https://www.elastic.co/support/matrix#matrix_browsers) ### For maintainers - [ ] This was checked for breaking API changes and was [labeled appropriately](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process) --- .../controls/common/options_list/mocks.tsx | 14 +- .../controls/common/options_list/types.ts | 4 +- .../public/__stories__/controls.stories.tsx | 10 +- .../components/options_list_popover.test.tsx | 12 +- .../options_list_popover_suggestions.tsx | 24 +-- .../embeddable/options_list_embeddable.tsx | 2 +- .../options_list/options_list.story.ts | 2 +- ...ions_list_cheap_suggestion_queries.test.ts | 142 +++++++++--------- .../options_list_cheap_suggestion_queries.ts | 33 ++-- ..._list_expensive_suggestion_queries.test.ts | 123 +++++++-------- ...tions_list_expensive_suggestion_queries.ts | 21 ++- 11 files changed, 196 insertions(+), 191 deletions(-) diff --git a/src/plugins/controls/common/options_list/mocks.tsx b/src/plugins/controls/common/options_list/mocks.tsx index 936a620ec288c..d0e2977a9b439 100644 --- a/src/plugins/controls/common/options_list/mocks.tsx +++ b/src/plugins/controls/common/options_list/mocks.tsx @@ -17,13 +17,13 @@ const mockOptionsListComponentState = { searchString: { value: '', valid: true }, field: undefined, totalCardinality: 0, - availableOptions: { - woof: { doc_count: 100 }, - bark: { doc_count: 75 }, - meow: { doc_count: 50 }, - quack: { doc_count: 25 }, - moo: { doc_count: 5 }, - }, + availableOptions: [ + { value: 'woof', docCount: 100 }, + { value: 'bark', docCount: 75 }, + { value: 'meow', docCount: 50 }, + { value: 'quack', docCount: 25 }, + { value: 'moo', docCount: 5 }, + ], invalidSelections: [], allowExpensiveQueries: true, popoverOpen: false, diff --git a/src/plugins/controls/common/options_list/types.ts b/src/plugins/controls/common/options_list/types.ts index 510dac280fe76..8437eb0382b6e 100644 --- a/src/plugins/controls/common/options_list/types.ts +++ b/src/plugins/controls/common/options_list/types.ts @@ -28,9 +28,7 @@ export interface OptionsListEmbeddableInput extends DataControlInput { placeholder?: string; } -export interface OptionsListSuggestions { - [key: string]: { doc_count: number }; -} +export type OptionsListSuggestions = Array<{ value: string; docCount?: number }>; /** * The Options list response is returned from the serverside Options List route. diff --git a/src/plugins/controls/public/__stories__/controls.stories.tsx b/src/plugins/controls/public/__stories__/controls.stories.tsx index 4326ce056d118..a0ba30622e150 100644 --- a/src/plugins/controls/public/__stories__/controls.stories.tsx +++ b/src/plugins/controls/public/__stories__/controls.stories.tsx @@ -35,7 +35,11 @@ import { injectStorybookDataView } from '../services/data_views/data_views.story import { replaceOptionsListMethod } from '../services/options_list/options_list.story'; import { populateStorybookControlFactories } from './storybook_control_factories'; import { replaceValueSuggestionMethod } from '../services/unified_search/unified_search.story'; -import { OptionsListResponse, OptionsListRequest } from '../../common/options_list/types'; +import { + OptionsListResponse, + OptionsListRequest, + OptionsListSuggestions, +} from '../../common/options_list/types'; export default { title: 'Controls', @@ -56,9 +60,9 @@ const storybookStubOptionsListRequest = async ( r({ suggestions: getFlightSearchOptions(request.field.name, request.searchString).reduce( (o, current, index) => { - return { ...o, [current]: { doc_count: index } }; + return [...o, { value: current, docCount: index }]; }, - {} + [] as OptionsListSuggestions ), totalCardinality: 100, }), diff --git a/src/plugins/controls/public/options_list/components/options_list_popover.test.tsx b/src/plugins/controls/public/options_list/components/options_list_popover.test.tsx index ffdb1045cad88..e2fa74dfbf2f1 100644 --- a/src/plugins/controls/public/options_list/components/options_list_popover.test.tsx +++ b/src/plugins/controls/public/options_list/components/options_list_popover.test.tsx @@ -70,7 +70,7 @@ describe('Options list popover', () => { }); test('no available options', async () => { - const popover = await mountComponent({ componentState: { availableOptions: {} } }); + const popover = await mountComponent({ componentState: { availableOptions: [] } }); const availableOptionsDiv = findTestSubject(popover, 'optionsList-control-available-options'); const noOptionsDiv = findTestSubject( availableOptionsDiv, @@ -127,9 +127,7 @@ describe('Options list popover', () => { selectedOptions: ['bark', 'woof'], }, componentState: { - availableOptions: { - bark: { doc_count: 75 }, - }, + availableOptions: [{ value: 'bark', docCount: 75 }], validSelections: ['bark'], invalidSelections: ['woof'], }, @@ -154,9 +152,7 @@ describe('Options list popover', () => { const popover = await mountComponent({ explicitInput: { selectedOptions: ['bark', 'woof', 'meow'] }, componentState: { - availableOptions: { - bark: { doc_count: 75 }, - }, + availableOptions: [{ value: 'bark', docCount: 75 }], validSelections: ['bark'], invalidSelections: ['woof', 'meow'], }, @@ -219,7 +215,7 @@ describe('Options list popover', () => { test('if existsSelected = false and no suggestions, then "Exists" does not show up', async () => { const popover = await mountComponent({ - componentState: { availableOptions: {} }, + componentState: { availableOptions: [] }, explicitInput: { existsSelected: false }, }); const existsOption = findTestSubject(popover, 'optionsList-control-selection-exists'); diff --git a/src/plugins/controls/public/options_list/components/options_list_popover_suggestions.tsx b/src/plugins/controls/public/options_list/components/options_list_popover_suggestions.tsx index 8d727bed55e20..e5c14e5ea70fc 100644 --- a/src/plugins/controls/public/options_list/components/options_list_popover_suggestions.tsx +++ b/src/plugins/controls/public/options_list/components/options_list_popover_suggestions.tsx @@ -48,7 +48,7 @@ export const OptionsListPopoverSuggestions = ({ const canLoadMoreSuggestions = useMemo( () => totalCardinality - ? Object.keys(availableOptions ?? {}).length < + ? (availableOptions ?? []).length < Math.min(totalCardinality, MAX_OPTIONS_LIST_REQUEST_SIZE) : false, [availableOptions, totalCardinality] @@ -61,7 +61,7 @@ export const OptionsListPopoverSuggestions = ({ [invalidSelections] ); const suggestions = useMemo(() => { - return showOnlySelected ? selectedOptions : Object.keys(availableOptions ?? {}); + return showOnlySelected ? selectedOptions : availableOptions ?? []; }, [availableOptions, selectedOptions, showOnlySelected]); const existsSelectableOption = useMemo(() => { @@ -79,19 +79,23 @@ export const OptionsListPopoverSuggestions = ({ const [selectableOptions, setSelectableOptions] = useState([]); // will be set in following useEffect useEffect(() => { /* This useEffect makes selectableOptions responsive to search, show only selected, and clear selections */ - const options: EuiSelectableOption[] = (suggestions ?? []).map((key) => { + const options: EuiSelectableOption[] = (suggestions ?? []).map((suggestion) => { + if (typeof suggestion === 'string') { + // this means that `showOnlySelected` is true, and doc count is not known when this is the case + suggestion = { value: suggestion }; + } return { - key, - label: key, - checked: selectedOptionsSet?.has(key) ? 'on' : undefined, - 'data-test-subj': `optionsList-control-selection-${key}`, + key: suggestion.value, + label: suggestion.value, + checked: selectedOptionsSet?.has(suggestion.value) ? 'on' : undefined, + 'data-test-subj': `optionsList-control-selection-${suggestion.value}`, className: - showOnlySelected && invalidSelectionsSet.has(key) + showOnlySelected && invalidSelectionsSet.has(suggestion.value) ? 'optionsList__selectionInvalid' : 'optionsList__validSuggestion', append: - !showOnlySelected && availableOptions?.[key] ? ( - + !showOnlySelected && suggestion?.docCount ? ( + ) : undefined, }; }); diff --git a/src/plugins/controls/public/options_list/embeddable/options_list_embeddable.tsx b/src/plugins/controls/public/options_list/embeddable/options_list_embeddable.tsx index e2503b4e530e8..08d5f1150baf5 100644 --- a/src/plugins/controls/public/options_list/embeddable/options_list_embeddable.tsx +++ b/src/plugins/controls/public/options_list/embeddable/options_list_embeddable.tsx @@ -371,7 +371,7 @@ export class OptionsListEmbeddable extends Embeddable { this.dispatch.updateQueryResults({ - availableOptions: {}, + availableOptions: [], }); this.dispatch.setLoading(false); }); diff --git a/src/plugins/controls/public/services/options_list/options_list.story.ts b/src/plugins/controls/public/services/options_list/options_list.story.ts index 6d3305f97b9aa..cf674887a0ba0 100644 --- a/src/plugins/controls/public/services/options_list/options_list.story.ts +++ b/src/plugins/controls/public/services/options_list/options_list.story.ts @@ -18,7 +18,7 @@ let optionsListRequestMethod = async (request: OptionsListRequest, abortSignal: setTimeout( () => r({ - suggestions: {}, + suggestions: [], totalCardinality: 100, }), 120 diff --git a/src/plugins/controls/server/options_list/options_list_cheap_suggestion_queries.test.ts b/src/plugins/controls/server/options_list/options_list_cheap_suggestion_queries.test.ts index 31783a1267aca..0476788791f69 100644 --- a/src/plugins/controls/server/options_list/options_list_cheap_suggestion_queries.test.ts +++ b/src/plugins/controls/server/options_list/options_list_cheap_suggestion_queries.test.ts @@ -388,17 +388,20 @@ describe('options list cheap queries', () => { expect( suggestionAggBuilder.parse(rawSearchResponseMock, optionsListRequestBodyMock).suggestions ).toMatchInlineSnapshot(` - Object { - "cool1": Object { - "doc_count": 5, + Array [ + Object { + "docCount": 5, + "value": "cool1", }, - "cool2": Object { - "doc_count": 15, + Object { + "docCount": 15, + "value": "cool2", }, - "cool3": Object { - "doc_count": 10, + Object { + "docCount": 10, + "value": "cool3", }, - } + ] `); }); @@ -421,14 +424,16 @@ describe('options list cheap queries', () => { expect( suggestionAggBuilder.parse(rawSearchResponseMock, optionsListRequestBodyMock).suggestions ).toMatchInlineSnapshot(` - Object { - "false": Object { - "doc_count": 55, + Array [ + Object { + "docCount": 55, + "value": "false", }, - "true": Object { - "doc_count": 155, + Object { + "docCount": 155, + "value": "true", }, - } + ] `); }); @@ -455,17 +460,20 @@ describe('options list cheap queries', () => { expect( suggestionAggBuilder.parse(rawSearchResponseMock, optionsListRequestBodyMock).suggestions ).toMatchInlineSnapshot(` - Object { - "cool1": Object { - "doc_count": 5, + Array [ + Object { + "docCount": 5, + "value": "cool1", }, - "cool2": Object { - "doc_count": 15, + Object { + "docCount": 15, + "value": "cool2", }, - "cool3": Object { - "doc_count": 10, + Object { + "docCount": 10, + "value": "cool3", }, - } + ] `); }); @@ -490,17 +498,20 @@ describe('options list cheap queries', () => { expect( suggestionAggBuilder.parse(rawSearchResponseMock, optionsListRequestBodyMock).suggestions ).toMatchInlineSnapshot(` - Object { - "cool1": Object { - "doc_count": 5, + Array [ + Object { + "docCount": 5, + "value": "cool1", }, - "cool2": Object { - "doc_count": 15, + Object { + "docCount": 15, + "value": "cool2", }, - "cool3": Object { - "doc_count": 10, + Object { + "docCount": 10, + "value": "cool3", }, - } + ] `); }); }); @@ -552,55 +563,50 @@ describe('options list cheap queries', () => { rawSearchResponseMock, optionsListRequestBodyMock ).suggestions; - /** first, verify that the sorting worked as expected */ - expect(Object.keys(parsed)).toMatchInlineSnapshot(` - Array [ - "52:ae76:5947:5e2a:551:fe6a:712a:c72", - "111.52.174.2", - "196.162.13.39", - "f7a9:640b:b5a0:1219:8d75:ed94:3c3e:2e63", - "23.216.241.120", - "28c7:c9a4:42fd:16b0:4de5:e41e:28d9:9172", - "21.35.91.62", - "21.35.91.61", - "203.88.33.151", - "1ec:aa98:b0a6:d07c:590:18a0:8a33:2eb8", - ] - `); - /** then, make sure the object is structured properly */ + expect(parsed).toMatchInlineSnapshot(` - Object { - "111.52.174.2": Object { - "doc_count": 11, + Array [ + Object { + "docCount": 12, + "value": "52:ae76:5947:5e2a:551:fe6a:712a:c72", }, - "196.162.13.39": Object { - "doc_count": 10, + Object { + "docCount": 11, + "value": "111.52.174.2", }, - "1ec:aa98:b0a6:d07c:590:18a0:8a33:2eb8": Object { - "doc_count": 6, + Object { + "docCount": 10, + "value": "196.162.13.39", }, - "203.88.33.151": Object { - "doc_count": 7, + Object { + "docCount": 10, + "value": "f7a9:640b:b5a0:1219:8d75:ed94:3c3e:2e63", }, - "21.35.91.61": Object { - "doc_count": 8, + Object { + "docCount": 9, + "value": "23.216.241.120", }, - "21.35.91.62": Object { - "doc_count": 8, + Object { + "docCount": 9, + "value": "28c7:c9a4:42fd:16b0:4de5:e41e:28d9:9172", }, - "23.216.241.120": Object { - "doc_count": 9, + Object { + "docCount": 8, + "value": "21.35.91.62", }, - "28c7:c9a4:42fd:16b0:4de5:e41e:28d9:9172": Object { - "doc_count": 9, + Object { + "docCount": 8, + "value": "21.35.91.61", }, - "52:ae76:5947:5e2a:551:fe6a:712a:c72": Object { - "doc_count": 12, + Object { + "docCount": 7, + "value": "203.88.33.151", }, - "f7a9:640b:b5a0:1219:8d75:ed94:3c3e:2e63": Object { - "doc_count": 10, + Object { + "docCount": 6, + "value": "1ec:aa98:b0a6:d07c:590:18a0:8a33:2eb8", }, - } + ] `); }); }); diff --git a/src/plugins/controls/server/options_list/options_list_cheap_suggestion_queries.ts b/src/plugins/controls/server/options_list/options_list_cheap_suggestion_queries.ts index 3a302cf62d04b..3b69b2818b909 100644 --- a/src/plugins/controls/server/options_list/options_list_cheap_suggestion_queries.ts +++ b/src/plugins/controls/server/options_list/options_list_cheap_suggestion_queries.ts @@ -51,11 +51,11 @@ const cheapSuggestionAggSubtypes: { [key: string]: OptionsListSuggestionAggregat }, }), parse: (rawEsResult) => ({ - suggestions: get(rawEsResult, 'aggregations.suggestions.buckets').reduce( - (suggestions: OptionsListSuggestions, suggestion: EsBucket) => { - return { ...suggestions, [suggestion.key]: { doc_count: suggestion.doc_count } }; + suggestions: get(rawEsResult, 'aggregations.suggestions.buckets')?.reduce( + (acc: OptionsListSuggestions, suggestion: EsBucket) => { + return [...acc, { value: suggestion.key, docCount: suggestion.doc_count }]; }, - {} + [] ), }), }, @@ -75,13 +75,10 @@ const cheapSuggestionAggSubtypes: { [key: string]: OptionsListSuggestionAggregat }), parse: (rawEsResult) => ({ suggestions: get(rawEsResult, 'aggregations.suggestions.buckets')?.reduce( - (suggestions: OptionsListSuggestions, suggestion: EsBucket & { key_as_string: string }) => { - return { - ...suggestions, - [suggestion.key_as_string]: { doc_count: suggestion.doc_count }, - }; + (acc: OptionsListSuggestions, suggestion: EsBucket & { key_as_string: string }) => { + return [...acc, { value: suggestion.key_as_string, docCount: suggestion.doc_count }]; }, - {} + [] ), }), }, @@ -134,7 +131,7 @@ const cheapSuggestionAggSubtypes: { [key: string]: OptionsListSuggestionAggregat if (!Boolean(rawEsResult.aggregations?.suggestions)) { // if this is happens, that means there is an invalid search that snuck through to the server side code; // so, might as well early return with no suggestions - return { suggestions: {} }; + return { suggestions: [] }; } const buckets: EsBucket[] = []; @@ -153,9 +150,9 @@ const cheapSuggestionAggSubtypes: { [key: string]: OptionsListSuggestionAggregat return { suggestions: sortedSuggestions .slice(0, 10) // only return top 10 results - .reduce((suggestions, suggestion: EsBucket) => { - return { ...suggestions, [suggestion.key]: { doc_count: suggestion.doc_count } }; - }, {}), + .reduce((acc: OptionsListSuggestions, suggestion: EsBucket) => { + return [...acc, { value: suggestion.key, docCount: suggestion.doc_count }]; + }, []), }; }, }, @@ -190,11 +187,11 @@ const cheapSuggestionAggSubtypes: { [key: string]: OptionsListSuggestionAggregat }; }, parse: (rawEsResult) => ({ - suggestions: get(rawEsResult, 'aggregations.nestedSuggestions.suggestions.buckets').reduce( - (suggestions: OptionsListSuggestions, suggestion: EsBucket) => { - return { ...suggestions, [suggestion.key]: { doc_count: suggestion.doc_count } }; + suggestions: get(rawEsResult, 'aggregations.nestedSuggestions.suggestions.buckets')?.reduce( + (acc: OptionsListSuggestions, suggestion: EsBucket) => { + return [...acc, { value: suggestion.key, docCount: suggestion.doc_count }]; }, - {} + [] ), }), }, diff --git a/src/plugins/controls/server/options_list/options_list_expensive_suggestion_queries.test.ts b/src/plugins/controls/server/options_list/options_list_expensive_suggestion_queries.test.ts index 7026359e10ee4..5638cbc347366 100644 --- a/src/plugins/controls/server/options_list/options_list_expensive_suggestion_queries.test.ts +++ b/src/plugins/controls/server/options_list/options_list_expensive_suggestion_queries.test.ts @@ -466,17 +466,20 @@ describe('options list expensive queries', () => { expect(suggestionAggBuilder.parse(rawSearchResponseMock, optionsListRequestBodyMock)) .toMatchInlineSnapshot(` Object { - "suggestions": Object { - "cool1": Object { - "doc_count": 5, + "suggestions": Array [ + Object { + "docCount": 5, + "value": "cool1", }, - "cool2": Object { - "doc_count": 15, + Object { + "docCount": 15, + "value": "cool2", }, - "cool3": Object { - "doc_count": 10, + Object { + "docCount": 10, + "value": "cool3", }, - }, + ], "totalCardinality": 3, } `); @@ -503,14 +506,16 @@ describe('options list expensive queries', () => { expect(suggestionAggBuilder.parse(rawSearchResponseMock, optionsListRequestBodyMock)) .toMatchInlineSnapshot(` Object { - "suggestions": Object { - "false": Object { - "doc_count": 55, + "suggestions": Array [ + Object { + "docCount": 55, + "value": "false", }, - "true": Object { - "doc_count": 155, + Object { + "docCount": 155, + "value": "true", }, - }, + ], "totalCardinality": 2, } `); @@ -546,17 +551,20 @@ describe('options list expensive queries', () => { expect(suggestionAggBuilder.parse(rawSearchResponseMock, optionsListRequestBodyMock)) .toMatchInlineSnapshot(` Object { - "suggestions": Object { - "cool1": Object { - "doc_count": 5, + "suggestions": Array [ + Object { + "docCount": 5, + "value": "cool1", }, - "cool2": Object { - "doc_count": 15, + Object { + "docCount": 15, + "value": "cool2", }, - "cool3": Object { - "doc_count": 10, + Object { + "docCount": 10, + "value": "cool3", }, - }, + ], "totalCardinality": 3, } `); @@ -621,55 +629,50 @@ describe('options list expensive queries', () => { rawSearchResponseMock, optionsListRequestBodyMock ).suggestions; - /** first, verify that the sorting worked as expected */ - expect(Object.keys(parsed)).toMatchInlineSnapshot(` - Array [ - "52:ae76:5947:5e2a:551:fe6a:712a:c72", - "111.52.174.2", - "196.162.13.39", - "f7a9:640b:b5a0:1219:8d75:ed94:3c3e:2e63", - "23.216.241.120", - "28c7:c9a4:42fd:16b0:4de5:e41e:28d9:9172", - "21.35.91.62", - "21.35.91.61", - "203.88.33.151", - "1ec:aa98:b0a6:d07c:590:18a0:8a33:2eb8", - ] - `); - /** then, make sure the object is structured properly */ + expect(parsed).toMatchInlineSnapshot(` - Object { - "111.52.174.2": Object { - "doc_count": 11, + Array [ + Object { + "docCount": 12, + "value": "52:ae76:5947:5e2a:551:fe6a:712a:c72", }, - "196.162.13.39": Object { - "doc_count": 10, + Object { + "docCount": 11, + "value": "111.52.174.2", }, - "1ec:aa98:b0a6:d07c:590:18a0:8a33:2eb8": Object { - "doc_count": 6, + Object { + "docCount": 10, + "value": "196.162.13.39", }, - "203.88.33.151": Object { - "doc_count": 7, + Object { + "docCount": 10, + "value": "f7a9:640b:b5a0:1219:8d75:ed94:3c3e:2e63", }, - "21.35.91.61": Object { - "doc_count": 8, + Object { + "docCount": 9, + "value": "23.216.241.120", }, - "21.35.91.62": Object { - "doc_count": 8, + Object { + "docCount": 9, + "value": "28c7:c9a4:42fd:16b0:4de5:e41e:28d9:9172", }, - "23.216.241.120": Object { - "doc_count": 9, + Object { + "docCount": 8, + "value": "21.35.91.62", }, - "28c7:c9a4:42fd:16b0:4de5:e41e:28d9:9172": Object { - "doc_count": 9, + Object { + "docCount": 8, + "value": "21.35.91.61", }, - "52:ae76:5947:5e2a:551:fe6a:712a:c72": Object { - "doc_count": 12, + Object { + "docCount": 7, + "value": "203.88.33.151", }, - "f7a9:640b:b5a0:1219:8d75:ed94:3c3e:2e63": Object { - "doc_count": 10, + Object { + "docCount": 6, + "value": "1ec:aa98:b0a6:d07c:590:18a0:8a33:2eb8", }, - } + ] `); }); }); diff --git a/src/plugins/controls/server/options_list/options_list_expensive_suggestion_queries.ts b/src/plugins/controls/server/options_list/options_list_expensive_suggestion_queries.ts index 63347f8d436d3..a1114191d1fa8 100644 --- a/src/plugins/controls/server/options_list/options_list_expensive_suggestion_queries.ts +++ b/src/plugins/controls/server/options_list/options_list_expensive_suggestion_queries.ts @@ -93,9 +93,9 @@ const expensiveSuggestionAggSubtypes: { [key: string]: OptionsListSuggestionAggr const suggestions = get(rawEsResult, `${basePath}.suggestions.buckets`)?.reduce( (acc: OptionsListSuggestions, suggestion: EsBucket) => { - return { ...acc, [suggestion.key]: { doc_count: suggestion.doc_count } }; + return [...acc, { value: suggestion.key, docCount: suggestion.doc_count }]; }, - {} + [] ); return { suggestions, @@ -120,14 +120,11 @@ const expensiveSuggestionAggSubtypes: { [key: string]: OptionsListSuggestionAggr parse: (rawEsResult) => { const suggestions = get(rawEsResult, 'aggregations.suggestions.buckets')?.reduce( (acc: OptionsListSuggestions, suggestion: EsBucket & { key_as_string: string }) => { - return { - ...acc, - [suggestion.key_as_string]: { doc_count: suggestion.doc_count }, - }; + return [...acc, { value: suggestion.key_as_string, docCount: suggestion.doc_count }]; }, - {} + [] ); - return { suggestions, totalCardinality: Object.keys(suggestions).length }; // cardinality is only ever 0, 1, or 2 so safe to use length here + return { suggestions, totalCardinality: suggestions.length }; // cardinality is only ever 0, 1, or 2 so safe to use length here }, }, @@ -185,7 +182,7 @@ const expensiveSuggestionAggSubtypes: { [key: string]: OptionsListSuggestionAggr if (!Boolean(rawEsResult.aggregations?.suggestions)) { // if this is happens, that means there is an invalid search that snuck through to the server side code; // so, might as well early return with no suggestions - return { suggestions: {}, totalCardinality: 0 }; + return { suggestions: [], totalCardinality: 0 }; } const buckets: EsBucket[] = []; getIpBuckets(rawEsResult, buckets, 'ipv4'); // modifies buckets array directly, i.e. "by reference" @@ -200,11 +197,11 @@ const expensiveSuggestionAggSubtypes: { [key: string]: OptionsListSuggestionAggr (bucketA: EsBucket, bucketB: EsBucket) => bucketB.doc_count - bucketA.doc_count ); - const suggestions: OptionsListSuggestions = sortedSuggestions + const suggestions = sortedSuggestions .slice(0, request.size) .reduce((acc: OptionsListSuggestions, suggestion: EsBucket) => { - return { ...acc, [suggestion.key]: { doc_count: suggestion.doc_count } }; - }, {}); + return [...acc, { value: suggestion.key, docCount: suggestion.doc_count }]; + }, []); const totalCardinality = (get(rawEsResult, `aggregations.suggestions.buckets.ipv4.unique_terms.value`) ?? 0) + (get(rawEsResult, `aggregations.suggestions.buckets.ipv6.unique_terms.value`) ?? 0); From 6ec97802d97d54599546e58374866a389ec412ed Mon Sep 17 00:00:00 2001 From: Tiago Costa Date: Wed, 26 Apr 2023 19:44:22 +0100 Subject: [PATCH 32/73] skip failing version bump suite (#155924) --- x-pack/test/accessibility/apps/ingest_node_pipelines.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/x-pack/test/accessibility/apps/ingest_node_pipelines.ts b/x-pack/test/accessibility/apps/ingest_node_pipelines.ts index 4bbd9cde06d2d..a2aa7e3c860fe 100644 --- a/x-pack/test/accessibility/apps/ingest_node_pipelines.ts +++ b/x-pack/test/accessibility/apps/ingest_node_pipelines.ts @@ -14,7 +14,8 @@ export default function ({ getService, getPageObjects }: any) { const log = getService('log'); const a11y = getService('a11y'); /* this is the wrapping service around axe */ - describe('Ingest Pipelines Accessibility', async () => { + // FAILING VERSION BUMP: https://github.com/elastic/kibana/issues/155924 + describe.skip('Ingest Pipelines Accessibility', async () => { before(async () => { await putSamplePipeline(esClient); await common.navigateToApp('ingestPipelines'); From 5e713fb225f5cd2312878c515f61f7f01b4a5e49 Mon Sep 17 00:00:00 2001 From: Nicolas Chaulet Date: Wed, 26 Apr 2023 14:45:25 -0400 Subject: [PATCH 33/73] [Fleet] Add unit test for fleet agent id verification config flag (#155720) --- .../elasticsearch/template/template.test.ts | 33 +++++++++++++++++-- 1 file changed, 30 insertions(+), 3 deletions(-) diff --git a/x-pack/plugins/fleet/server/services/epm/elasticsearch/template/template.test.ts b/x-pack/plugins/fleet/server/services/epm/elasticsearch/template/template.test.ts index 8ef565ccd320a..5668ebacd5258 100644 --- a/x-pack/plugins/fleet/server/services/epm/elasticsearch/template/template.test.ts +++ b/x-pack/plugins/fleet/server/services/epm/elasticsearch/template/template.test.ts @@ -17,6 +17,10 @@ import { appContextService } from '../../..'; import type { RegistryDataStream } from '../../../../types'; import { processFields } from '../../fields/field'; import type { Field } from '../../fields/field'; +import { + FLEET_COMPONENT_TEMPLATES, + FLEET_GLOBALS_COMPONENT_TEMPLATE_NAME, +} from '../../../../constants'; import { generateMappings, @@ -26,7 +30,9 @@ import { updateCurrentWriteIndices, } from './template'; -const FLEET_COMPONENT_TEMPLATES = ['.fleet_globals-1', '.fleet_agent_id_verification-1']; +const FLEET_COMPONENT_TEMPLATES_NAMES = FLEET_COMPONENT_TEMPLATES.map( + (componentTemplate) => componentTemplate.name +); // Add our own serialiser to just do JSON.stringify expect.addSnapshotSerializer({ @@ -69,7 +75,28 @@ describe('EPM template', () => { }); expect(template.composed_of).toStrictEqual([ ...composedOfTemplates, - ...FLEET_COMPONENT_TEMPLATES, + ...FLEET_COMPONENT_TEMPLATES_NAMES, + ]); + }); + + it('does not create fleet agent id verification component template if agentIdVerification is disabled', () => { + appContextService.start( + createAppContextStartContractMock({ + agentIdVerificationEnabled: false, + }) + ); + const composedOfTemplates = ['component1', 'component2']; + + const template = getTemplate({ + templateIndexPattern: 'name-*', + packageName: 'nginx', + composedOfTemplates, + templatePriority: 200, + mappings: { properties: [] }, + }); + expect(template.composed_of).toStrictEqual([ + ...composedOfTemplates, + FLEET_GLOBALS_COMPONENT_TEMPLATE_NAME, ]); }); @@ -83,7 +110,7 @@ describe('EPM template', () => { templatePriority: 200, mappings: { properties: [] }, }); - expect(template.composed_of).toStrictEqual(FLEET_COMPONENT_TEMPLATES); + expect(template.composed_of).toStrictEqual(FLEET_COMPONENT_TEMPLATES_NAMES); }); it('adds hidden field correctly', () => { From 8e37b3841701e0343f265e544920697f1f6a8f59 Mon Sep 17 00:00:00 2001 From: Clint Andrew Hall Date: Wed, 26 Apr 2023 14:48:23 -0400 Subject: [PATCH 34/73] [serverless] Create the Serverless Plugin (#155582) > Derived from https://github.com/elastic/kibana/pull/153274 for production. ## Summary This PR creates the `serverless` plugin for Kibana Serverless projects. ![image](https://user-images.githubusercontent.com/297604/233892935-b3713575-a2f7-4e82-a9dd-e8c11823683f.png) It uses the methodology proven out in the proof-of-concept (https://github.com/elastic/kibana/pull/153274) and prepares it for production: - Adds chrome style and related API to the `chrome` services. - Creates the `serverless` plugin. - Invokes the new chrome style API for all serverless projects. - Alters `yarn` scripts to support all project types, and switching between them. - Creates the new "Project Switcher" component for use in the new chrome header for Serverless. - Creates a Storybook config for this and future components. - Adds API endpoint to trigger project switching and `Watcher` restarts. Screenshot 2023-04-26 at 10 44 01 AM ## Next steps - [x] Creating a PR for enabling/disabling related plugins for Serverless. (https://github.com/elastic/kibana/pull/155583) - [ ] Creating product plugin PR based on https://github.com/elastic/kibana/pull/153274. --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> --- .../steps/storybooks/build_and_upload.ts | 1 + .github/CODEOWNERS | 4 + .i18nrc.json | 1 + config/serverless.es.yml | 1 + config/serverless.oblt.yml | 1 + config/serverless.security.yml | 1 + config/serverless.yml | 2 + docs/developer/plugin-list.asciidoc | 4 + package.json | 5 + .../src/chrome_service.test.ts | 2 + .../src/chrome_service.tsx | 99 ++++++++---- .../src/ui/index.ts | 1 + .../src/ui/project/header.tsx | 90 +++++++++++ .../src/ui/project/index.ts | 9 ++ .../src/ui/project/navigation.tsx | 76 +++++++++ .../tsconfig.json | 5 +- .../src/chrome_service.mock.ts | 2 + .../core/chrome/core-chrome-browser/index.ts | 27 ++-- .../core-chrome-browser/src/contracts.ts | 13 +- .../chrome/core-chrome-browser/src/index.ts | 2 +- .../chrome/core-chrome-browser/src/types.ts | 3 + packages/kbn-optimizer/limits.yml | 1 + .../serverless/project_switcher/README.mdx | 12 ++ packages/serverless/project_switcher/index.ts | 11 ++ .../project_switcher/jest.config.js | 13 ++ .../serverless/project_switcher/kibana.jsonc | 5 + .../project_switcher/mocks/jest.mock.ts | 23 +++ .../project_switcher/mocks/storybook.mock.ts | 51 ++++++ .../serverless/project_switcher/package.json | 6 + .../project_switcher/src/constants.ts | 24 +++ .../project_switcher/src/header_button.tsx | 31 ++++ .../serverless/project_switcher/src/index.ts | 12 ++ .../serverless/project_switcher/src/item.tsx | 33 ++++ .../project_switcher/src/loader.tsx | 21 +++ .../serverless/project_switcher/src/logo.tsx | 32 ++++ .../project_switcher/src/services.tsx | 63 ++++++++ .../src/switcher.component.tsx | 73 +++++++++ .../project_switcher/src/switcher.stories.tsx | 42 +++++ .../project_switcher/src/switcher.test.tsx | 151 ++++++++++++++++++ .../project_switcher/src/switcher.tsx | 21 +++ .../serverless/project_switcher/src/types.ts | 39 +++++ .../serverless/project_switcher/tsconfig.json | 23 +++ .../serverless/storybook/config/README.mdx | 5 + .../serverless/storybook/config/constants.ts | 13 ++ packages/serverless/storybook/config/index.ts | 9 ++ .../serverless/storybook/config/kibana.jsonc | 6 + packages/serverless/storybook/config/main.ts | 17 ++ .../serverless/storybook/config/manager.ts | 23 +++ .../serverless/storybook/config/package.json | 6 + .../serverless/storybook/config/preview.ts | 22 +++ .../serverless/storybook/config/tsconfig.json | 19 +++ packages/serverless/types/README.mdx | 10 ++ packages/serverless/types/index.d.ts | 9 ++ packages/serverless/types/kibana.jsonc | 5 + packages/serverless/types/package.json | 6 + packages/serverless/types/tsconfig.json | 17 ++ src/cli/serve/serve.js | 50 +++++- src/core/public/styles/rendering/_base.scss | 3 + src/dev/storybook/aliases.ts | 1 + .../test_suites/core_plugins/rendering.ts | 2 + tsconfig.base.json | 8 + x-pack/.i18nrc.json | 1 + x-pack/plugins/security/public/config.ts | 1 + .../nav_control/nav_control_service.tsx | 7 +- x-pack/plugins/security/public/plugin.tsx | 1 + x-pack/plugins/security/server/config.test.ts | 6 + x-pack/plugins/security/server/config.ts | 2 + x-pack/plugins/security/server/index.ts | 1 + x-pack/plugins/serverless/README.mdx | 22 +++ x-pack/plugins/serverless/assets/diagram.png | Bin 0 -> 438787 bytes x-pack/plugins/serverless/common/index.ts | 12 ++ x-pack/plugins/serverless/kibana.jsonc | 21 +++ x-pack/plugins/serverless/package.json | 11 ++ x-pack/plugins/serverless/public/config.ts | 15 ++ x-pack/plugins/serverless/public/index.ts | 15 ++ x-pack/plugins/serverless/public/plugin.tsx | 65 ++++++++ x-pack/plugins/serverless/public/types.ts | 12 ++ x-pack/plugins/serverless/server/config.ts | 58 +++++++ x-pack/plugins/serverless/server/index.ts | 16 ++ x-pack/plugins/serverless/server/plugin.ts | 92 +++++++++++ x-pack/plugins/serverless/server/types.ts | 12 ++ x-pack/plugins/serverless/tsconfig.json | 24 +++ yarn.lock | 16 ++ 83 files changed, 1621 insertions(+), 56 deletions(-) create mode 100644 packages/core/chrome/core-chrome-browser-internal/src/ui/project/header.tsx create mode 100644 packages/core/chrome/core-chrome-browser-internal/src/ui/project/index.ts create mode 100644 packages/core/chrome/core-chrome-browser-internal/src/ui/project/navigation.tsx create mode 100644 packages/serverless/project_switcher/README.mdx create mode 100644 packages/serverless/project_switcher/index.ts create mode 100644 packages/serverless/project_switcher/jest.config.js create mode 100644 packages/serverless/project_switcher/kibana.jsonc create mode 100644 packages/serverless/project_switcher/mocks/jest.mock.ts create mode 100644 packages/serverless/project_switcher/mocks/storybook.mock.ts create mode 100644 packages/serverless/project_switcher/package.json create mode 100644 packages/serverless/project_switcher/src/constants.ts create mode 100644 packages/serverless/project_switcher/src/header_button.tsx create mode 100644 packages/serverless/project_switcher/src/index.ts create mode 100644 packages/serverless/project_switcher/src/item.tsx create mode 100644 packages/serverless/project_switcher/src/loader.tsx create mode 100644 packages/serverless/project_switcher/src/logo.tsx create mode 100644 packages/serverless/project_switcher/src/services.tsx create mode 100644 packages/serverless/project_switcher/src/switcher.component.tsx create mode 100644 packages/serverless/project_switcher/src/switcher.stories.tsx create mode 100644 packages/serverless/project_switcher/src/switcher.test.tsx create mode 100644 packages/serverless/project_switcher/src/switcher.tsx create mode 100644 packages/serverless/project_switcher/src/types.ts create mode 100644 packages/serverless/project_switcher/tsconfig.json create mode 100644 packages/serverless/storybook/config/README.mdx create mode 100644 packages/serverless/storybook/config/constants.ts create mode 100755 packages/serverless/storybook/config/index.ts create mode 100644 packages/serverless/storybook/config/kibana.jsonc create mode 100644 packages/serverless/storybook/config/main.ts create mode 100644 packages/serverless/storybook/config/manager.ts create mode 100644 packages/serverless/storybook/config/package.json create mode 100644 packages/serverless/storybook/config/preview.ts create mode 100644 packages/serverless/storybook/config/tsconfig.json create mode 100644 packages/serverless/types/README.mdx create mode 100644 packages/serverless/types/index.d.ts create mode 100644 packages/serverless/types/kibana.jsonc create mode 100644 packages/serverless/types/package.json create mode 100644 packages/serverless/types/tsconfig.json create mode 100755 x-pack/plugins/serverless/README.mdx create mode 100644 x-pack/plugins/serverless/assets/diagram.png create mode 100644 x-pack/plugins/serverless/common/index.ts create mode 100644 x-pack/plugins/serverless/kibana.jsonc create mode 100644 x-pack/plugins/serverless/package.json create mode 100644 x-pack/plugins/serverless/public/config.ts create mode 100644 x-pack/plugins/serverless/public/index.ts create mode 100644 x-pack/plugins/serverless/public/plugin.tsx create mode 100644 x-pack/plugins/serverless/public/types.ts create mode 100644 x-pack/plugins/serverless/server/config.ts create mode 100644 x-pack/plugins/serverless/server/index.ts create mode 100644 x-pack/plugins/serverless/server/plugin.ts create mode 100644 x-pack/plugins/serverless/server/types.ts create mode 100644 x-pack/plugins/serverless/tsconfig.json diff --git a/.buildkite/scripts/steps/storybooks/build_and_upload.ts b/.buildkite/scripts/steps/storybooks/build_and_upload.ts index b16e75abdb8a1..3796076d0a3cd 100644 --- a/.buildkite/scripts/steps/storybooks/build_and_upload.ts +++ b/.buildkite/scripts/steps/storybooks/build_and_upload.ts @@ -43,6 +43,7 @@ const STORYBOOKS = [ 'observability', 'presentation', 'security_solution', + 'serverless', 'shared_ux', 'triggers_actions_ui', 'ui_actions_enhanced', diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 93f49f1277ac7..d76ed31b2b2b1 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -572,6 +572,10 @@ packages/kbn-securitysolution-t-grid @elastic/security-solution-platform packages/kbn-securitysolution-utils @elastic/security-solution-platform packages/kbn-server-http-tools @elastic/kibana-core packages/kbn-server-route-repository @elastic/apm-ui +x-pack/plugins/serverless @elastic/appex-sharedux +packages/serverless/project_switcher @elastic/appex-sharedux +packages/serverless/storybook/config @elastic/appex-sharedux +packages/serverless/types @elastic/appex-sharedux test/plugin_functional/plugins/session_notifications @elastic/kibana-core x-pack/plugins/session_view @elastic/sec-cloudnative-integrations packages/kbn-set-map @elastic/kibana-operations diff --git a/.i18nrc.json b/.i18nrc.json index 31ab7a91e206c..c2bf494c68c06 100644 --- a/.i18nrc.json +++ b/.i18nrc.json @@ -83,6 +83,7 @@ "share": "src/plugins/share", "sharedUXPackages": "packages/shared-ux", "securitySolutionPackages": "x-pack/packages/security-solution", + "serverlessPackages": "packages/serverless", "coloring": "packages/kbn-coloring/src", "languageDocumentationPopover": "packages/kbn-language-documentation-popover/src", "statusPage": "src/legacy/core_plugins/status_page", diff --git a/config/serverless.es.yml b/config/serverless.es.yml index e69de29bb2d1d..71b4f03446401 100644 --- a/config/serverless.es.yml +++ b/config/serverless.es.yml @@ -0,0 +1 @@ +xpack.serverless.plugin.developer.projectSwitcher.currentType: 'search' diff --git a/config/serverless.oblt.yml b/config/serverless.oblt.yml index ba76648238348..ddf1066edb882 100644 --- a/config/serverless.oblt.yml +++ b/config/serverless.oblt.yml @@ -1 +1,2 @@ xpack.infra.logs.app_target: discover +xpack.serverless.plugin.developer.projectSwitcher.currentType: 'observability' diff --git a/config/serverless.security.yml b/config/serverless.security.yml index e69de29bb2d1d..efa3558e0e9d9 100644 --- a/config/serverless.security.yml +++ b/config/serverless.security.yml @@ -0,0 +1 @@ +xpack.serverless.plugin.developer.projectSwitcher.currentType: 'security' diff --git a/config/serverless.yml b/config/serverless.yml index e65b15f064328..ec24139422975 100644 --- a/config/serverless.yml +++ b/config/serverless.yml @@ -1,4 +1,6 @@ newsfeed.enabled: false +xpack.security.showNavLinks: false +xpack.serverless.plugin.enabled: true xpack.fleet.enableExperimental: ['fleetServerStandalone'] xpack.fleet.internal.disableILMPolicies: true diff --git a/docs/developer/plugin-list.asciidoc b/docs/developer/plugin-list.asciidoc index 6b534f45b4f2d..412a9e8b5569e 100644 --- a/docs/developer/plugin-list.asciidoc +++ b/docs/developer/plugin-list.asciidoc @@ -706,6 +706,10 @@ Kibana. |Welcome to the Kibana Security Solution plugin! This README will go over getting started with development and testing. +|{kib-repo}blob/{branch}/x-pack/plugins/serverless/README.mdx[serverless] +| + + |{kib-repo}blob/{branch}/x-pack/plugins/session_view/README.md[sessionView] |Session View is meant to provide a visualization into what is going on in a particular Linux environment where the agent is running. It looks likes a terminal emulator; however, it is a tool for introspecting process activity and understanding user and service behaviour in your Linux servers and infrastructure. It is a time-ordered series of process executions displayed in a tree over time. diff --git a/package.json b/package.json index e6761869fa3c7..0751d8d84ea09 100644 --- a/package.json +++ b/package.json @@ -54,6 +54,7 @@ "lint:es": "node scripts/eslint", "lint:style": "node scripts/stylelint", "makelogs": "node scripts/makelogs", + "serverless": "node scripts/kibana --dev --serverless", "serverless-es": "node scripts/kibana --dev --serverless=es", "serverless-oblt": "node scripts/kibana --dev --serverless=oblt", "serverless-security": "node scripts/kibana --dev --serverless=security", @@ -573,6 +574,9 @@ "@kbn/securitysolution-utils": "link:packages/kbn-securitysolution-utils", "@kbn/server-http-tools": "link:packages/kbn-server-http-tools", "@kbn/server-route-repository": "link:packages/kbn-server-route-repository", + "@kbn/serverless": "link:x-pack/plugins/serverless", + "@kbn/serverless-project-switcher": "link:packages/serverless/project_switcher", + "@kbn/serverless-types": "link:packages/serverless/types", "@kbn/session-notifications-plugin": "link:test/plugin_functional/plugins/session_notifications", "@kbn/session-view-plugin": "link:x-pack/plugins/session_view", "@kbn/set-map": "link:packages/kbn-set-map", @@ -1112,6 +1116,7 @@ "@kbn/repo-source-classifier": "link:packages/kbn-repo-source-classifier", "@kbn/repo-source-classifier-cli": "link:packages/kbn-repo-source-classifier-cli", "@kbn/security-api-integration-helpers": "link:x-pack/test/security_api_integration/packages/helpers", + "@kbn/serverless-storybook-config": "link:packages/serverless/storybook/config", "@kbn/some-dev-log": "link:packages/kbn-some-dev-log", "@kbn/sort-package-json": "link:packages/kbn-sort-package-json", "@kbn/spec-to-console": "link:packages/kbn-spec-to-console", diff --git a/packages/core/chrome/core-chrome-browser-internal/src/chrome_service.test.ts b/packages/core/chrome/core-chrome-browser-internal/src/chrome_service.test.ts index 37b1b9a2eab7d..0087c5d019f98 100644 --- a/packages/core/chrome/core-chrome-browser-internal/src/chrome_service.test.ts +++ b/packages/core/chrome/core-chrome-browser-internal/src/chrome_service.test.ts @@ -126,6 +126,7 @@ describe('start', () => { Array [ Array [ "kbnBody", + "kbnBody--classicLayout", "kbnBody--noHeaderBanner", "kbnBody--chromeHidden", "kbnVersion-1-2-3", @@ -143,6 +144,7 @@ describe('start', () => { Array [ Array [ "kbnBody", + "kbnBody--classicLayout", "kbnBody--noHeaderBanner", "kbnBody--chromeHidden", "kbnVersion-8-0-0", diff --git a/packages/core/chrome/core-chrome-browser-internal/src/chrome_service.tsx b/packages/core/chrome/core-chrome-browser-internal/src/chrome_service.tsx index 4e0762aee8620..41362b3d80dcd 100644 --- a/packages/core/chrome/core-chrome-browser-internal/src/chrome_service.tsx +++ b/packages/core/chrome/core-chrome-browser-internal/src/chrome_service.tsx @@ -26,6 +26,7 @@ import type { ChromeGlobalHelpExtensionMenuLink, ChromeHelpExtension, ChromeUserBanner, + ChromeStyle, } from '@kbn/core-chrome-browser'; import type { CustomBrandingStart } from '@kbn/core-custom-branding-browser'; import { KIBANA_ASK_ELASTIC_LINK } from './constants'; @@ -33,7 +34,7 @@ import { DocTitleService } from './doc_title'; import { NavControlsService } from './nav_controls'; import { NavLinksService } from './nav_links'; import { RecentlyAccessedService } from './recently_accessed'; -import { Header } from './ui'; +import { Header, ProjectHeader } from './ui'; import type { InternalChromeStart } from './types'; const IS_LOCKED_KEY = 'core.chrome.isLocked'; @@ -119,6 +120,7 @@ export class ChromeService { const customNavLink$ = new BehaviorSubject(undefined); const helpSupportUrl$ = new BehaviorSubject(KIBANA_ASK_ELASTIC_LINK); const isNavDrawerLocked$ = new BehaviorSubject(localStorage.getItem(IS_LOCKED_KEY) === 'true'); + const chromeStyle$ = new BehaviorSubject('classic'); const getKbnVersionClass = () => { // we assume that the version is valid and has the form 'X.X.X' @@ -131,10 +133,11 @@ export class ChromeService { }; const headerBanner$ = new BehaviorSubject(undefined); - const bodyClasses$ = combineLatest([headerBanner$, this.isVisible$!]).pipe( - map(([headerBanner, isVisible]) => { + const bodyClasses$ = combineLatest([headerBanner$, this.isVisible$!, chromeStyle$]).pipe( + map(([headerBanner, isVisible, chromeStyle]) => { return [ 'kbnBody', + chromeStyle === 'project' ? 'kbnBody--projectLayout' : 'kbnBody--classicLayout', headerBanner ? 'kbnBody--hasHeaderBanner' : 'kbnBody--noHeaderBanner', isVisible ? 'kbnBody--chromeVisible' : 'kbnBody--chromeHidden', getKbnVersionClass(), @@ -163,6 +166,10 @@ export class ChromeService { const getIsNavDrawerLocked$ = isNavDrawerLocked$.pipe(takeUntil(this.stop$)); + const setChromeStyle = (style: ChromeStyle) => { + chromeStyle$.next(style); + }; + const isIE = () => { const ua = window.navigator.userAgent; const msie = ua.indexOf('MSIE '); // IE 10 or older @@ -203,41 +210,65 @@ export class ChromeService { }); } + const getHeaderComponent = () => { + const Component = ({ style$ }: { style$: typeof chromeStyle$ }) => { + if (style$.getValue() === 'project') { + return ( + + ); + } + + return ( +
+ ); + }; + return ; + }; + return { navControls, navLinks, recentlyAccessed, docTitle, - - getHeaderComponent: () => ( -
- ), + getHeaderComponent, getIsVisible$: () => this.isVisible$, @@ -302,6 +333,8 @@ export class ChromeService { }, getBodyClasses$: () => bodyClasses$.pipe(takeUntil(this.stop$)), + setChromeStyle, + getChromeStyle$: () => chromeStyle$.pipe(takeUntil(this.stop$)), }; } diff --git a/packages/core/chrome/core-chrome-browser-internal/src/ui/index.ts b/packages/core/chrome/core-chrome-browser-internal/src/ui/index.ts index 5afd3e0f587bb..7a5ecadd26f23 100644 --- a/packages/core/chrome/core-chrome-browser-internal/src/ui/index.ts +++ b/packages/core/chrome/core-chrome-browser-internal/src/ui/index.ts @@ -7,5 +7,6 @@ */ export { Header } from './header'; +export { ProjectHeader } from './project'; export { LoadingIndicator } from './loading_indicator'; export type { NavType } from './header'; diff --git a/packages/core/chrome/core-chrome-browser-internal/src/ui/project/header.tsx b/packages/core/chrome/core-chrome-browser-internal/src/ui/project/header.tsx new file mode 100644 index 0000000000000..e85ae262c3bb7 --- /dev/null +++ b/packages/core/chrome/core-chrome-browser-internal/src/ui/project/header.tsx @@ -0,0 +1,90 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import React from 'react'; +import { Router } from 'react-router-dom'; +import { EuiHeader, EuiHeaderLogo, EuiHeaderSection, EuiHeaderSectionItem } from '@elastic/eui'; +import { + ChromeBreadcrumb, + ChromeGlobalHelpExtensionMenuLink, + ChromeHelpExtension, + ChromeNavControl, +} from '@kbn/core-chrome-browser/src'; +import { Observable } from 'rxjs'; +import { MountPoint } from '@kbn/core-mount-utils-browser'; +import { InternalApplicationStart } from '@kbn/core-application-browser-internal'; +import { HeaderBreadcrumbs } from '../header/header_breadcrumbs'; +import { HeaderActionMenu } from '../header/header_action_menu'; +import { HeaderHelpMenu } from '../header/header_help_menu'; +import { HeaderNavControls } from '../header/header_nav_controls'; +import { ProjectNavigation } from './navigation'; + +interface Props { + breadcrumbs$: Observable; + actionMenu$: Observable; + kibanaDocLink: string; + globalHelpExtensionMenuLinks$: Observable; + helpExtension$: Observable; + helpSupportUrl$: Observable; + kibanaVersion: string; + application: InternalApplicationStart; + navControlsRight$: Observable; +} + +export const ProjectHeader = ({ + application, + kibanaDocLink, + kibanaVersion, + ...observables +}: Props) => { + const renderLogo = () => ( + e.preventDefault()} + aria-label="Go to home page" + /> + ); + + return ( + <> + + + {renderLogo()} + + + + + + + + + + + + + + + + + + + + + + + + ); +}; diff --git a/packages/core/chrome/core-chrome-browser-internal/src/ui/project/index.ts b/packages/core/chrome/core-chrome-browser-internal/src/ui/project/index.ts new file mode 100644 index 0000000000000..af18e057731b0 --- /dev/null +++ b/packages/core/chrome/core-chrome-browser-internal/src/ui/project/index.ts @@ -0,0 +1,9 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +export { ProjectHeader } from './header'; diff --git a/packages/core/chrome/core-chrome-browser-internal/src/ui/project/navigation.tsx b/packages/core/chrome/core-chrome-browser-internal/src/ui/project/navigation.tsx new file mode 100644 index 0000000000000..20549325ec851 --- /dev/null +++ b/packages/core/chrome/core-chrome-browser-internal/src/ui/project/navigation.tsx @@ -0,0 +1,76 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 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, { useCallback } from 'react'; +import useLocalStorage from 'react-use/lib/useLocalStorage'; +import { css } from '@emotion/react'; + +import { i18n } from '@kbn/i18n'; +import { EuiButtonIcon, EuiCollapsibleNav, EuiThemeProvider, useEuiTheme } from '@elastic/eui'; + +const LOCAL_STORAGE_IS_OPEN_KEY = 'PROJECT_NAVIGATION_OPEN' as const; +const SIZE_OPEN = 248; +const SIZE_CLOSED = 40; + +const buttonCSS = css` + margin-left: -32px; + margin-top: 12px; + position: fixed; + z-index: 1000; +`; + +const openAriaLabel = i18n.translate('core.ui.chrome.projectNav.collapsibleNavOpenAriaLabel', { + defaultMessage: 'Close navigation', +}); + +const closedAriaLabel = i18n.translate('core.ui.chrome.projectNav.collapsibleNavClosedAriaLabel', { + defaultMessage: 'Open navigation', +}); + +export const ProjectNavigation: React.FC = ({ children }) => { + const { euiTheme, colorMode } = useEuiTheme(); + + const [isOpen, setIsOpen] = useLocalStorage(LOCAL_STORAGE_IS_OPEN_KEY, true); + + const toggleOpen = useCallback(() => { + setIsOpen(!isOpen); + }, [isOpen, setIsOpen]); + + const collabsibleNavCSS = css` + border-inline-end-width: 1, + background: ${euiTheme.colors.darkestShade}, + display: flex, + flex-direction: row, + `; + + return ( + + + + + } + > + {isOpen && children} + + + ); +}; diff --git a/packages/core/chrome/core-chrome-browser-internal/tsconfig.json b/packages/core/chrome/core-chrome-browser-internal/tsconfig.json index 4d4d6cad3bc21..cd27209bef12c 100644 --- a/packages/core/chrome/core-chrome-browser-internal/tsconfig.json +++ b/packages/core/chrome/core-chrome-browser-internal/tsconfig.json @@ -5,7 +5,10 @@ "types": [ "jest", "node", - "react" + "react", + "@kbn/ambient-ui-types", + "@kbn/ambient-storybook-types", + "@emotion/react/types/css-prop" ] }, "include": [ diff --git a/packages/core/chrome/core-chrome-browser-mocks/src/chrome_service.mock.ts b/packages/core/chrome/core-chrome-browser-mocks/src/chrome_service.mock.ts index 2f5c4deb1f38d..c7c62c7811277 100644 --- a/packages/core/chrome/core-chrome-browser-mocks/src/chrome_service.mock.ts +++ b/packages/core/chrome/core-chrome-browser-mocks/src/chrome_service.mock.ts @@ -61,6 +61,8 @@ const createStartContractMock = () => { setHeaderBanner: jest.fn(), hasHeaderBanner$: jest.fn(), getBodyClasses$: jest.fn(), + getChromeStyle$: jest.fn(), + setChromeStyle: jest.fn(), }; startContract.navLinks.getAll.mockReturnValue([]); startContract.getIsVisible$.mockReturnValue(new BehaviorSubject(false)); diff --git a/packages/core/chrome/core-chrome-browser/index.ts b/packages/core/chrome/core-chrome-browser/index.ts index 3fbef34126a4a..1d2dca4c957bc 100644 --- a/packages/core/chrome/core-chrome-browser/index.ts +++ b/packages/core/chrome/core-chrome-browser/index.ts @@ -7,25 +7,26 @@ */ export type { - ChromeUserBanner, + ChromeBadge, ChromeBreadcrumb, + ChromeBreadcrumbsAppendExtension, + ChromeDocTitle, + ChromeGlobalHelpExtensionMenuLink, ChromeHelpExtension, - ChromeHelpExtensionMenuLink, ChromeHelpExtensionLinkBase, + ChromeHelpExtensionMenuCustomLink, + ChromeHelpExtensionMenuDiscussLink, + ChromeHelpExtensionMenuDocumentationLink, + ChromeHelpExtensionMenuGitHubLink, + ChromeHelpExtensionMenuLink, ChromeHelpMenuActions, - ChromeNavLink, - ChromeBreadcrumbsAppendExtension, - ChromeNavLinks, ChromeNavControl, ChromeNavControls, - ChromeBadge, - ChromeHelpExtensionMenuGitHubLink, - ChromeHelpExtensionMenuDocumentationLink, - ChromeHelpExtensionMenuDiscussLink, - ChromeHelpExtensionMenuCustomLink, - ChromeGlobalHelpExtensionMenuLink, - ChromeDocTitle, - ChromeStart, + ChromeNavLink, + ChromeNavLinks, ChromeRecentlyAccessed, ChromeRecentlyAccessedHistoryItem, + ChromeStart, + ChromeStyle, + ChromeUserBanner, } from './src'; diff --git a/packages/core/chrome/core-chrome-browser/src/contracts.ts b/packages/core/chrome/core-chrome-browser/src/contracts.ts index a81d9c3c6338f..3f6f756e2d2b1 100644 --- a/packages/core/chrome/core-chrome-browser/src/contracts.ts +++ b/packages/core/chrome/core-chrome-browser/src/contracts.ts @@ -13,7 +13,7 @@ import type { ChromeDocTitle } from './doc_title'; import type { ChromeNavControls } from './nav_controls'; import type { ChromeHelpExtension } from './help_extension'; import type { ChromeBreadcrumb, ChromeBreadcrumbsAppendExtension } from './breadcrumb'; -import type { ChromeBadge, ChromeUserBanner } from './types'; +import type { ChromeBadge, ChromeStyle, ChromeUserBanner } from './types'; import { ChromeGlobalHelpExtensionMenuLink } from './help_extension'; /** @@ -150,4 +150,15 @@ export interface ChromeStart { * Get an observable of the current header banner presence state. */ hasHeaderBanner$(): Observable; + + /** + * Sets the style type of the chrome. + * @param style The style type to apply to the chrome. + */ + setChromeStyle(style: ChromeStyle): void; + + /** + * Get an observable of the current style type of the chrome. + */ + getChromeStyle$(): Observable; } diff --git a/packages/core/chrome/core-chrome-browser/src/index.ts b/packages/core/chrome/core-chrome-browser/src/index.ts index 716af097fded7..89ba12d616d0e 100644 --- a/packages/core/chrome/core-chrome-browser/src/index.ts +++ b/packages/core/chrome/core-chrome-browser/src/index.ts @@ -26,4 +26,4 @@ export type { ChromeRecentlyAccessed, ChromeRecentlyAccessedHistoryItem, } from './recently_accessed'; -export type { ChromeBadge, ChromeUserBanner } from './types'; +export type { ChromeBadge, ChromeUserBanner, ChromeStyle } from './types'; diff --git a/packages/core/chrome/core-chrome-browser/src/types.ts b/packages/core/chrome/core-chrome-browser/src/types.ts index 81b8c32a1a04c..d4374687ff828 100644 --- a/packages/core/chrome/core-chrome-browser/src/types.ts +++ b/packages/core/chrome/core-chrome-browser/src/types.ts @@ -20,3 +20,6 @@ export interface ChromeBadge { export interface ChromeUserBanner { content: MountPoint; } + +/** @public */ +export type ChromeStyle = 'classic' | 'project'; diff --git a/packages/kbn-optimizer/limits.yml b/packages/kbn-optimizer/limits.yml index 56259881447db..af6396c11e063 100644 --- a/packages/kbn-optimizer/limits.yml +++ b/packages/kbn-optimizer/limits.yml @@ -115,6 +115,7 @@ pageLoadAssetSize: searchprofiler: 67080 security: 65433 securitySolution: 66738 + serverless: 16573 sessionView: 77750 share: 71239 snapshotRestore: 79032 diff --git a/packages/serverless/project_switcher/README.mdx b/packages/serverless/project_switcher/README.mdx new file mode 100644 index 0000000000000..240988346458c --- /dev/null +++ b/packages/serverless/project_switcher/README.mdx @@ -0,0 +1,12 @@ +--- +id: serverless/components/ProjectSwitcher +slug: /serverless/components/project-switcher +title: Project Switcher +description: A popup which allows a developer to switch between project types on their dev server. +tags: ['serverless', 'component'] +date: 2023-04-23 +--- + +When working on Serverless instances of Kibana, developers likely want to switch between different project types to test changes. This Project Switcher is intended to be placed into the header bar by the Serverless plugin when the server is in development mode to allow "quick switching" between configurations. + +The connected component uses `http` to post a selection to a given API endpoint, intended to alter the YML configuration and trigger Watcher to restart the server. To that end, it will post its message to a given API endpoint and replace the content of `document.body`. The remainder of the process is left to the Serverless plugin. diff --git a/packages/serverless/project_switcher/index.ts b/packages/serverless/project_switcher/index.ts new file mode 100644 index 0000000000000..69148308099a5 --- /dev/null +++ b/packages/serverless/project_switcher/index.ts @@ -0,0 +1,11 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +export type { ProjectSwitcherProps, KibanaDependencies } from './src'; + +export { ProjectSwitcher, ProjectSwitcherKibanaProvider, ProjectSwitcherProvider } from './src'; diff --git a/packages/serverless/project_switcher/jest.config.js b/packages/serverless/project_switcher/jest.config.js new file mode 100644 index 0000000000000..713bdaaedaca2 --- /dev/null +++ b/packages/serverless/project_switcher/jest.config.js @@ -0,0 +1,13 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +module.exports = { + preset: '@kbn/test', + rootDir: '../../..', + roots: ['/packages/serverless/project_switcher'], +}; diff --git a/packages/serverless/project_switcher/kibana.jsonc b/packages/serverless/project_switcher/kibana.jsonc new file mode 100644 index 0000000000000..6e37bb95cafda --- /dev/null +++ b/packages/serverless/project_switcher/kibana.jsonc @@ -0,0 +1,5 @@ +{ + "type": "shared-common", + "id": "@kbn/serverless-project-switcher", + "owner": "@elastic/appex-sharedux" +} diff --git a/packages/serverless/project_switcher/mocks/jest.mock.ts b/packages/serverless/project_switcher/mocks/jest.mock.ts new file mode 100644 index 0000000000000..935b89b63dd13 --- /dev/null +++ b/packages/serverless/project_switcher/mocks/jest.mock.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 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 { Services, KibanaDependencies } from '../src/types'; + +export const getProjectSwitcherServicesMock: () => jest.Mocked = () => ({ + setProjectType: jest.fn(), +}); + +export const getProjectSwitcherKibanaDependenciesMock: () => jest.Mocked = + () => ({ + coreStart: { + http: { + post: jest.fn(() => Promise.resolve({ data: {} })), + }, + }, + projectChangeAPIUrl: 'serverless/change_project', + }); diff --git a/packages/serverless/project_switcher/mocks/storybook.mock.ts b/packages/serverless/project_switcher/mocks/storybook.mock.ts new file mode 100644 index 0000000000000..08faa007c5eb5 --- /dev/null +++ b/packages/serverless/project_switcher/mocks/storybook.mock.ts @@ -0,0 +1,51 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { action } from '@storybook/addon-actions'; +import { AbstractStorybookMock } from '@kbn/shared-ux-storybook-mock'; + +import type { ProjectSwitcherProps, Services } from '../src/types'; + +type PropArguments = Pick; + +/** + * Storybook parameters provided from the controls addon. + */ +export type ProjectSwitcherStorybookParams = Record; + +/** + * Storybook mocks for the `NoDataCard` component. + */ +export class ProjectSwitcherStorybookMock extends AbstractStorybookMock< + ProjectSwitcherProps, + Services, + PropArguments, + {} +> { + propArguments = { + currentProjectType: { + control: { type: 'radio' }, + options: ['observability', 'security', 'search'], + defaultValue: 'observability', + }, + }; + serviceArguments = {}; + dependencies = []; + + getProps(params?: ProjectSwitcherStorybookParams): ProjectSwitcherProps { + return { + currentProjectType: this.getArgumentValue('currentProjectType', params), + }; + } + + getServices(_params: ProjectSwitcherStorybookParams): Services { + return { + setProjectType: action('setProjectType'), + }; + } +} diff --git a/packages/serverless/project_switcher/package.json b/packages/serverless/project_switcher/package.json new file mode 100644 index 0000000000000..5910a823783d5 --- /dev/null +++ b/packages/serverless/project_switcher/package.json @@ -0,0 +1,6 @@ +{ + "name": "@kbn/serverless-project-switcher", + "private": true, + "version": "1.0.0", + "license": "SSPL-1.0 OR Elastic License 2.0" +} \ No newline at end of file diff --git a/packages/serverless/project_switcher/src/constants.ts b/packages/serverless/project_switcher/src/constants.ts new file mode 100644 index 0000000000000..e3a277bfc7953 --- /dev/null +++ b/packages/serverless/project_switcher/src/constants.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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import type { IconType } from '@elastic/eui'; +import type { ProjectType } from '@kbn/serverless-types'; + +export const icons: Record = { + observability: 'logoObservability', + security: 'logoSecurity', + search: 'logoEnterpriseSearch', +} as const; + +export const labels: Record = { + observability: 'Observability', + security: 'Security', + search: 'Enterprise Search', +} as const; + +export const projectTypes: ProjectType[] = ['security', 'observability', 'search']; diff --git a/packages/serverless/project_switcher/src/header_button.tsx b/packages/serverless/project_switcher/src/header_button.tsx new file mode 100644 index 0000000000000..ee1bd0acc5888 --- /dev/null +++ b/packages/serverless/project_switcher/src/header_button.tsx @@ -0,0 +1,31 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 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, { MouseEventHandler } from 'react'; +import { EuiHeaderSectionItemButton, EuiIcon } from '@elastic/eui'; + +import { ProjectType } from '@kbn/serverless-types'; + +import { icons } from './constants'; + +export const TEST_ID = 'projectSwitcherButton'; + +export interface Props { + onClick: MouseEventHandler; + currentProjectType: ProjectType; +} + +export const HeaderButton = ({ onClick, currentProjectType }: Props) => ( + + + +); diff --git a/packages/serverless/project_switcher/src/index.ts b/packages/serverless/project_switcher/src/index.ts new file mode 100644 index 0000000000000..adb6beef6514f --- /dev/null +++ b/packages/serverless/project_switcher/src/index.ts @@ -0,0 +1,12 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +export type { KibanaDependencies, ProjectSwitcherProps } from './types'; + +export { ProjectSwitcher } from './switcher'; +export { ProjectSwitcherKibanaProvider, ProjectSwitcherProvider } from './services'; diff --git a/packages/serverless/project_switcher/src/item.tsx b/packages/serverless/project_switcher/src/item.tsx new file mode 100644 index 0000000000000..71bca220c450a --- /dev/null +++ b/packages/serverless/project_switcher/src/item.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 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 { EuiIcon, EuiKeyPadMenuItem, type EuiIconProps } from '@elastic/eui'; +import { ProjectType } from '@kbn/serverless-types'; + +import { labels, icons } from './constants'; + +type OnChangeType = (id: string, value?: any) => void; + +interface ItemProps extends Pick { + type: ProjectType; + onChange: (type: ProjectType) => void; + isSelected: boolean; +} + +export const SwitcherItem = ({ type: id, onChange, isSelected }: ItemProps) => ( + + + +); diff --git a/packages/serverless/project_switcher/src/loader.tsx b/packages/serverless/project_switcher/src/loader.tsx new file mode 100644 index 0000000000000..854f6e6d9f61b --- /dev/null +++ b/packages/serverless/project_switcher/src/loader.tsx @@ -0,0 +1,21 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import React from 'react'; + +import { Logo, type Props } from './logo'; + +export const Loader = (props: Props) => ( +
+
+ +
Loading Project
+
+
+
+); diff --git a/packages/serverless/project_switcher/src/logo.tsx b/packages/serverless/project_switcher/src/logo.tsx new file mode 100644 index 0000000000000..e0d827b18f4f2 --- /dev/null +++ b/packages/serverless/project_switcher/src/logo.tsx @@ -0,0 +1,32 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 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 { EuiIcon } from '@elastic/eui'; +import type { ProjectType } from '@kbn/serverless-types'; + +export interface Props { + project: ProjectType; +} + +export const Logo = ({ project }: Props) => { + let type = 'logoElastic'; + switch (project) { + case 'search': + type = 'logoElasticsearch'; + break; + case 'security': + type = 'logoSecurity'; + break; + case 'observability': + type = 'logoObservability'; + break; + } + + return ; +}; diff --git a/packages/serverless/project_switcher/src/services.tsx b/packages/serverless/project_switcher/src/services.tsx new file mode 100644 index 0000000000000..413de92d4f1ad --- /dev/null +++ b/packages/serverless/project_switcher/src/services.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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import React, { FC, useContext } from 'react'; +import ReactDOM from 'react-dom'; +import { Loader } from './loader'; + +import type { Services, KibanaDependencies } from './types'; + +const Context = React.createContext(null); + +/** + * A Context Provider that provides services to the component and its dependencies. + */ +export const ProjectSwitcherProvider: FC = ({ children, ...services }) => { + return {children}; +}; + +/** + * Kibana-specific Provider that maps dependencies to services. + */ +export const ProjectSwitcherKibanaProvider: FC = ({ + children, + coreStart, + projectChangeAPIUrl, +}) => { + const value: Services = { + setProjectType: (projectType) => { + coreStart.http + .post(projectChangeAPIUrl, { body: JSON.stringify({ id: projectType }) }) + .then(() => { + ReactDOM.render(, document.body); + + // Give the watcher a couple of seconds to see the file change. + setTimeout(() => { + window.location.href = '/'; + }, 2000); + }); + }, + }; + + return {children}; +}; + +/** + * React hook for accessing pre-wired services. + */ +export function useServices() { + const context = useContext(Context); + + if (!context) { + throw new Error( + 'ProjectSwitcher Context is missing. Ensure your component or React root is wrapped with ProjectSwitcherContext.' + ); + } + + return context; +} diff --git a/packages/serverless/project_switcher/src/switcher.component.tsx b/packages/serverless/project_switcher/src/switcher.component.tsx new file mode 100644 index 0000000000000..fd319f3839d6d --- /dev/null +++ b/packages/serverless/project_switcher/src/switcher.component.tsx @@ -0,0 +1,73 @@ +/* + * Copyright 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, { useState } from 'react'; +import { css } from '@emotion/react'; +import { EuiPopover, useGeneratedHtmlId, EuiPopoverTitle, EuiKeyPadMenu } from '@elastic/eui'; + +import { ProjectType } from '@kbn/serverless-types'; + +import { SwitcherItem } from './item'; +import type { ProjectSwitcherComponentProps } from './types'; +import { HeaderButton } from './header_button'; +import { projectTypes } from './constants'; + +export { TEST_ID as TEST_ID_BUTTON } from './header_button'; +export const TEST_ID_ITEM_GROUP = 'projectSwitcherItemGroup'; + +const switcherCSS = css` + min-width: 240px; +`; + +export const ProjectSwitcher = ({ + currentProjectType, + onProjectChange, +}: ProjectSwitcherComponentProps) => { + const [isOpen, setIsOpen] = useState(false); + const id = useGeneratedHtmlId({ + prefix: 'switcherPopover', + }); + + const closePopover = () => { + setIsOpen(false); + }; + + const onButtonClick = () => { + setIsOpen(!isOpen); + }; + + const onChange = (projectType: ProjectType) => { + closePopover(); + onProjectChange(projectType); + return false; + }; + + const items = projectTypes.map((type) => ( + + )); + + const button = ; + + return ( + + Switch Project Type + + {items} + + + ); +}; diff --git a/packages/serverless/project_switcher/src/switcher.stories.tsx b/packages/serverless/project_switcher/src/switcher.stories.tsx new file mode 100644 index 0000000000000..09bece7b00f27 --- /dev/null +++ b/packages/serverless/project_switcher/src/switcher.stories.tsx @@ -0,0 +1,42 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import React from 'react'; + +import { + ProjectSwitcherStorybookMock, + type ProjectSwitcherStorybookParams, +} from '../mocks/storybook.mock'; + +import { ProjectSwitcher as Component } from './switcher'; +import { ProjectSwitcherProvider as Provider } from './services'; + +import mdx from '../README.mdx'; + +export default { + title: 'Developer/Project Switcher', + description: '', + parameters: { + docs: { + page: mdx, + }, + }, +}; + +const mock = new ProjectSwitcherStorybookMock(); +const argTypes = mock.getArgumentTypes(); + +export const ProjectSwitcher = (params: ProjectSwitcherStorybookParams) => { + return ( + + + + ); +}; + +ProjectSwitcher.argTypes = argTypes; diff --git a/packages/serverless/project_switcher/src/switcher.test.tsx b/packages/serverless/project_switcher/src/switcher.test.tsx new file mode 100644 index 0000000000000..5c4fac1d8c161 --- /dev/null +++ b/packages/serverless/project_switcher/src/switcher.test.tsx @@ -0,0 +1,151 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import React from 'react'; +import { render, RenderResult, screen, within, waitFor, cleanup } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; + +import { ProjectType } from '@kbn/serverless-types'; + +import { ProjectSwitcherKibanaProvider, ProjectSwitcherProvider } from './services'; +import { + getProjectSwitcherKibanaDependenciesMock, + getProjectSwitcherServicesMock, +} from '../mocks/jest.mock'; +import { ProjectSwitcher } from './switcher'; +import { + ProjectSwitcher as ProjectSwitcherComponent, + TEST_ID_BUTTON, + TEST_ID_ITEM_GROUP, +} from './switcher.component'; +import { KibanaDependencies, Services } from './types'; + +const renderKibanaProjectSwitcher = ( + currentProjectType: ProjectType = 'observability' +): [RenderResult, jest.Mocked] => { + const mock = getProjectSwitcherKibanaDependenciesMock(); + return [ + render( + + + + ), + mock, + ]; +}; + +const renderProjectSwitcher = ( + currentProjectType: ProjectType = 'observability' +): [RenderResult, jest.Mocked] => { + const mock = getProjectSwitcherServicesMock(); + return [ + render( + + + + ), + mock, + ]; +}; + +describe('ProjectSwitcher', () => { + describe('Component', () => { + test('is rendered', () => { + expect(() => + render( + + ) + ).not.toThrowError(); + }); + }); + + describe('Connected Component', () => { + beforeEach(() => { + jest.spyOn(console, 'error').mockImplementation(() => {}); + }); + + test("doesn't render if the Provider is missing", () => { + expect(() => render()).toThrowError(); + }); + + describe('with Services', () => { + test('is rendered', () => { + renderProjectSwitcher(); + const button = screen.queryByTestId(TEST_ID_BUTTON); + expect(button).not.toBeNull(); + }); + + test('opens', async () => { + renderProjectSwitcher(); + + let group = screen.queryByTestId(TEST_ID_ITEM_GROUP); + expect(group).toBeNull(); + + const button = screen.getByTestId(TEST_ID_BUTTON); + await waitFor(() => userEvent.click(button)); + + group = screen.queryByTestId(TEST_ID_ITEM_GROUP); + expect(group).not.toBeNull(); + }); + + test('calls setProjectType when clicked', async () => { + const [_, mock] = renderProjectSwitcher(); + + const button = screen.getByTestId(TEST_ID_BUTTON); + await waitFor(() => userEvent.click(button)); + + const group = screen.getByTestId(TEST_ID_ITEM_GROUP); + const project = await within(group).findByLabelText('Security'); + await waitFor(() => userEvent.click(project)); + + expect(mock.setProjectType).toHaveBeenCalled(); + }); + }); + }); + + describe('with Kibana Dependencies', () => { + beforeEach(() => { + cleanup(); + }); + + test('is rendered', () => { + renderKibanaProjectSwitcher(); + const button = screen.queryByTestId(TEST_ID_BUTTON); + expect(button).not.toBeNull(); + }); + + test('opens', async () => { + renderKibanaProjectSwitcher(); + + let group = screen.queryByTestId(TEST_ID_ITEM_GROUP); + expect(group).toBeNull(); + + const button = screen.getByTestId(TEST_ID_BUTTON); + userEvent.click(button); + + group = screen.queryByTestId(TEST_ID_ITEM_GROUP); + expect(group).not.toBeNull(); + }); + + test('posts message to change project', async () => { + const [_, mock] = renderKibanaProjectSwitcher(); + + const button = screen.getByTestId(TEST_ID_BUTTON); + await waitFor(() => userEvent.click(button)); + + const group = screen.getByTestId(TEST_ID_ITEM_GROUP); + const project = await within(group).findByLabelText('Security'); + await waitFor(() => userEvent.click(project)); + + expect(mock.coreStart.http.post).toHaveBeenCalled(); + }); + }); +}); diff --git a/packages/serverless/project_switcher/src/switcher.tsx b/packages/serverless/project_switcher/src/switcher.tsx new file mode 100644 index 0000000000000..4fdacf31987b6 --- /dev/null +++ b/packages/serverless/project_switcher/src/switcher.tsx @@ -0,0 +1,21 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import React from 'react'; +import { ProjectType } from '@kbn/serverless-types'; +import { ProjectSwitcher as Component } from './switcher.component'; + +import { useServices } from './services'; +import type { ProjectSwitcherProps } from './types'; + +export const ProjectSwitcher = (props: ProjectSwitcherProps) => { + const { setProjectType } = useServices(); + const onProjectChange = (projectType: ProjectType) => setProjectType(projectType); + + return ; +}; diff --git a/packages/serverless/project_switcher/src/types.ts b/packages/serverless/project_switcher/src/types.ts new file mode 100644 index 0000000000000..5f0b8fcf55c15 --- /dev/null +++ b/packages/serverless/project_switcher/src/types.ts @@ -0,0 +1,39 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { ProjectType } from '@kbn/serverless-types'; + +/** + * A list of services that are consumed by this component. + */ +export interface Services { + setProjectType: (projectType: ProjectType) => void; +} + +/** + * An interface containing a collection of Kibana plugins and services required to + * render this component. + */ +export interface KibanaDependencies { + coreStart: { + http: { + post: (path: string, options: { body: string }) => Promise; + }; + }; + projectChangeAPIUrl: string; +} + +/** + * Props for the `ProjectSwitcher` pure component. + */ +export interface ProjectSwitcherComponentProps { + onProjectChange: (projectType: ProjectType) => void; + currentProjectType: ProjectType; +} + +export type ProjectSwitcherProps = Pick; diff --git a/packages/serverless/project_switcher/tsconfig.json b/packages/serverless/project_switcher/tsconfig.json new file mode 100644 index 0000000000000..8fd6b54236754 --- /dev/null +++ b/packages/serverless/project_switcher/tsconfig.json @@ -0,0 +1,23 @@ +{ + "extends": "../../../tsconfig.base.json", + "compilerOptions": { + "outDir": "target/types", + "types": [ + "jest", + "node", + "react", + "@kbn/ambient-ui-types" + ] + }, + "include": [ + "**/*.ts", + "**/*.tsx", + ], + "exclude": [ + "target/**/*" + ], + "kbn_references": [ + "@kbn/shared-ux-storybook-mock", + "@kbn/serverless-types", + ] +} diff --git a/packages/serverless/storybook/config/README.mdx b/packages/serverless/storybook/config/README.mdx new file mode 100644 index 0000000000000..bba4efa56f3ae --- /dev/null +++ b/packages/serverless/storybook/config/README.mdx @@ -0,0 +1,5 @@ +# Serverless Storybook config + +This directory contains the configuration for the Storybook deployment for all Serverless component packages. + +For more information, refer to the [Storybook documentation](https://storybook.js.org/docs/react/configure/overview) and the `@kbn/storybook` package. diff --git a/packages/serverless/storybook/config/constants.ts b/packages/serverless/storybook/config/constants.ts new file mode 100644 index 0000000000000..be7ae8926447f --- /dev/null +++ b/packages/serverless/storybook/config/constants.ts @@ -0,0 +1,13 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +/** The title of the Storybook. */ +export const TITLE = 'Serverless Storybook'; + +/** The remote URL of the root from which Storybook loads stories for Serverless. */ +export const URL = 'https://github.com/elastic/kibana/tree/main/packages/serverless'; diff --git a/packages/serverless/storybook/config/index.ts b/packages/serverless/storybook/config/index.ts new file mode 100755 index 0000000000000..5a73da614bf27 --- /dev/null +++ b/packages/serverless/storybook/config/index.ts @@ -0,0 +1,9 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +export { TITLE, URL } from './constants'; diff --git a/packages/serverless/storybook/config/kibana.jsonc b/packages/serverless/storybook/config/kibana.jsonc new file mode 100644 index 0000000000000..a141e67afd745 --- /dev/null +++ b/packages/serverless/storybook/config/kibana.jsonc @@ -0,0 +1,6 @@ +{ + "type": "shared-common", + "id": "@kbn/serverless-storybook-config", + "owner": "@elastic/appex-sharedux", + "devOnly": true +} diff --git a/packages/serverless/storybook/config/main.ts b/packages/serverless/storybook/config/main.ts new file mode 100644 index 0000000000000..47a47a5a802b3 --- /dev/null +++ b/packages/serverless/storybook/config/main.ts @@ -0,0 +1,17 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { defaultConfig } from '@kbn/storybook'; + +module.exports = { + ...defaultConfig, + stories: ['../../**/*.stories.+(tsx|mdx)'], + reactOptions: { + strictMode: true, + }, +}; diff --git a/packages/serverless/storybook/config/manager.ts b/packages/serverless/storybook/config/manager.ts new file mode 100644 index 0000000000000..fb973258b9053 --- /dev/null +++ b/packages/serverless/storybook/config/manager.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 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 { addons } from '@storybook/addons'; +import { create } from '@storybook/theming'; +import { PANEL_ID as selectedPanel } from '@storybook/addon-actions'; + +import { TITLE as brandTitle, URL as brandUrl } from './constants'; + +addons.setConfig({ + theme: create({ + base: 'light', + brandTitle, + brandUrl, + }), + selectedPanel, + showPanel: true.valueOf, +}); diff --git a/packages/serverless/storybook/config/package.json b/packages/serverless/storybook/config/package.json new file mode 100644 index 0000000000000..3ff7ce7258729 --- /dev/null +++ b/packages/serverless/storybook/config/package.json @@ -0,0 +1,6 @@ +{ + "name": "@kbn/serverless-storybook-config", + "private": true, + "version": "1.0.0", + "license": "SSPL-1.0 OR Elastic License 2.0" +} \ No newline at end of file diff --git a/packages/serverless/storybook/config/preview.ts b/packages/serverless/storybook/config/preview.ts new file mode 100644 index 0000000000000..ee65b88614fb9 --- /dev/null +++ b/packages/serverless/storybook/config/preview.ts @@ -0,0 +1,22 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 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. + */ + +/* eslint-disable @typescript-eslint/no-namespace,@typescript-eslint/no-empty-interface */ +declare global { + namespace NodeJS { + interface Global {} + interface InspectOptions {} + type ConsoleConstructor = console.ConsoleConstructor; + } +} + +/* eslint-enable */ +import jest from 'jest-mock'; + +/* @ts-expect-error TS doesn't see jest as a property of window, and I don't want to edit our global config. */ +window.jest = jest; diff --git a/packages/serverless/storybook/config/tsconfig.json b/packages/serverless/storybook/config/tsconfig.json new file mode 100644 index 0000000000000..1d676d9c2948d --- /dev/null +++ b/packages/serverless/storybook/config/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "outDir": "target/types", + "types": [ + "jest", + "node" + ] + }, + "include": [ + "**/*.ts", + ], + "exclude": [ + "target/**/*" + ], + "kbn_references": [ + "@kbn/storybook", + ] +} diff --git a/packages/serverless/types/README.mdx b/packages/serverless/types/README.mdx new file mode 100644 index 0000000000000..b12bc55becfc0 --- /dev/null +++ b/packages/serverless/types/README.mdx @@ -0,0 +1,10 @@ +--- +id: serverless/packages/types +slug: /serverless/packages/types +title: Serverless Typescript Types +description: A package of common types for Serverless projects. +tags: ['serverless', 'package'] +date: 2023-04-23 +--- + +This package contains common types for Serverless projects. \ No newline at end of file diff --git a/packages/serverless/types/index.d.ts b/packages/serverless/types/index.d.ts new file mode 100644 index 0000000000000..b384e7a59fbba --- /dev/null +++ b/packages/serverless/types/index.d.ts @@ -0,0 +1,9 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +export type ProjectType = 'observability' | 'security' | 'search'; diff --git a/packages/serverless/types/kibana.jsonc b/packages/serverless/types/kibana.jsonc new file mode 100644 index 0000000000000..0b5a8fffe84be --- /dev/null +++ b/packages/serverless/types/kibana.jsonc @@ -0,0 +1,5 @@ +{ + "type": "shared-common", + "id": "@kbn/serverless-types", + "owner": "@elastic/appex-sharedux" +} diff --git a/packages/serverless/types/package.json b/packages/serverless/types/package.json new file mode 100644 index 0000000000000..c9b7b0810fdfb --- /dev/null +++ b/packages/serverless/types/package.json @@ -0,0 +1,6 @@ +{ + "name": "@kbn/serverless-types", + "private": true, + "version": "1.0.0", + "license": "SSPL-1.0 OR Elastic License 2.0" +} \ No newline at end of file diff --git a/packages/serverless/types/tsconfig.json b/packages/serverless/types/tsconfig.json new file mode 100644 index 0000000000000..6d27b06d5f8ba --- /dev/null +++ b/packages/serverless/types/tsconfig.json @@ -0,0 +1,17 @@ +{ + "extends": "../../../tsconfig.base.json", + "compilerOptions": { + "outDir": "target/types", + "types": [ + "jest", + "node" + ] + }, + "include": [ + "**/*.ts", + ], + "exclude": [ + "target/**/*" + ], + "kbn_references": [] +} diff --git a/src/cli/serve/serve.js b/src/cli/serve/serve.js index 9facf94408235..46f4de867ed58 100644 --- a/src/cli/serve/serve.js +++ b/src/cli/serve/serve.js @@ -8,7 +8,7 @@ import { set as lodashSet } from '@kbn/safer-lodash-set'; import _ from 'lodash'; -import { statSync } from 'fs'; +import { statSync, copyFileSync, existsSync, readFileSync, writeFileSync } from 'fs'; import { resolve } from 'path'; import url from 'url'; @@ -29,7 +29,7 @@ function getServerlessProjectMode(opts) { return null; } - if (VALID_SERVERLESS_PROJECT_MODE.includes(opts.serverless)) { + if (VALID_SERVERLESS_PROJECT_MODE.includes(opts.serverless) || opts.serverless === true) { return opts.serverless; } @@ -115,6 +115,38 @@ function maybeAddConfig(name, configs, method) { } } +/** + * @param {string} file + * @param {'es' | 'security' | 'oblt' | true} mode + * @param {string[]} configs + * @param {'push' | 'unshift'} method + */ +function maybeSetRecentConfig(file, mode, configs, method) { + const path = resolve(getConfigDirectory(), file); + + try { + if (mode === true) { + if (!existsSync(path)) { + const data = readFileSync(path.replace('recent', 'es'), 'utf-8'); + writeFileSync( + path, + `${data}\nxpack.serverless.plugin.developer.projectSwitcher.enabled: true\n` + ); + } + } else { + copyFileSync(path.replace('recent', mode), path); + } + + configs[method](path); + } catch (err) { + if (err.code === 'ENOENT') { + return; + } + + throw err; + } +} + /** * @returns {string[]} */ @@ -255,7 +287,15 @@ export default function (program) { } if (isServerlessCapableDistribution()) { - command.option('--serverless ', 'Start Kibana in a serverless project mode'); + command + .option( + '--serverless', + 'Start Kibana in the most recent serverless project mode, (default is es)' + ) + .option( + '--serverless ', + 'Start Kibana in a specific serverless project mode' + ); } if (DEV_MODE_SUPPORTED) { @@ -285,7 +325,7 @@ export default function (program) { // we "unshift" .serverless. config so that it only overrides defaults if (serverlessMode) { maybeAddConfig(`serverless.yml`, configs, 'push'); - maybeAddConfig(`serverless.${serverlessMode}.yml`, configs, 'unshift'); + maybeSetRecentConfig('serverless.recent.yml', serverlessMode, configs, 'unshift'); } // .dev. configs are "pushed" so that they override all other config files @@ -293,7 +333,7 @@ export default function (program) { maybeAddConfig('kibana.dev.yml', configs, 'push'); if (serverlessMode) { maybeAddConfig(`serverless.dev.yml`, configs, 'push'); - maybeAddConfig(`serverless.${serverlessMode}.dev.yml`, configs, 'push'); + maybeSetRecentConfig('serverless.recent.dev.yml', serverlessMode, configs, 'unshift'); } } diff --git a/src/core/public/styles/rendering/_base.scss b/src/core/public/styles/rendering/_base.scss index 9d4296ca3b4ef..a9ece9955e6ca 100644 --- a/src/core/public/styles/rendering/_base.scss +++ b/src/core/public/styles/rendering/_base.scss @@ -75,6 +75,9 @@ &.kbnBody--chromeHidden { @include kbnAffordForHeader(0); } + &.kbnBody--projectLayout { + @include kbnAffordForHeader($euiHeaderHeightCompensation); + } &.kbnBody--chromeHidden.kbnBody--hasHeaderBanner { @include kbnAffordForHeader($kbnHeaderBannerHeight); } diff --git a/src/dev/storybook/aliases.ts b/src/dev/storybook/aliases.ts index 24dc93c33894e..e050926610259 100644 --- a/src/dev/storybook/aliases.ts +++ b/src/dev/storybook/aliases.ts @@ -46,6 +46,7 @@ export const storybookAliases = { presentation: 'src/plugins/presentation_util/storybook', security_solution: 'x-pack/plugins/security_solution/.storybook', security_solution_packages: 'x-pack/packages/security-solution/storybook/config', + serverless: 'packages/serverless/storybook/config', shared_ux: 'packages/shared-ux/storybook/config', threat_intelligence: 'x-pack/plugins/threat_intelligence/.storybook', triggers_actions_ui: 'x-pack/plugins/triggers_actions_ui/.storybook', diff --git a/test/plugin_functional/test_suites/core_plugins/rendering.ts b/test/plugin_functional/test_suites/core_plugins/rendering.ts index 7d624e7d1c94f..a4591f6f5f47e 100644 --- a/test/plugin_functional/test_suites/core_plugins/rendering.ts +++ b/test/plugin_functional/test_suites/core_plugins/rendering.ts @@ -223,6 +223,7 @@ export default function ({ getService }: PluginFunctionalProviderContext) { 'xpack.security.loginAssistanceMessage (string)', 'xpack.security.sameSiteCookies (alternatives)', 'xpack.security.showInsecureClusterWarning (boolean)', + 'xpack.security.showNavLinks (boolean)', 'xpack.securitySolution.enableExperimental (array)', 'xpack.securitySolution.prebuiltRulesPackageVersion (string)', 'xpack.snapshot_restore.slm_ui.enabled (boolean)', @@ -270,6 +271,7 @@ export default function ({ getService }: PluginFunctionalProviderContext) { 'xpack.security.loginAssistanceMessage (string)', 'xpack.security.sameSiteCookies (alternatives)', 'xpack.security.showInsecureClusterWarning (boolean)', + 'xpack.security.showNavLinks (boolean)', ]; // We don't assert that actualExposedConfigKeys and expectedExposedConfigKeys are equal, because test failure messages with large // arrays are hard to grok. Instead, we take the difference between the two arrays and assert them separately, that way it's diff --git a/tsconfig.base.json b/tsconfig.base.json index a5de10e9f4fe3..f333d6f58b34f 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -1138,6 +1138,14 @@ "@kbn/server-http-tools/*": ["packages/kbn-server-http-tools/*"], "@kbn/server-route-repository": ["packages/kbn-server-route-repository"], "@kbn/server-route-repository/*": ["packages/kbn-server-route-repository/*"], + "@kbn/serverless": ["x-pack/plugins/serverless"], + "@kbn/serverless/*": ["x-pack/plugins/serverless/*"], + "@kbn/serverless-project-switcher": ["packages/serverless/project_switcher"], + "@kbn/serverless-project-switcher/*": ["packages/serverless/project_switcher/*"], + "@kbn/serverless-storybook-config": ["packages/serverless/storybook/config"], + "@kbn/serverless-storybook-config/*": ["packages/serverless/storybook/config/*"], + "@kbn/serverless-types": ["packages/serverless/types"], + "@kbn/serverless-types/*": ["packages/serverless/types/*"], "@kbn/session-notifications-plugin": ["test/plugin_functional/plugins/session_notifications"], "@kbn/session-notifications-plugin/*": ["test/plugin_functional/plugins/session_notifications/*"], "@kbn/session-view-plugin": ["x-pack/plugins/session_view"], diff --git a/x-pack/.i18nrc.json b/x-pack/.i18nrc.json index 8071085e9cc8b..8e85011f366d8 100644 --- a/x-pack/.i18nrc.json +++ b/x-pack/.i18nrc.json @@ -62,6 +62,7 @@ "xpack.searchProfiler": "plugins/searchprofiler", "xpack.security": "plugins/security", "xpack.server": "legacy/server", + "xpack.serverless": "plugins/serverless", "xpack.securitySolution": "plugins/security_solution", "xpack.sessionView": "plugins/session_view", "xpack.snapshotRestore": "plugins/snapshot_restore", diff --git a/x-pack/plugins/security/public/config.ts b/x-pack/plugins/security/public/config.ts index 440bd8da27d90..6a5a8dac01500 100644 --- a/x-pack/plugins/security/public/config.ts +++ b/x-pack/plugins/security/public/config.ts @@ -9,4 +9,5 @@ export interface ConfigType { loginAssistanceMessage: string; showInsecureClusterWarning: boolean; sameSiteCookies: 'Strict' | 'Lax' | 'None' | undefined; + showNavLinks: boolean; } diff --git a/x-pack/plugins/security/public/nav_control/nav_control_service.tsx b/x-pack/plugins/security/public/nav_control/nav_control_service.tsx index 91d0c33ade107..e1af50e986450 100644 --- a/x-pack/plugins/security/public/nav_control/nav_control_service.tsx +++ b/x-pack/plugins/security/public/nav_control/nav_control_service.tsx @@ -29,6 +29,7 @@ interface SetupDeps { securityLicense: SecurityLicense; logoutUrl: string; securityApiClients: SecurityApiClients; + showNavLinks?: boolean; } interface StartDeps { @@ -54,16 +55,18 @@ export class SecurityNavControlService { private securityApiClients!: SecurityApiClients; private navControlRegistered!: boolean; + private showNavLinks!: boolean; private securityFeaturesSubscription?: Subscription; private readonly stop$ = new ReplaySubject(1); private userMenuLinks$ = new BehaviorSubject([]); - public setup({ securityLicense, logoutUrl, securityApiClients }: SetupDeps) { + public setup({ securityLicense, logoutUrl, securityApiClients, showNavLinks = true }: SetupDeps) { this.securityLicense = securityLicense; this.logoutUrl = logoutUrl; this.securityApiClients = securityApiClients; + this.showNavLinks = showNavLinks; } public start({ core, authc }: StartDeps): SecurityNavControlServiceStart { @@ -72,7 +75,7 @@ export class SecurityNavControlService { const isAnonymousPath = core.http.anonymousPaths.isAnonymous(window.location.pathname); const shouldRegisterNavControl = - !isAnonymousPath && showLinks && !this.navControlRegistered; + this.showNavLinks && !isAnonymousPath && showLinks && !this.navControlRegistered; if (shouldRegisterNavControl) { this.registerSecurityNavControl(core, authc); } diff --git a/x-pack/plugins/security/public/plugin.tsx b/x-pack/plugins/security/public/plugin.tsx index c56c40f63b4d0..084a34e635dcf 100644 --- a/x-pack/plugins/security/public/plugin.tsx +++ b/x-pack/plugins/security/public/plugin.tsx @@ -107,6 +107,7 @@ export class SecurityPlugin securityLicense: license, logoutUrl: getLogoutUrl(core.http), securityApiClients: this.securityApiClients, + showNavLinks: this.config.showNavLinks, }); this.analyticsService.setup({ diff --git a/x-pack/plugins/security/server/config.test.ts b/x-pack/plugins/security/server/config.test.ts index 8b7324e70d646..ea255d61ee255 100644 --- a/x-pack/plugins/security/server/config.test.ts +++ b/x-pack/plugins/security/server/config.test.ts @@ -60,6 +60,7 @@ describe('config schema', () => { "selector": Object {}, }, "cookieName": "sid", + "enabled": true, "encryptionKey": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", "loginAssistanceMessage": "", "public": Object {}, @@ -70,6 +71,7 @@ describe('config schema', () => { "lifespan": "P30D", }, "showInsecureClusterWarning": true, + "showNavLinks": true, } `); @@ -113,6 +115,7 @@ describe('config schema', () => { "selector": Object {}, }, "cookieName": "sid", + "enabled": true, "encryptionKey": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", "loginAssistanceMessage": "", "public": Object {}, @@ -123,6 +126,7 @@ describe('config schema', () => { "lifespan": "P30D", }, "showInsecureClusterWarning": true, + "showNavLinks": true, } `); @@ -166,6 +170,7 @@ describe('config schema', () => { "selector": Object {}, }, "cookieName": "sid", + "enabled": true, "loginAssistanceMessage": "", "public": Object {}, "secureCookies": false, @@ -175,6 +180,7 @@ describe('config schema', () => { "lifespan": "P30D", }, "showInsecureClusterWarning": true, + "showNavLinks": true, } `); }); diff --git a/x-pack/plugins/security/server/config.ts b/x-pack/plugins/security/server/config.ts index e3584427964f3..91abf77a376f8 100644 --- a/x-pack/plugins/security/server/config.ts +++ b/x-pack/plugins/security/server/config.ts @@ -204,6 +204,7 @@ export const ConfigSchema = schema.object({ loginAssistanceMessage: schema.string({ defaultValue: '' }), showInsecureClusterWarning: schema.boolean({ defaultValue: true }), loginHelp: schema.maybe(schema.string()), + showNavLinks: schema.boolean({ defaultValue: true }), cookieName: schema.string({ defaultValue: 'sid' }), encryptionKey: schema.conditional( schema.contextRef('dist'), @@ -295,6 +296,7 @@ export const ConfigSchema = schema.object({ ) ), }), + enabled: schema.boolean({ defaultValue: true }), }); export function createConfig( diff --git a/x-pack/plugins/security/server/index.ts b/x-pack/plugins/security/server/index.ts index 89ddb41375a91..06ba1e77118e9 100644 --- a/x-pack/plugins/security/server/index.ts +++ b/x-pack/plugins/security/server/index.ts @@ -52,6 +52,7 @@ export const config: PluginConfigDescriptor> = { loginAssistanceMessage: true, showInsecureClusterWarning: true, sameSiteCookies: true, + showNavLinks: true, }, }; export const plugin: PluginInitializer< diff --git a/x-pack/plugins/serverless/README.mdx b/x-pack/plugins/serverless/README.mdx new file mode 100755 index 0000000000000..f3b9940e4b371 --- /dev/null +++ b/x-pack/plugins/serverless/README.mdx @@ -0,0 +1,22 @@ +--- +id: serverless/plugin +slug: /serverless/plugin +title: Serverless Plugin +description: The plugin responsible for managing Serverless settings and providing services to all product serverless plugins. +tags: ['serverless', 'plugin'] +date: 2023-04-23 +--- + +![diagram](./assets/diagram.png) + +a. `serverless.yml` config enables Serverless plugin, provides settings for *all* projects, (e.g. disabling Reporting). + +b. Product-specific `yml` file enables corresponding Project plugin, provides settings for a specific project, (e.g. disabling Observability). + +c. Project plugin interacts with Serverless plugin to customize Serverless Kibana. + +d. Serverless plugin interacts with Kibana Core to customize Classic Kibana. + +e. Project plugin interacts with corresponding Solution plugin to customize the Solution experience for Serverless. + +Communication occurs in a *single direction*. While it would be tempting to add a global flag to check if Serverless is enabled, doing so short-circuits the "affecting" model. \ No newline at end of file diff --git a/x-pack/plugins/serverless/assets/diagram.png b/x-pack/plugins/serverless/assets/diagram.png new file mode 100644 index 0000000000000000000000000000000000000000..57dda7513bb4bde8317575a5237bb98aef9d0d70 GIT binary patch literal 438787 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