[Feature Flags] Add APM transaction + better example code (#199671)

Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
Alejandro Fernández Haro 2024-11-12 11:59:52 +01:00 committed by GitHub
parent 803738fa0c
commit 93d7044919
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 122 additions and 42 deletions

View file

@ -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 (
<>
<EuiPageTemplate>
@ -67,22 +48,21 @@ export const FeatureFlagsExampleApp = ({ featureFlags }: FeatureFlagsExampleAppD
.
</p>
<EuiHorizontalRule />
<EuiListGroup>
<p>
The feature flags are:
<EuiListGroupItem label={`${FeatureFlagExampleBoolean}: ${bool}`} />
<EuiListGroupItem label={`${FeatureFlagExampleString}: ${str}`} />
<EuiListGroupItem label={`${FeatureFlagExampleNumber}: ${num}`} />
</p>
</EuiListGroup>
<EuiListGroup>
<p>
The <strong>observed</strong> feature flags are:
<EuiListGroupItem label={`${FeatureFlagExampleBoolean}: ${bool$}`} />
<EuiListGroupItem label={`${FeatureFlagExampleString}: ${str$}`} />
<EuiListGroupItem label={`${FeatureFlagExampleNumber}: ${num$}`} />
</p>
</EuiListGroup>
<h3>Rendered separately</h3>
<p>
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.
</p>
<FeatureFlagsStaticList featureFlags={featureFlags} />
<FeatureFlagsReactiveList featureFlags={featureFlags} />
<EuiHorizontalRule />
<h3>Rendered together</h3>
<p>
`useObservable` causes a full re-render of the component, updating the{' '}
<i>statically</i>
evaluated flags as well.
</p>
<FeatureFlagsFullList featureFlags={featureFlags} />
</EuiText>
</EuiPageTemplate.Section>
</EuiPageTemplate>

View file

@ -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 (
<EuiListGroup>
<p>
The feature flags are:
<EuiListGroupItem label={`${FeatureFlagExampleBoolean}: ${bool}`} />
<EuiListGroupItem label={`${FeatureFlagExampleString}: ${str}`} />
<EuiListGroupItem label={`${FeatureFlagExampleNumber}: ${num}`} />
</p>
</EuiListGroup>
);
};
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 (
<EuiListGroup>
<p>
The <strong>observed</strong> feature flags are:
<EuiListGroupItem label={`${FeatureFlagExampleBoolean}: ${bool$}`} />
<EuiListGroupItem label={`${FeatureFlagExampleString}: ${str$}`} />
<EuiListGroupItem label={`${FeatureFlagExampleNumber}: ${num$}`} />
</p>
</EuiListGroup>
);
};
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 (
<>
<EuiListGroup>
<p>
The feature flags are:
<EuiListGroupItem label={`${FeatureFlagExampleBoolean}: ${bool}`} />
<EuiListGroupItem label={`${FeatureFlagExampleString}: ${str}`} />
<EuiListGroupItem label={`${FeatureFlagExampleNumber}: ${num}`} />
</p>
</EuiListGroup>
<EuiListGroup>
<p>
The <strong>observed</strong> feature flags are:
<EuiListGroupItem label={`${FeatureFlagExampleBoolean}: ${bool$}`} />
<EuiListGroupItem label={`${FeatureFlagExampleString}: ${str$}`} />
<EuiListGroupItem label={`${FeatureFlagExampleNumber}: ${num$}`} />
</p>
</EuiListGroup>
</>
);
};

View file

@ -20,5 +20,6 @@
"@kbn/core-plugins-server",
"@kbn/config-schema",
"@kbn/developer-examples-plugin",
"@kbn/core-feature-flags-browser",
]
}

View file

@ -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.

View file

@ -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),
};