diff --git a/src/plugins/discover/common/config.ts b/src/plugins/discover/common/config.ts new file mode 100644 index 0000000000000..f3afc0ebf08be --- /dev/null +++ b/src/plugins/discover/common/config.ts @@ -0,0 +1,21 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { schema, TypeOf } from '@kbn/config-schema'; + +export const configSchema = schema.object({ + enableUiSettingsValidations: schema.boolean({ defaultValue: false }), + experimental: schema.maybe( + schema.object({ + ruleFormV2Enabled: schema.maybe(schema.boolean({ defaultValue: false })), + }) + ), +}); + +export type ConfigSchema = TypeOf; +export type ExperimentalFeatures = ConfigSchema['experimental']; diff --git a/src/plugins/discover/public/application/discover_router.test.tsx b/src/plugins/discover/public/application/discover_router.test.tsx index 479abb5511729..5fb68bb9f2e99 100644 --- a/src/plugins/discover/public/application/discover_router.test.tsx +++ b/src/plugins/discover/public/application/discover_router.test.tsx @@ -49,9 +49,12 @@ const gatherRoutes = (wrapper: ShallowWrapper) => { }); }; +const mockExperimentalFeatures = {}; + const props: DiscoverRoutesProps = { customizationCallbacks: [], customizationContext: mockCustomizationContext, + experimentalFeatures: mockExperimentalFeatures, }; describe('DiscoverRoutes', () => { @@ -156,6 +159,7 @@ describe('CustomDiscoverRoutes', () => { ); expect(component.find(DiscoverRoutes).getElement()).toMatchObject( @@ -163,6 +167,7 @@ describe('CustomDiscoverRoutes', () => { prefix={addProfile('', mockProfile)} customizationCallbacks={callbacks} customizationContext={mockCustomizationContext} + experimentalFeatures={mockExperimentalFeatures} /> ); }); @@ -173,6 +178,7 @@ describe('CustomDiscoverRoutes', () => { ); expect(component.find(NotFoundRoute).getElement()).toMatchObject(); @@ -191,6 +197,7 @@ describe('DiscoverRouter', () => { history={history} profileRegistry={profileRegistry} customizationContext={mockCustomizationContext} + experimentalFeatures={mockExperimentalFeatures} /> ); gatherRoutes(component); @@ -201,6 +208,7 @@ describe('DiscoverRouter', () => { ); }); @@ -210,6 +218,7 @@ describe('DiscoverRouter', () => { ); }); diff --git a/src/plugins/discover/public/application/discover_router.tsx b/src/plugins/discover/public/application/discover_router.tsx index a9d252127013c..d7aff97483665 100644 --- a/src/plugins/discover/public/application/discover_router.tsx +++ b/src/plugins/discover/public/application/discover_router.tsx @@ -12,6 +12,7 @@ import React, { useCallback, useMemo } from 'react'; import { History } from 'history'; import { EuiErrorBoundary } from '@elastic/eui'; import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public'; +import { ExperimentalFeatures } from '../../common/config'; import { ContextAppRoute } from './context'; import { SingleDocRoute } from './doc'; import { DiscoverMainRoute } from './main'; @@ -26,6 +27,7 @@ export interface DiscoverRoutesProps { prefix?: string; customizationCallbacks: CustomizationCallback[]; customizationContext: DiscoverCustomizationContext; + experimentalFeatures: ExperimentalFeatures; } export const DiscoverRoutes = ({ prefix, ...mainRouteProps }: DiscoverRoutesProps) => { @@ -67,6 +69,7 @@ export const DiscoverRoutes = ({ prefix, ...mainRouteProps }: DiscoverRoutesProp interface CustomDiscoverRoutesProps { profileRegistry: DiscoverProfileRegistry; customizationContext: DiscoverCustomizationContext; + experimentalFeatures: ExperimentalFeatures; } export const CustomDiscoverRoutes = ({ profileRegistry, ...props }: CustomDiscoverRoutesProps) => { @@ -93,6 +96,7 @@ export interface DiscoverRouterProps { services: DiscoverServices; profileRegistry: DiscoverProfileRegistry; customizationContext: DiscoverCustomizationContext; + experimentalFeatures: ExperimentalFeatures; history: History; } diff --git a/src/plugins/discover/public/application/index.tsx b/src/plugins/discover/public/application/index.tsx index 89dd06d2104d8..97ae7c62aa953 100644 --- a/src/plugins/discover/public/application/index.tsx +++ b/src/plugins/discover/public/application/index.tsx @@ -9,6 +9,7 @@ import React from 'react'; import { i18n } from '@kbn/i18n'; import { toMountPoint } from '@kbn/react-kibana-mount'; +import { ExperimentalFeatures } from '../../common/config'; import { DiscoverRouter } from './discover_router'; import { DiscoverServices } from '../build_services'; import type { DiscoverProfileRegistry } from '../customizations/profile_registry'; @@ -19,6 +20,7 @@ export interface RenderAppProps { services: DiscoverServices; profileRegistry: DiscoverProfileRegistry; customizationContext: DiscoverCustomizationContext; + experimentalFeatures: ExperimentalFeatures; } export const renderApp = ({ @@ -26,6 +28,7 @@ export const renderApp = ({ services, profileRegistry, customizationContext, + experimentalFeatures, }: RenderAppProps) => { const { history, capabilities, chrome, data, core } = services; @@ -45,6 +48,7 @@ export const renderApp = ({ services={services} profileRegistry={profileRegistry} customizationContext={customizationContext} + experimentalFeatures={experimentalFeatures} history={history} />, { diff --git a/src/plugins/discover/public/plugin.tsx b/src/plugins/discover/public/plugin.tsx index a862c78446375..f1f7dee81abd5 100644 --- a/src/plugins/discover/public/plugin.tsx +++ b/src/plugins/discover/public/plugin.tsx @@ -78,6 +78,7 @@ import { } from './components/discover_container'; import { getESQLSearchProvider } from './global_search/search_provider'; import { HistoryService } from './history_service'; +import { ConfigSchema, ExperimentalFeatures } from '../common/config'; /** * @public @@ -207,7 +208,10 @@ export interface DiscoverStartPlugins { export class DiscoverPlugin implements Plugin { - constructor(private readonly initializerContext: PluginInitializerContext) {} + constructor(private readonly initializerContext: PluginInitializerContext) { + this.experimentalFeatures = + initializerContext.config.get().experimental ?? this.experimentalFeatures; + } private appStateUpdater = new BehaviorSubject(() => ({})); private historyService = new HistoryService(); @@ -222,6 +226,9 @@ export class DiscoverPlugin enabled: false, showLogsExplorerTabs: false, }; + private experimentalFeatures: ExperimentalFeatures = { + ruleFormV2Enabled: false, + }; setup( core: CoreSetup, @@ -355,6 +362,7 @@ export class DiscoverPlugin displayMode: 'standalone', inlineTopNav: this.inlineTopNav, }, + experimentalFeatures: this.experimentalFeatures, }); return () => { diff --git a/src/plugins/discover/server/config.ts b/src/plugins/discover/server/config.ts index 5813ce2d50498..1203502538a6c 100644 --- a/src/plugins/discover/server/config.ts +++ b/src/plugins/discover/server/config.ts @@ -6,15 +6,12 @@ * Side Public License, v 1. */ -import { schema, TypeOf } from '@kbn/config-schema'; import { PluginConfigDescriptor } from '@kbn/core-plugins-server'; - -const configSchema = schema.object({ - enableUiSettingsValidations: schema.boolean({ defaultValue: false }), -}); - -export type ConfigSchema = TypeOf; +import { configSchema, type ConfigSchema } from '../common/config'; export const config: PluginConfigDescriptor = { schema: configSchema, + exposeToBrowser: { + experimental: true, + }, }; diff --git a/src/plugins/discover/server/plugin.ts b/src/plugins/discover/server/plugin.ts index 19800cdc0dbb2..0ffb4bb303a9a 100644 --- a/src/plugins/discover/server/plugin.ts +++ b/src/plugins/discover/server/plugin.ts @@ -20,7 +20,7 @@ import { createSearchEmbeddableFactory } from './embeddable'; import { initializeLocatorServices } from './locator'; import { registerSampleData } from './sample_data'; import { getUiSettings } from './ui_settings'; -import { ConfigSchema } from './config'; +import { ConfigSchema } from '../common/config'; export class DiscoverServerPlugin implements Plugin diff --git a/test/plugin_functional/test_suites/core_plugins/rendering.ts b/test/plugin_functional/test_suites/core_plugins/rendering.ts index 5b06a48809c4c..a31f6260bc9e0 100644 --- a/test/plugin_functional/test_suites/core_plugins/rendering.ts +++ b/test/plugin_functional/test_suites/core_plugins/rendering.ts @@ -340,6 +340,18 @@ export default function ({ getService }: PluginFunctionalProviderContext) { 'xpack.observability_onboarding.ui.enabled (boolean)', 'xpack.observabilityLogsExplorer.navigation.showAppLink (any)', // conditional, is actually a boolean 'share.new_version.enabled (boolean)', + /** + * Rule form V2 feature flags + */ + 'discover.experimental.ruleFormV2Enabled (boolean)', + 'xpack.infra.featureFlags.ruleFormV2Enabled (boolean)', + 'xpack.legacy_uptime.experimental.ruleFormV2Enabled (boolean)', + 'xpack.ml.experimental.ruleFormV2.enabled (boolean)', + 'xpack.transform.experimental.ruleFormV2Enabled (boolean)', + 'xpack.apm.featureFlags.ruleFormV2Enabled (boolean)', + 'xpack.observability.unsafe.ruleFormV2.enabled (boolean)', + 'xpack.slo.experimental.ruleFormV2.enabled (boolean)', + /**/ ]; // We don't assert that actualExposedConfigKeys and expectedExposedConfigKeys are equal, because test failure messages with large // arrays are hard to grok. Instead, we take the difference between the two arrays and assert them separately, that way it's diff --git a/x-pack/plugins/ml/common/constants/app.ts b/x-pack/plugins/ml/common/constants/app.ts index aff8a08550879..d74e1cb30c761 100644 --- a/x-pack/plugins/ml/common/constants/app.ts +++ b/x-pack/plugins/ml/common/constants/app.ts @@ -19,12 +19,16 @@ export const ML_EXTERNAL_BASE_PATH = '/api/ml'; export type MlFeatures = Record<'ad' | 'dfa' | 'nlp', boolean>; export type CompatibleModule = 'security' | 'observability' | 'search'; +export type ExperimentalFeatures = Record<'ruleFormV2', boolean>; export interface ConfigSchema { ad?: { enabled: boolean }; dfa?: { enabled: boolean }; nlp?: { enabled: boolean }; compatibleModuleType?: CompatibleModule; + experimental?: { + ruleFormV2?: { enabled: boolean }; + }; } export function initEnabledFeatures(enabledFeatures: MlFeatures, config: ConfigSchema) { @@ -38,3 +42,12 @@ export function initEnabledFeatures(enabledFeatures: MlFeatures, config: ConfigS enabledFeatures.nlp = config.nlp.enabled; } } + +export function initExperimentalFeatures( + experimentalFeatures: ExperimentalFeatures, + config: ConfigSchema +) { + if (config.experimental?.ruleFormV2?.enabled !== undefined) { + experimentalFeatures.ruleFormV2 = config.experimental.ruleFormV2.enabled; + } +} diff --git a/x-pack/plugins/ml/public/application/app.tsx b/x-pack/plugins/ml/public/application/app.tsx index 59871c8b2aa83..6ac6b1363565a 100644 --- a/x-pack/plugins/ml/public/application/app.tsx +++ b/x-pack/plugins/ml/public/application/app.tsx @@ -20,7 +20,7 @@ import { KibanaContextProvider, KibanaThemeProvider } from '@kbn/kibana-react-pl import { StorageContextProvider } from '@kbn/ml-local-storage'; import useLifecycles from 'react-use/lib/useLifecycles'; import useObservable from 'react-use/lib/useObservable'; -import type { MlFeatures } from '../../common/constants/app'; +import type { ExperimentalFeatures, MlFeatures } from '../../common/constants/app'; import { MlLicense } from '../../common/license'; import { MlCapabilitiesService } from './capabilities/check_capabilities'; import { ML_STORAGE_KEYS } from '../../common/types/storage'; @@ -49,6 +49,7 @@ interface AppProps { appMountParams: AppMountParameters; isServerless: boolean; mlFeatures: MlFeatures; + experimentalFeatures: ExperimentalFeatures; } const localStorage = new Storage(window.localStorage); @@ -91,7 +92,14 @@ export interface MlServicesContext { export type MlGlobalServices = ReturnType; -const App: FC = ({ coreStart, deps, appMountParams, isServerless, mlFeatures }) => { +const App: FC = ({ + coreStart, + deps, + appMountParams, + isServerless, + mlFeatures, + experimentalFeatures, +}) => { const pageDeps: PageDependencies = { history: appMountParams.history, setHeaderActionMenu: appMountParams.setHeaderActionMenu, @@ -171,6 +179,7 @@ const App: FC = ({ coreStart, deps, appMountParams, isServerless, mlFe isServerless={isServerless} mlFeatures={mlFeatures} showMLNavMenu={chromeStyle === 'classic'} + experimentalFeatures={experimentalFeatures} > @@ -188,7 +197,8 @@ export const renderApp = ( deps: MlDependencies, appMountParams: AppMountParameters, isServerless: boolean, - mlFeatures: MlFeatures + mlFeatures: MlFeatures, + experimentalFeatures: ExperimentalFeatures ) => { setDependencyCache({ timefilter: deps.data.query.timefilter, @@ -211,6 +221,7 @@ export const renderApp = ( appMountParams={appMountParams} isServerless={isServerless} mlFeatures={mlFeatures} + experimentalFeatures={experimentalFeatures} />, appMountParams.element ); diff --git a/x-pack/plugins/ml/public/application/contexts/ml/serverless_context.tsx b/x-pack/plugins/ml/public/application/contexts/ml/serverless_context.tsx index 9d981bd004ca5..0fd5d8aee3c23 100644 --- a/x-pack/plugins/ml/public/application/contexts/ml/serverless_context.tsx +++ b/x-pack/plugins/ml/public/application/contexts/ml/serverless_context.tsx @@ -7,7 +7,7 @@ import type { FC } from 'react'; import React, { createContext, useContext, useMemo } from 'react'; -import type { MlFeatures } from '../../../../common/constants/app'; +import type { ExperimentalFeatures, MlFeatures } from '../../../../common/constants/app'; export interface EnabledFeatures { showNodeInfo: boolean; @@ -16,6 +16,7 @@ export interface EnabledFeatures { isADEnabled: boolean; isDFAEnabled: boolean; isNLPEnabled: boolean; + showRuleFormV2: boolean; } export const EnabledFeaturesContext = createContext({ showNodeInfo: true, @@ -30,6 +31,7 @@ interface Props { isServerless: boolean; mlFeatures: MlFeatures; showMLNavMenu?: boolean; + experimentalFeatures?: ExperimentalFeatures; } export const EnabledFeaturesContextProvider: FC = ({ @@ -37,6 +39,7 @@ export const EnabledFeaturesContextProvider: FC = ({ isServerless, showMLNavMenu = true, mlFeatures, + experimentalFeatures, }) => { const features: EnabledFeatures = { showNodeInfo: !isServerless, @@ -45,6 +48,7 @@ export const EnabledFeaturesContextProvider: FC = ({ isADEnabled: mlFeatures.ad, isDFAEnabled: mlFeatures.dfa, isNLPEnabled: mlFeatures.nlp, + showRuleFormV2: experimentalFeatures?.ruleFormV2 ?? false, }; return ( diff --git a/x-pack/plugins/ml/public/plugin.ts b/x-pack/plugins/ml/public/plugin.ts index 8b8e617a3ebc1..be063f6a91e52 100644 --- a/x-pack/plugins/ml/public/plugin.ts +++ b/x-pack/plugins/ml/public/plugin.ts @@ -65,6 +65,8 @@ import { PLUGIN_ICON_SOLUTION, PLUGIN_ID, type ConfigSchema, + type ExperimentalFeatures, + initExperimentalFeatures, } from '../common/constants/app'; import type { MlCapabilities } from './shared'; import type { ElasticModels } from './application/services/elastic_models_service'; @@ -127,10 +129,14 @@ export class MlPlugin implements Plugin { dfa: true, nlp: true, }; + private experimentalFeatures: ExperimentalFeatures = { + ruleFormV2: false, + }; constructor(private initializerContext: PluginInitializerContext) { this.isServerless = initializerContext.env.packageInfo.buildFlavor === 'serverless'; initEnabledFeatures(this.enabledFeatures, initializerContext.config.get()); + initExperimentalFeatures(this.experimentalFeatures, initializerContext.config.get()); } setup( @@ -183,7 +189,8 @@ export class MlPlugin implements Plugin { }, params, this.isServerless, - this.enabledFeatures + this.enabledFeatures, + this.experimentalFeatures ); }, }); diff --git a/x-pack/plugins/ml/server/config_schema.ts b/x-pack/plugins/ml/server/config_schema.ts index 928840cdca345..9d5e560443790 100644 --- a/x-pack/plugins/ml/server/config_schema.ts +++ b/x-pack/plugins/ml/server/config_schema.ts @@ -25,4 +25,9 @@ export const configSchema = schema.object({ dfa: enabledSchema, nlp: enabledSchema, compatibleModuleType: compatibleModuleTypeSchema, + experimental: schema.maybe( + schema.object({ + ruleFormV2: enabledSchema, + }) + ), }); diff --git a/x-pack/plugins/ml/server/index.ts b/x-pack/plugins/ml/server/index.ts index ea5a603741857..2ea7f1ded668b 100644 --- a/x-pack/plugins/ml/server/index.ts +++ b/x-pack/plugins/ml/server/index.ts @@ -34,6 +34,7 @@ export const config: PluginConfigDescriptor = { ad: true, dfa: true, nlp: true, + experimental: true, }, }; diff --git a/x-pack/plugins/observability_solution/apm/common/apm_feature_flags.ts b/x-pack/plugins/observability_solution/apm/common/apm_feature_flags.ts index 09428865474fe..3b9da8cdebd92 100644 --- a/x-pack/plugins/observability_solution/apm/common/apm_feature_flags.ts +++ b/x-pack/plugins/observability_solution/apm/common/apm_feature_flags.ts @@ -17,6 +17,7 @@ export enum ApmFeatureFlagName { SourcemapApiAvailable = 'sourcemapApiAvailable', StorageExplorerAvailable = 'storageExplorerAvailable', ProfilingIntegrationAvailable = 'profilingIntegrationAvailable', + RuleFormV2Enabled = 'ruleFormV2Enabled', } const apmFeatureFlagMap = { @@ -52,6 +53,10 @@ const apmFeatureFlagMap = { default: false, type: t.boolean, }, + [ApmFeatureFlagName.RuleFormV2Enabled]: { + default: false, + type: t.boolean, + }, }; type ApmFeatureFlagMap = typeof apmFeatureFlagMap; diff --git a/x-pack/plugins/observability_solution/apm/public/context/apm_plugin/mock_apm_plugin_context.tsx b/x-pack/plugins/observability_solution/apm/public/context/apm_plugin/mock_apm_plugin_context.tsx index 05033575445a9..e026dc210df25 100644 --- a/x-pack/plugins/observability_solution/apm/public/context/apm_plugin/mock_apm_plugin_context.tsx +++ b/x-pack/plugins/observability_solution/apm/public/context/apm_plugin/mock_apm_plugin_context.tsx @@ -86,6 +86,7 @@ const mockConfig: ConfigSchema = { sourcemapApiAvailable: true, storageExplorerAvailable: true, profilingIntegrationAvailable: false, + ruleFormV2Enabled: false, }, serverless: { enabled: false }, }; diff --git a/x-pack/plugins/observability_solution/apm/public/index.ts b/x-pack/plugins/observability_solution/apm/public/index.ts index e893adca92017..5d25f64a0d6e4 100644 --- a/x-pack/plugins/observability_solution/apm/public/index.ts +++ b/x-pack/plugins/observability_solution/apm/public/index.ts @@ -25,6 +25,7 @@ export interface ConfigSchema { sourcemapApiAvailable: boolean; storageExplorerAvailable: boolean; profilingIntegrationAvailable: boolean; + ruleFormV2Enabled: boolean; }; serverless: { enabled: boolean; diff --git a/x-pack/plugins/observability_solution/apm/server/index.ts b/x-pack/plugins/observability_solution/apm/server/index.ts index c5a6f90e5258a..5ce76520e3589 100644 --- a/x-pack/plugins/observability_solution/apm/server/index.ts +++ b/x-pack/plugins/observability_solution/apm/server/index.ts @@ -78,6 +78,7 @@ const configSchema = schema.object({ * enabling this feature flag. */ profilingIntegrationAvailable: schema.boolean({ defaultValue: false }), + ruleFormV2Enabled: schema.boolean({ defaultValue: false }), }), serverless: schema.object({ enabled: offeringBasedSchema({ diff --git a/x-pack/plugins/observability_solution/infra/common/plugin_config_types.ts b/x-pack/plugins/observability_solution/infra/common/plugin_config_types.ts index b69afe176cab8..efc338e93cc9e 100644 --- a/x-pack/plugins/observability_solution/infra/common/plugin_config_types.ts +++ b/x-pack/plugins/observability_solution/infra/common/plugin_config_types.ts @@ -35,6 +35,7 @@ export interface InfraConfig { logThresholdAlertRuleEnabled: boolean; alertsAndRulesDropdownEnabled: boolean; profilingEnabled: boolean; + ruleFormV2Enabled: boolean; }; } diff --git a/x-pack/plugins/observability_solution/infra/public/components/asset_details/__stories__/decorator.tsx b/x-pack/plugins/observability_solution/infra/public/components/asset_details/__stories__/decorator.tsx index 58eb37f58de71..59e67d3f669b1 100644 --- a/x-pack/plugins/observability_solution/infra/public/components/asset_details/__stories__/decorator.tsx +++ b/x-pack/plugins/observability_solution/infra/public/components/asset_details/__stories__/decorator.tsx @@ -188,6 +188,7 @@ export const DecorateWithKibanaContext: DecoratorFn = (story) => { logThresholdAlertRuleEnabled: true, alertsAndRulesDropdownEnabled: true, profilingEnabled: false, + ruleFormV2Enabled: false, }, }; diff --git a/x-pack/plugins/observability_solution/infra/public/containers/plugin_config_context.test.tsx b/x-pack/plugins/observability_solution/infra/public/containers/plugin_config_context.test.tsx index 1b9980bf5f173..8b5bcc8de861c 100644 --- a/x-pack/plugins/observability_solution/infra/public/containers/plugin_config_context.test.tsx +++ b/x-pack/plugins/observability_solution/infra/public/containers/plugin_config_context.test.tsx @@ -29,6 +29,7 @@ describe('usePluginConfig()', () => { logThresholdAlertRuleEnabled: true, alertsAndRulesDropdownEnabled: true, profilingEnabled: false, + ruleFormV2Enabled: false, }, }; const { result } = renderHook(() => usePluginConfig(), { diff --git a/x-pack/plugins/observability_solution/infra/server/lib/alerting/metric_threshold/metric_threshold_executor.test.ts b/x-pack/plugins/observability_solution/infra/server/lib/alerting/metric_threshold/metric_threshold_executor.test.ts index be38db04c3798..c83a5111772f4 100644 --- a/x-pack/plugins/observability_solution/infra/server/lib/alerting/metric_threshold/metric_threshold_executor.test.ts +++ b/x-pack/plugins/observability_solution/infra/server/lib/alerting/metric_threshold/metric_threshold_executor.test.ts @@ -2424,6 +2424,7 @@ const createMockStaticConfiguration = (sources: any): InfraConfig => ({ logThresholdAlertRuleEnabled: true, alertsAndRulesDropdownEnabled: true, profilingEnabled: false, + ruleFormV2Enabled: false, }, enabled: true, sources, diff --git a/x-pack/plugins/observability_solution/infra/server/lib/sources/sources.test.ts b/x-pack/plugins/observability_solution/infra/server/lib/sources/sources.test.ts index ef58e5a985778..c741c0d24538c 100644 --- a/x-pack/plugins/observability_solution/infra/server/lib/sources/sources.test.ts +++ b/x-pack/plugins/observability_solution/infra/server/lib/sources/sources.test.ts @@ -102,6 +102,7 @@ const createMockStaticConfiguration = (): InfraConfig => ({ logThresholdAlertRuleEnabled: true, alertsAndRulesDropdownEnabled: true, profilingEnabled: false, + ruleFormV2Enabled: false, }, enabled: true, }); diff --git a/x-pack/plugins/observability_solution/infra/server/plugin.ts b/x-pack/plugins/observability_solution/infra/server/plugin.ts index a9401a064d931..a73be376ff2cc 100644 --- a/x-pack/plugins/observability_solution/infra/server/plugin.ts +++ b/x-pack/plugins/observability_solution/infra/server/plugin.ts @@ -122,6 +122,7 @@ export const config: PluginConfigDescriptor = { * enabling this feature flag. */ profilingEnabled: schema.boolean({ defaultValue: false }), + ruleFormV2Enabled: schema.boolean({ defaultValue: false }), }), }), exposeToBrowser: publicConfigKeys, diff --git a/x-pack/plugins/observability_solution/observability/public/plugin.ts b/x-pack/plugins/observability_solution/observability/public/plugin.ts index 2a1cd8ad37400..bd123c5d06564 100644 --- a/x-pack/plugins/observability_solution/observability/public/plugin.ts +++ b/x-pack/plugins/observability_solution/observability/public/plugin.ts @@ -106,6 +106,9 @@ export interface ConfigSchema { thresholdRule?: { enabled: boolean; }; + ruleFormV2?: { + enabled: boolean; + }; }; } export type ObservabilityPublicSetup = ReturnType; diff --git a/x-pack/plugins/observability_solution/observability/server/index.ts b/x-pack/plugins/observability_solution/observability/server/index.ts index c6c96c6599632..f7a9435198f8e 100644 --- a/x-pack/plugins/observability_solution/observability/server/index.ts +++ b/x-pack/plugins/observability_solution/observability/server/index.ts @@ -52,6 +52,9 @@ const configSchema = schema.object({ traditional: schema.boolean({ defaultValue: false }), }), }), + ruleFormV2: schema.object({ + enabled: schema.boolean({ defaultValue: false }), + }), }), customThresholdRule: schema.object({ groupByPageSize: schema.number({ defaultValue: 10_000 }), diff --git a/x-pack/plugins/observability_solution/slo/common/config.ts b/x-pack/plugins/observability_solution/slo/common/config.ts new file mode 100644 index 0000000000000..973fc562d743b --- /dev/null +++ b/x-pack/plugins/observability_solution/slo/common/config.ts @@ -0,0 +1,29 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { schema, TypeOf } from '@kbn/config-schema'; + +export const configSchema = schema.object({ + sloOrphanSummaryCleanUpTaskEnabled: schema.boolean({ defaultValue: true }), + enabled: schema.boolean({ defaultValue: true }), + experimental: schema.maybe( + schema.object({ + ruleFormV2: schema.object({ + enabled: schema.boolean({ defaultValue: false }), + }), + }) + ), +}); + +export const config = { + schema: configSchema, + exposeToBrowser: { + experimental: true, + }, +}; +export type SloConfig = TypeOf; +export type ExperimentalFeatures = SloConfig['experimental']; diff --git a/x-pack/plugins/observability_solution/slo/public/application.tsx b/x-pack/plugins/observability_solution/slo/public/application.tsx index 2e2d01f83409d..ec77355a3dbbb 100644 --- a/x-pack/plugins/observability_solution/slo/public/application.tsx +++ b/x-pack/plugins/observability_solution/slo/public/application.tsx @@ -24,6 +24,7 @@ import { PluginContext } from './context/plugin_context'; import { SloPublicPluginsStart } from './types'; import { routes } from './routes/routes'; +import { ExperimentalFeatures } from '../common/config'; function App() { return ( @@ -52,6 +53,7 @@ export const renderApp = ({ kibanaVersion, isServerless, observabilityRuleTypeRegistry, + experimentalFeatures, }: { core: CoreStart; plugins: SloPublicPluginsStart; @@ -62,6 +64,7 @@ export const renderApp = ({ isDev?: boolean; kibanaVersion: string; isServerless?: boolean; + experimentalFeatures: ExperimentalFeatures; }) => { const { element, history, theme$ } = appMountParameters; const i18nCore = core.i18n; @@ -100,6 +103,7 @@ export const renderApp = ({ appMountParameters, ObservabilityPageTemplate, observabilityRuleTypeRegistry, + experimentalFeatures, }} > diff --git a/x-pack/plugins/observability_solution/slo/public/components/header_menu/header_menu.tsx b/x-pack/plugins/observability_solution/slo/public/components/header_menu/header_menu.tsx index 2b76afab609e1..009c1ef31ff87 100644 --- a/x-pack/plugins/observability_solution/slo/public/components/header_menu/header_menu.tsx +++ b/x-pack/plugins/observability_solution/slo/public/components/header_menu/header_menu.tsx @@ -16,7 +16,6 @@ export function HeaderMenu(): React.ReactElement | null { const { http, theme } = useKibana().services; const { appMountParameters } = usePluginContext(); - return ( ; + experimentalFeatures?: ExperimentalFeatures; } export const PluginContext = createContext({} as PluginContextValue); diff --git a/x-pack/plugins/observability_solution/slo/public/pages/slo_edit/shared_flyout/get_create_slo_flyout.tsx b/x-pack/plugins/observability_solution/slo/public/pages/slo_edit/shared_flyout/get_create_slo_flyout.tsx index b7d3dd47819c2..d73144d089ffd 100644 --- a/x-pack/plugins/observability_solution/slo/public/pages/slo_edit/shared_flyout/get_create_slo_flyout.tsx +++ b/x-pack/plugins/observability_solution/slo/public/pages/slo_edit/shared_flyout/get_create_slo_flyout.tsx @@ -14,6 +14,7 @@ import { LazyObservabilityPageTemplateProps } from '@kbn/observability-shared-pl import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public'; import { RecursivePartial } from '@kbn/utility-types'; import { ObservabilityRuleTypeRegistry } from '@kbn/observability-plugin/public'; +import { ExperimentalFeatures } from '../../../../common/config'; import { CreateSLOForm } from '../types'; import { PluginContext } from '../../../context/plugin_context'; import { SloPublicPluginsStart } from '../../../types'; @@ -27,6 +28,7 @@ export const getCreateSLOFlyoutLazy = ({ isDev, kibanaVersion, isServerless, + experimentalFeatures, }: { core: CoreStart; plugins: SloPublicPluginsStart; @@ -35,6 +37,7 @@ export const getCreateSLOFlyoutLazy = ({ isDev?: boolean; kibanaVersion: string; isServerless?: boolean; + experimentalFeatures: ExperimentalFeatures; }) => { return ({ onClose, @@ -60,6 +63,7 @@ export const getCreateSLOFlyoutLazy = ({ isDev, observabilityRuleTypeRegistry, ObservabilityPageTemplate, + experimentalFeatures, }} > diff --git a/x-pack/plugins/observability_solution/slo/public/plugin.ts b/x-pack/plugins/observability_solution/slo/public/plugin.ts index 45bd9abb95ef1..285c85b06e78e 100644 --- a/x-pack/plugins/observability_solution/slo/public/plugin.ts +++ b/x-pack/plugins/observability_solution/slo/public/plugin.ts @@ -24,13 +24,18 @@ import { SloListLocatorDefinition } from './locators/slo_list'; import { SLOS_BASE_PATH } from '../common/locators/paths'; import { getCreateSLOFlyoutLazy } from './pages/slo_edit/shared_flyout/get_create_slo_flyout'; import { registerBurnRateRuleType } from './rules/register_burn_rate_rule_type'; +import { ExperimentalFeatures, SloConfig } from '../common/config'; export class SloPlugin implements Plugin { private readonly appUpdater$ = new BehaviorSubject(() => ({})); + private experimentalFeatures: ExperimentalFeatures = { ruleFormV2: { enabled: false } }; - constructor(private readonly initContext: PluginInitializerContext) {} + constructor(private readonly initContext: PluginInitializerContext) { + this.experimentalFeatures = + this.initContext.config.get().experimental ?? this.experimentalFeatures; + } public setup( coreSetup: CoreSetup, @@ -60,6 +65,7 @@ export class SloPlugin ObservabilityPageTemplate: pluginsStart.observabilityShared.navigation.PageTemplate, plugins: { ...pluginsStart, ruleTypeRegistry, actionTypeRegistry }, isServerless: !!pluginsStart.serverless, + experimentalFeatures: this.experimentalFeatures, }); }; const appUpdater$ = this.appUpdater$; @@ -145,6 +151,7 @@ export class SloPlugin ObservabilityPageTemplate: pluginsStart.observabilityShared.navigation.PageTemplate, plugins: { ...pluginsStart, ruleTypeRegistry, actionTypeRegistry }, isServerless: !!pluginsStart.serverless, + experimentalFeatures: this.experimentalFeatures, }), }; } diff --git a/x-pack/plugins/observability_solution/slo/server/index.ts b/x-pack/plugins/observability_solution/slo/server/index.ts index 1707a7c692952..5d6ccadb7f323 100644 --- a/x-pack/plugins/observability_solution/slo/server/index.ts +++ b/x-pack/plugins/observability_solution/slo/server/index.ts @@ -6,7 +6,7 @@ */ import { PluginInitializerContext } from '@kbn/core/server'; -import { schema, TypeOf } from '@kbn/config-schema'; +import { configSchema } from '../common/config'; // This exports static code and TypeScript types, // as well as, Kibana Platform `plugin()` initializer. @@ -18,12 +18,10 @@ export async function plugin(initializerContext: PluginInitializerContext) { export type { PluginSetup, PluginStart } from './plugin'; -const configSchema = schema.object({ - sloOrphanSummaryCleanUpTaskEnabled: schema.boolean({ defaultValue: true }), - enabled: schema.boolean({ defaultValue: true }), -}); - export const config = { schema: configSchema, + exposeToBrowser: { + experimental: true, + }, }; -export type SloConfig = TypeOf; +export type { SloConfig } from '../common/config'; diff --git a/x-pack/plugins/observability_solution/uptime/common/config.ts b/x-pack/plugins/observability_solution/uptime/common/config.ts index 238d53dfeebfa..69464c3ea3486 100644 --- a/x-pack/plugins/observability_solution/uptime/common/config.ts +++ b/x-pack/plugins/observability_solution/uptime/common/config.ts @@ -11,10 +11,19 @@ import { schema, TypeOf } from '@kbn/config-schema'; const uptimeConfig = schema.object({ index: schema.maybe(schema.string()), enabled: schema.boolean({ defaultValue: true }), + experimental: schema.maybe( + schema.object({ + ruleFormV2Enabled: schema.maybe(schema.boolean({ defaultValue: false })), + }) + ), }); export const config: PluginConfigDescriptor = { schema: uptimeConfig, + exposeToBrowser: { + experimental: true, + }, }; export type UptimeConfig = TypeOf; +export type ExperimentalFeatures = UptimeConfig['experimental']; diff --git a/x-pack/plugins/observability_solution/uptime/public/legacy_uptime/app/render_app.tsx b/x-pack/plugins/observability_solution/uptime/public/legacy_uptime/app/render_app.tsx index 1a9d5aac398ab..47fe51afa44ff 100644 --- a/x-pack/plugins/observability_solution/uptime/public/legacy_uptime/app/render_app.tsx +++ b/x-pack/plugins/observability_solution/uptime/public/legacy_uptime/app/render_app.tsx @@ -11,6 +11,7 @@ import { i18n as i18nFormatter } from '@kbn/i18n'; import { AppMountParameters, CoreStart } from '@kbn/core/public'; import { getIntegratedAppAvailability } from '../lib/adapters/framework/capabilities_adapter'; import { DEFAULT_TIMEPICKER_QUICK_RANGES, INTEGRATED_SOLUTIONS } from '../../../common/constants'; +import type { ExperimentalFeatures } from '../../../common/config'; import { UptimeApp, UptimeAppProps } from './uptime_app'; import { ClientPluginsSetup, ClientPluginsStart } from '../../plugin'; @@ -19,7 +20,8 @@ export function renderApp( plugins: ClientPluginsSetup, startPlugins: ClientPluginsStart, appMountParameters: AppMountParameters, - isDev: boolean + isDev: boolean, + experimentalFeatures: ExperimentalFeatures ) { const { application: { capabilities }, diff --git a/x-pack/plugins/observability_solution/uptime/public/plugin.ts b/x-pack/plugins/observability_solution/uptime/public/plugin.ts index 1e891b8086893..e933209ecb992 100644 --- a/x-pack/plugins/observability_solution/uptime/public/plugin.ts +++ b/x-pack/plugins/observability_solution/uptime/public/plugin.ts @@ -56,6 +56,7 @@ import { ObservabilityAIAssistantPublicSetup, } from '@kbn/observability-ai-assistant-plugin/public'; import { PLUGIN } from '../common/constants/plugin'; +import { UptimeConfig } from '../common/config'; import { LazySyntheticsPolicyCreateExtension, LazySyntheticsPolicyEditExtension, @@ -119,9 +120,15 @@ export type ClientStart = void; export class UptimePlugin implements Plugin { - constructor(private readonly initContext: PluginInitializerContext) {} + constructor(private readonly initContext: PluginInitializerContext) { + this.experimentalFeatures = + this.initContext.config.get().experimental || this.experimentalFeatures; + } private uptimeAppUpdater = new BehaviorSubject(() => ({})); + private experimentalFeatures: UptimeConfig['experimental'] = { + ruleFormV2Enabled: false, + }; public setup(core: CoreSetup, plugins: ClientPluginsSetup): void { if (plugins.home) { @@ -202,7 +209,14 @@ export class UptimePlugin mount: async (params: AppMountParameters) => { const [coreStart, corePlugins] = await core.getStartServices(); const { renderApp } = await import('./legacy_uptime/app/render_app'); - return renderApp(coreStart, plugins, corePlugins, params, this.initContext.env.mode.dev); + return renderApp( + coreStart, + plugins, + corePlugins, + params, + this.initContext.env.mode.dev, + this.experimentalFeatures + ); }, updater$: this.uptimeAppUpdater, }); diff --git a/x-pack/plugins/transform/common/config.ts b/x-pack/plugins/transform/common/config.ts new file mode 100644 index 0000000000000..8f5b2e4f7d22b --- /dev/null +++ b/x-pack/plugins/transform/common/config.ts @@ -0,0 +1,20 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { TypeOf } from '@kbn/config-schema'; +import { schema } from '@kbn/config-schema'; + +export const configSchema = schema.object({ + experimental: schema.maybe( + schema.object({ + ruleFormV2Enabled: schema.maybe(schema.boolean()), + }) + ), +}); + +export type ConfigSchema = TypeOf; +export type ExperimentalFeatures = ConfigSchema['experimental']; diff --git a/x-pack/plugins/transform/public/app/app.tsx b/x-pack/plugins/transform/public/app/app.tsx index 40e0c75ef3356..9da8af2ba8407 100644 --- a/x-pack/plugins/transform/public/app/app.tsx +++ b/x-pack/plugins/transform/public/app/app.tsx @@ -15,6 +15,7 @@ import { Router, Routes, Route } from '@kbn/shared-ux-router'; import type { ScopedHistory } from '@kbn/core/public'; import { KibanaContextProvider, KibanaThemeProvider } from '@kbn/kibana-react-plugin/public'; +import type { ExperimentalFeatures } from '../../common/config'; import { SECTION_SLUG } from './common/constants'; import type { AppDependencies } from './app_dependencies'; import { CloneTransformSection } from './sections/clone_transform'; @@ -22,6 +23,7 @@ import { CreateTransformSection } from './sections/create_transform'; import { TransformManagementSection } from './sections/transform_management'; import { EnabledFeaturesContextProvider, + ExperimentalFeaturesContextProvider, type TransformEnabledFeatures, } from './serverless_context'; @@ -44,7 +46,8 @@ export const App: FC<{ history: ScopedHistory }> = ({ history }) => ( export const renderApp = ( element: HTMLElement, appDependencies: AppDependencies, - enabledFeatures: TransformEnabledFeatures + enabledFeatures: TransformEnabledFeatures, + experimentalFeatures: ExperimentalFeatures ) => { const I18nContext = appDependencies.i18n.Context; @@ -64,7 +67,9 @@ export const renderApp = ( - + + + diff --git a/x-pack/plugins/transform/public/app/mount_management_section.ts b/x-pack/plugins/transform/public/app/mount_management_section.ts index 85444d089d2d7..edde02eddac42 100644 --- a/x-pack/plugins/transform/public/app/mount_management_section.ts +++ b/x-pack/plugins/transform/public/app/mount_management_section.ts @@ -13,6 +13,7 @@ import { type TransformEnabledFeatures } from './serverless_context'; import type { PluginsDependencies } from '../plugin'; import { getMlSharedImports } from '../shared_imports'; +import type { ExperimentalFeatures } from '../../common/config'; import type { AppDependencies } from './app_dependencies'; import { breadcrumbService } from './services/navigation'; import { docTitleService } from './services/navigation'; @@ -24,7 +25,8 @@ const localStorage = new Storage(window.localStorage); export async function mountManagementSection( coreSetup: CoreSetup, params: ManagementAppMountParams, - isServerless: boolean + isServerless: boolean, + experimentalFeatures: ExperimentalFeatures ) { const { element, setBreadcrumbs, history } = params; const { http, getStartServices } = coreSetup; @@ -99,7 +101,12 @@ export async function mountManagementSection( const enabledFeatures: TransformEnabledFeatures = { showNodeInfo: !isServerless, }; - const unmountAppCallback = renderApp(element, appDependencies, enabledFeatures); + const unmountAppCallback = renderApp( + element, + appDependencies, + enabledFeatures, + experimentalFeatures + ); return () => { docTitle.reset(); diff --git a/x-pack/plugins/transform/public/app/serverless_context.tsx b/x-pack/plugins/transform/public/app/serverless_context.tsx index b62c89a54fec6..108eea3a7016c 100644 --- a/x-pack/plugins/transform/public/app/serverless_context.tsx +++ b/x-pack/plugins/transform/public/app/serverless_context.tsx @@ -7,6 +7,7 @@ import type { FC } from 'react'; import React, { createContext, useContext, useMemo } from 'react'; +import type { ExperimentalFeatures } from '../../common/config'; export interface TransformEnabledFeatures { showNodeInfo: boolean; @@ -15,9 +16,9 @@ export const EnabledFeaturesContext = createContext({ showNodeInfo: true, }); -export const EnabledFeaturesContextProvider: FC<{ enabledFeatures: TransformEnabledFeatures }> = ( - props -) => { +export const EnabledFeaturesContextProvider: FC<{ + enabledFeatures: TransformEnabledFeatures; +}> = (props) => { const { children, enabledFeatures } = props; return ( @@ -26,9 +27,30 @@ export const EnabledFeaturesContextProvider: FC<{ enabledFeatures: TransformEnab ); }; +export const ExperimentalFeaturesContext = createContext({ + ruleFormV2Enabled: false, +}); +export const ExperimentalFeaturesContextProvider: FC<{ + experimentalFeatures: ExperimentalFeatures; +}> = (props) => { + const { children, experimentalFeatures } = props; + return ( + + {children} + + ); +}; + export function useEnabledFeatures() { const context = useContext(EnabledFeaturesContext); return useMemo(() => { return context; }, [context]); } + +export function useExperimentalFeatures() { + const context = useContext(ExperimentalFeaturesContext); + return useMemo(() => { + return context; + }, [context]); +} diff --git a/x-pack/plugins/transform/public/plugin.ts b/x-pack/plugins/transform/public/plugin.ts index 098b55765a889..41e657481b59f 100644 --- a/x-pack/plugins/transform/public/plugin.ts +++ b/x-pack/plugins/transform/public/plugin.ts @@ -25,6 +25,7 @@ import type { ContentManagementPublicStart } from '@kbn/content-management-plugi import type { SavedSearchPublicPluginStart } from '@kbn/saved-search-plugin/public'; import type { PluginInitializerContext } from '@kbn/core/public'; import type { DataViewEditorStart } from '@kbn/data-view-editor-plugin/public'; +import type { ConfigSchema } from '../common/config'; import { registerFeature } from './register_feature'; import { getTransformHealthRuleType } from './alerting'; @@ -49,8 +50,13 @@ export interface PluginsDependencies { export class TransformUiPlugin { private isServerless: boolean = false; - constructor(initializerContext: PluginInitializerContext) { + private experimentalFeatures: ConfigSchema['experimental'] = { + ruleFormV2Enabled: false, + }; + constructor(initializerContext: PluginInitializerContext) { this.isServerless = initializerContext.env.packageInfo.buildFlavor === 'serverless'; + this.experimentalFeatures = + initializerContext.config.get().experimental ?? this.experimentalFeatures; } public setup(coreSetup: CoreSetup, pluginsSetup: PluginsDependencies): void { @@ -66,7 +72,12 @@ export class TransformUiPlugin { order: 5, mount: async (params) => { const { mountManagementSection } = await import('./app/mount_management_section'); - return mountManagementSection(coreSetup, params, this.isServerless); + return mountManagementSection( + coreSetup, + params, + this.isServerless, + this.experimentalFeatures + ); }, }); registerFeature(home); diff --git a/x-pack/plugins/transform/server/index.ts b/x-pack/plugins/transform/server/index.ts index 87d7e04d99db4..692593b0a0976 100644 --- a/x-pack/plugins/transform/server/index.ts +++ b/x-pack/plugins/transform/server/index.ts @@ -5,11 +5,19 @@ * 2.0. */ -import type { PluginInitializerContext } from '@kbn/core/server'; +import type { PluginInitializerContext, PluginConfigDescriptor } from '@kbn/core/server'; +import { configSchema, type ConfigSchema } from '../common/config'; export const plugin = async (ctx: PluginInitializerContext) => { const { TransformServerPlugin } = await import('./plugin'); return new TransformServerPlugin(ctx); }; +export const config: PluginConfigDescriptor = { + schema: configSchema, + exposeToBrowser: { + experimental: true, + }, +}; + export { registerTransformHealthRuleType } from './lib/alerting'; diff --git a/x-pack/plugins/triggers_actions_ui/.storybook/decorator.tsx b/x-pack/plugins/triggers_actions_ui/.storybook/decorator.tsx index 4abc239b7ed37..7b22d0520a6c4 100644 --- a/x-pack/plugins/triggers_actions_ui/.storybook/decorator.tsx +++ b/x-pack/plugins/triggers_actions_ui/.storybook/decorator.tsx @@ -66,6 +66,7 @@ export const StorybookContextDecorator: React.FC ruleKqlBar: true, isMustacheAutocompleteOn: false, showMustacheAutocompleteSwitch: false, + ruleFormV2: false, }, }); return ( diff --git a/x-pack/plugins/triggers_actions_ui/common/experimental_features.ts b/x-pack/plugins/triggers_actions_ui/common/experimental_features.ts index c03de37eac8cb..4259fbcb9a7c2 100644 --- a/x-pack/plugins/triggers_actions_ui/common/experimental_features.ts +++ b/x-pack/plugins/triggers_actions_ui/common/experimental_features.ts @@ -21,6 +21,7 @@ export const allowedExperimentalValues = Object.freeze({ ruleKqlBar: false, isMustacheAutocompleteOn: false, showMustacheAutocompleteSwitch: false, + ruleFormV2: false, }); type ExperimentalConfigKeys = Array; diff --git a/x-pack/plugins/triggers_actions_ui/public/common/get_experimental_features.test.tsx b/x-pack/plugins/triggers_actions_ui/public/common/get_experimental_features.test.tsx index 18245930833c1..2d0bbf6ecee07 100644 --- a/x-pack/plugins/triggers_actions_ui/public/common/get_experimental_features.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/common/get_experimental_features.test.tsx @@ -24,6 +24,7 @@ describe('getIsExperimentalFeatureEnabled', () => { ruleKqlBar: true, isMustacheAutocompleteOn: false, showMustacheAutocompleteSwitch: false, + ruleFormV2: false, }, }); @@ -63,6 +64,10 @@ describe('getIsExperimentalFeatureEnabled', () => { expect(result).toEqual(false); + result = getIsExperimentalFeatureEnabled('ruleFormV2'); + + expect(result).toEqual(false); + expect(() => getIsExperimentalFeatureEnabled('doesNotExist' as any)).toThrowError( `Invalid enable value doesNotExist. Allowed values are: ${allowedExperimentalValueKeys.join( ', '