diff --git a/examples/feature_flags_example/public/components/app.tsx b/examples/feature_flags_example/public/components/app.tsx index 432e7dc348abc..97f850b5dd475 100644 --- a/examples/feature_flags_example/public/components/app.tsx +++ b/examples/feature_flags_example/public/components/app.tsx @@ -8,24 +8,15 @@ */ import React from 'react'; -import { - EuiHorizontalRule, - EuiPageTemplate, - EuiTitle, - EuiText, - EuiLink, - EuiListGroup, - EuiListGroupItem, -} from '@elastic/eui'; +import { EuiHorizontalRule, EuiPageTemplate, EuiTitle, EuiText, EuiLink } from '@elastic/eui'; import type { CoreStart, FeatureFlagsStart } from '@kbn/core/public'; -import useObservable from 'react-use/lib/useObservable'; -import { - FeatureFlagExampleBoolean, - FeatureFlagExampleNumber, - FeatureFlagExampleString, -} from '../../common/feature_flags'; import { PLUGIN_NAME } from '../../common'; +import { + FeatureFlagsFullList, + FeatureFlagsReactiveList, + FeatureFlagsStaticList, +} from './feature_flags_list'; interface FeatureFlagsExampleAppDeps { featureFlags: FeatureFlagsStart; @@ -34,16 +25,6 @@ interface FeatureFlagsExampleAppDeps { } export const FeatureFlagsExampleApp = ({ featureFlags }: FeatureFlagsExampleAppDeps) => { - // Fetching the feature flags synchronously - const bool = featureFlags.getBooleanValue(FeatureFlagExampleBoolean, false); - const str = featureFlags.getStringValue(FeatureFlagExampleString, 'red'); - const num = featureFlags.getNumberValue(FeatureFlagExampleNumber, 1); - - // Use React Hooks to observe feature flags changes - const bool$ = useObservable(featureFlags.getBooleanValue$(FeatureFlagExampleBoolean, false)); - const str$ = useObservable(featureFlags.getStringValue$(FeatureFlagExampleString, 'red')); - const num$ = useObservable(featureFlags.getNumberValue$(FeatureFlagExampleNumber, 1)); - return ( <> @@ -67,22 +48,21 @@ export const FeatureFlagsExampleApp = ({ featureFlags }: FeatureFlagsExampleAppD .

- -

- The feature flags are: - - - -

-
- -

- The observed feature flags are: - - - -

-
+

Rendered separately

+

+ Each list are 2 different components, so only the reactive one is re-rendered when the + feature flag is updated and the static one keeps the value until the next refresh. +

+ + + +

Rendered together

+

+ `useObservable` causes a full re-render of the component, updating the{' '} + statically + evaluated flags as well. +

+
diff --git a/examples/feature_flags_example/public/components/feature_flags_list.tsx b/examples/feature_flags_example/public/components/feature_flags_list.tsx new file mode 100644 index 0000000000000..73d6535295865 --- /dev/null +++ b/examples/feature_flags_example/public/components/feature_flags_list.tsx @@ -0,0 +1,91 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the "Elastic License + * 2.0", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +import { EuiListGroup, EuiListGroupItem } from '@elastic/eui'; +import React from 'react'; +import type { FeatureFlagsStart } from '@kbn/core-feature-flags-browser'; +import useObservable from 'react-use/lib/useObservable'; +import { + FeatureFlagExampleBoolean, + FeatureFlagExampleNumber, + FeatureFlagExampleString, +} from '../../common/feature_flags'; + +export interface FeatureFlagsListProps { + featureFlags: FeatureFlagsStart; +} + +export const FeatureFlagsStaticList = ({ featureFlags }: FeatureFlagsListProps) => { + // Fetching the feature flags synchronously + const bool = featureFlags.getBooleanValue(FeatureFlagExampleBoolean, false); + const str = featureFlags.getStringValue(FeatureFlagExampleString, 'red'); + const num = featureFlags.getNumberValue(FeatureFlagExampleNumber, 1); + + return ( + +

+ The feature flags are: + + + +

+
+ ); +}; + +export const FeatureFlagsReactiveList = ({ featureFlags }: FeatureFlagsListProps) => { + // Use React Hooks to observe feature flags changes + const bool$ = useObservable(featureFlags.getBooleanValue$(FeatureFlagExampleBoolean, false)); + const str$ = useObservable(featureFlags.getStringValue$(FeatureFlagExampleString, 'red')); + const num$ = useObservable(featureFlags.getNumberValue$(FeatureFlagExampleNumber, 1)); + + return ( + +

+ The observed feature flags are: + + + +

+
+ ); +}; + +export const FeatureFlagsFullList = ({ featureFlags }: FeatureFlagsListProps) => { + // Fetching the feature flags synchronously + const bool = featureFlags.getBooleanValue(FeatureFlagExampleBoolean, false); + const str = featureFlags.getStringValue(FeatureFlagExampleString, 'red'); + const num = featureFlags.getNumberValue(FeatureFlagExampleNumber, 1); + + // Use React Hooks to observe feature flags changes + const bool$ = useObservable(featureFlags.getBooleanValue$(FeatureFlagExampleBoolean, false)); + const str$ = useObservable(featureFlags.getStringValue$(FeatureFlagExampleString, 'red')); + const num$ = useObservable(featureFlags.getNumberValue$(FeatureFlagExampleNumber, 1)); + + return ( + <> + +

+ The feature flags are: + + + +

+
+ +

+ The observed feature flags are: + + + +

+
+ + ); +}; diff --git a/examples/feature_flags_example/tsconfig.json b/examples/feature_flags_example/tsconfig.json index bbd68332f3d37..77eb5d09ca85b 100644 --- a/examples/feature_flags_example/tsconfig.json +++ b/examples/feature_flags_example/tsconfig.json @@ -20,5 +20,6 @@ "@kbn/core-plugins-server", "@kbn/config-schema", "@kbn/developer-examples-plugin", + "@kbn/core-feature-flags-browser", ] } diff --git a/packages/core/feature-flags/README.mdx b/packages/core/feature-flags/README.mdx index 99e10b72aa68c..8a28fdf35932b 100644 --- a/packages/core/feature-flags/README.mdx +++ b/packages/core/feature-flags/README.mdx @@ -15,7 +15,7 @@ The service is always enabled, however, it will return the fallback value if a f Kibana only registers a provider when running on Elastic Cloud Hosted/Serverless. And even in those scenarios, we expect that some customers might have network restrictions that might not allow the flags to evaluate. The fallback value must provide a non-broken experience to users. -:warning: Feature Flags are considered dynamic configuration and cannot be used for settings that require restarting Kibana. +⚠️Feature Flags are considered dynamic configuration and cannot be used for settings that require restarting Kibana. One example of invalid use cases are settings used during the `setup` lifecycle of the plugin, such as settings that define if an HTTP route is registered or not. Instead, you should always register the route, and return `404 - Not found` in the route handler if the feature flag returns a _disabled_ state. diff --git a/packages/core/feature-flags/core-feature-flags-browser-internal/src/feature_flags_service.ts b/packages/core/feature-flags/core-feature-flags-browser-internal/src/feature_flags_service.ts index e282cc52ddd4e..afc32d93aee3c 100644 --- a/packages/core/feature-flags/core-feature-flags-browser-internal/src/feature_flags_service.ts +++ b/packages/core/feature-flags/core-feature-flags-browser-internal/src/feature_flags_service.ts @@ -68,7 +68,15 @@ export class FeatureFlagsService { if (this.isProviderReadyPromise) { throw new Error('A provider has already been set. This API cannot be called twice.'); } + const transaction = apm.startTransaction('set-provider', 'feature-flags'); this.isProviderReadyPromise = OpenFeature.setProviderAndWait(provider); + this.isProviderReadyPromise + .then(() => transaction?.end()) + .catch((err) => { + this.logger.error(err); + apm.captureError(err); + transaction?.end(); + }); }, appendContext: (contextToAppend) => this.appendContext(contextToAppend), };