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/.github/CODEOWNERS b/.github/CODEOWNERS index f9586da37a0bf..d76ed31b2b2b1 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/config/serverless.yml b/config/serverless.yml index 772508c4b4bbf..ec24139422975 100644 --- a/config/serverless.yml +++ b/config/serverless.yml @@ -3,3 +3,17 @@ xpack.security.showNavLinks: false xpack.serverless.plugin.enabled: true 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/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", } 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, 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', + } +); 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/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/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/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. 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/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)`; } 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/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() 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); + }); + }, }; }, }; 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/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 ( { + 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..848b35b8bb293 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 @@ -5,19 +5,26 @@ * 2.0. */ -import { EuiButton, EuiContextMenuItem, EuiContextMenuPanel, EuiPopover } from '@elastic/eui'; import React, { useState } from 'react'; +import { useIsMutating } from '@tanstack/react-query'; import { i18n } from '@kbn/i18n'; +import { EuiButton, EuiContextMenuItem, EuiContextMenuPanel, EuiPopover } from '@elastic/eui'; 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 { useCloneSlo } from '../../../hooks/slo/use_clone_slo'; +import { + transformSloResponseToCreateSloInput, + transformValuesToCreateSLOInput, +} from '../../slo_edit/helpers/process_slo_form_values'; +import { SloDeleteConfirmationModal } from '../../slos/components/slo_delete_confirmation_modal'; +import type { RulesParams } from '../../../locators/rules'; export interface Props { slo: SLOWithSummaryResponse | undefined; @@ -28,11 +35,20 @@ export function HeaderControl({ isLoading, slo }: Props) { const { application: { navigateToUrl }, http: { basePath }, + notifications: { toasts }, + share: { + url: { locators }, + }, triggersActionsUi: { getAddRuleFlyout: AddRuleFlyout }, - } = useKibana().services; + } = useKibana().services; const { hasWriteCapabilities } = useCapabilities(); + const [isPopoverOpen, setIsPopoverOpen] = useState(false); const [isRuleFlyoutVisible, setRuleFlyoutVisibility] = useState(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); @@ -52,12 +68,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 = () => { @@ -85,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', @@ -108,11 +165,12 @@ export function HeaderControl({ isLoading, slo }: Props) { closePopover={closePopover} > @@ -123,6 +181,7 @@ export function HeaderControl({ isLoading, slo }: Props) { @@ -136,6 +195,7 @@ export function HeaderControl({ isLoading, slo }: Props) { @@ -143,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', + })} + + )} /> @@ -173,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 4028b80203591..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 @@ -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,19 @@ const mockKibana = () => { prepend: mockBasePathPrepend, }, }, + notifications: { + toasts: { + addSuccess: jest.fn(), + addError: jest.fn(), + }, + }, + share: { + url: { + locators: { + get: mockLocator, + }, + }, + }, triggersActionsUi: { getAddRuleFlyout: jest.fn(() => (
mocked component
@@ -182,6 +196,49 @@ 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')); + 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(); + + 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/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 049481486eb9e..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 @@ -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, + } ); }; @@ -175,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', })} ,
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/pages/slos/slos.tsx b/x-pack/plugins/observability/public/pages/slos/slos.tsx index b81998d8452de..f9cf30cb7b344 100644 --- a/x-pack/plugins/observability/public/pages/slos/slos.tsx +++ b/x-pack/plugins/observability/public/pages/slos/slos.tsx @@ -18,10 +18,10 @@ import { useFetchSloList } from '../../hooks/slo/use_fetch_slo_list'; import { SloList } from './components/slo_list'; import { SloListWelcomePrompt } from './components/slo_list_welcome_prompt'; import { AutoRefreshButton } from './components/auto_refresh_button'; -import { paths } from '../../config/paths'; -import type { ObservabilityAppServices } from '../../application/types'; import { HeaderTitle } from './components/header_title'; import { FeedbackButton } from '../../components/slo/feedback_button/feedback_button'; +import { paths } from '../../config/paths'; +import type { ObservabilityAppServices } from '../../application/types'; export function SlosPage() { const { 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, }; } 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/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; }; 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/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/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 }), + }), +}; 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', ], }, 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 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']);