mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 09:19:04 -04:00
[Feature flags example] Apply FF naming conventions (#196535)
This commit is contained in:
parent
bad11abb07
commit
f25b3be194
6 changed files with 54 additions and 14 deletions
|
@ -7,6 +7,6 @@
|
|||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
export const FeatureFlagExampleBoolean = 'example-boolean';
|
||||
export const FeatureFlagExampleString = 'example-string';
|
||||
export const FeatureFlagExampleNumber = 'example-number';
|
||||
export const FeatureFlagExampleBoolean = 'featureFlagsExample.exampleBoolean';
|
||||
export const FeatureFlagExampleString = 'featureFlagsExample.exampleString';
|
||||
export const FeatureFlagExampleNumber = 'featureFlagsExample.exampleNumber';
|
||||
|
|
|
@ -3,7 +3,7 @@ id: kibFeatureFlagsService
|
|||
slug: /kibana-dev-docs/tutorials/feature-flags-service
|
||||
title: Feature Flags service
|
||||
description: The Feature Flags service provides the necessary APIs to evaluate dynamic feature flags.
|
||||
date: 2024-07-26
|
||||
date: 2024-10-16
|
||||
tags: ['kibana', 'dev', 'contributor', 'api docs', 'a/b testing', 'feature flags', 'flags']
|
||||
---
|
||||
|
||||
|
@ -12,7 +12,13 @@ tags: ['kibana', 'dev', 'contributor', 'api docs', 'a/b testing', 'feature flags
|
|||
The Feature Flags service provides the necessary APIs to evaluate dynamic feature flags.
|
||||
|
||||
The service is always enabled, however, it will return the fallback value if a feature flags provider hasn't been attached.
|
||||
Kibana only registers a provider when running on Elastic Cloud Hosted/Serverless.
|
||||
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.
|
||||
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.
|
||||
|
||||
For a code example, refer to the [Feature Flags Example plugin](../../../examples/feature_flags_example)
|
||||
|
||||
|
@ -32,7 +38,7 @@ import type { PluginInitializerContext } from '@kbn/core-plugins-server';
|
|||
|
||||
export const featureFlags: FeatureFlagDefinitions = [
|
||||
{
|
||||
key: 'my-cool-feature',
|
||||
key: 'myPlugin.myCoolFeature',
|
||||
name: 'My cool feature',
|
||||
description: 'Enables the cool feature to auto-hide the navigation bar',
|
||||
tags: ['my-plugin', 'my-service', 'ui'],
|
||||
|
@ -118,7 +124,7 @@ async (context, request, response) => {
|
|||
const { featureFlags } = await context.core;
|
||||
return response.ok({
|
||||
body: {
|
||||
number: await featureFlags.getNumberValue('example-number', 1),
|
||||
number: await featureFlags.getNumberValue('myPlugin.exampleNumber', 1),
|
||||
},
|
||||
});
|
||||
}
|
||||
|
@ -142,7 +148,7 @@ provider. In the `kibana.yml`, the following config sets the overrides:
|
|||
|
||||
```yaml
|
||||
feature_flags.overrides:
|
||||
my-feature-flag: 'my-forced-value'
|
||||
myPlugin.myFeatureFlag: 'my-forced-value'
|
||||
```
|
||||
|
||||
> [!WARNING]
|
||||
|
|
|
@ -188,7 +188,11 @@ describe('FeatureFlagsService Browser', () => {
|
|||
beforeEach(async () => {
|
||||
addHandlerSpy = jest.spyOn(featureFlagsClient, 'addHandler');
|
||||
injectedMetadata.getFeatureFlags.mockReturnValue({
|
||||
overrides: { 'my-overridden-flag': true },
|
||||
overrides: {
|
||||
'my-overridden-flag': true,
|
||||
'myPlugin.myOverriddenFlag': true,
|
||||
myDestructuredObjPlugin: { myOverriddenFlag: true },
|
||||
},
|
||||
});
|
||||
featureFlagsService.setup({ injectedMetadata });
|
||||
startContract = await featureFlagsService.start();
|
||||
|
@ -288,5 +292,14 @@ describe('FeatureFlagsService Browser', () => {
|
|||
expect(getBooleanValueSpy).toHaveBeenCalledTimes(1);
|
||||
expect(getBooleanValueSpy).toHaveBeenCalledWith('another-flag', false);
|
||||
});
|
||||
|
||||
test('overrides with dotted names', async () => {
|
||||
const getBooleanValueSpy = jest.spyOn(featureFlagsClient, 'getBooleanValue');
|
||||
expect(startContract.getBooleanValue('myPlugin.myOverriddenFlag', false)).toEqual(true);
|
||||
expect(
|
||||
startContract.getBooleanValue('myDestructuredObjPlugin.myOverriddenFlag', false)
|
||||
).toEqual(true);
|
||||
expect(getBooleanValueSpy).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -20,6 +20,7 @@ import { apm } from '@elastic/apm-rum';
|
|||
import { type Client, ClientProviderEvents, OpenFeature } from '@openfeature/web-sdk';
|
||||
import deepMerge from 'deepmerge';
|
||||
import { filter, map, startWith, Subject } from 'rxjs';
|
||||
import { get } from 'lodash';
|
||||
|
||||
/**
|
||||
* setup method dependencies
|
||||
|
@ -172,9 +173,10 @@ export class FeatureFlagsService {
|
|||
flagName: string,
|
||||
fallbackValue: T
|
||||
): T {
|
||||
const override = get(this.overrides, flagName); // using lodash get because flagName can come with dots and the config parser might structure it in objects.
|
||||
const value =
|
||||
typeof this.overrides[flagName] !== 'undefined'
|
||||
? (this.overrides[flagName] as T)
|
||||
typeof override !== 'undefined'
|
||||
? (override as T)
|
||||
: // We have to bind the evaluation or the client will lose its internal context
|
||||
evaluationFn.bind(this.featureFlagsClient)(flagName, fallbackValue);
|
||||
apm.addLabels({ [`flag_${flagName}`]: value });
|
||||
|
|
|
@ -27,6 +27,8 @@ describe('FeatureFlagsService Server', () => {
|
|||
atPath: {
|
||||
overrides: {
|
||||
'my-overridden-flag': true,
|
||||
'myPlugin.myOverriddenFlag': true,
|
||||
myDestructuredObjPlugin: { myOverriddenFlag: true },
|
||||
},
|
||||
},
|
||||
}),
|
||||
|
@ -251,10 +253,25 @@ describe('FeatureFlagsService Server', () => {
|
|||
expect(getBooleanValueSpy).toHaveBeenCalledTimes(1);
|
||||
expect(getBooleanValueSpy).toHaveBeenCalledWith('another-flag', false);
|
||||
});
|
||||
|
||||
test('overrides with dotted names', async () => {
|
||||
const getBooleanValueSpy = jest.spyOn(featureFlagsClient, 'getBooleanValue');
|
||||
await expect(
|
||||
startContract.getBooleanValue('myPlugin.myOverriddenFlag', false)
|
||||
).resolves.toEqual(true);
|
||||
await expect(
|
||||
startContract.getBooleanValue('myDestructuredObjPlugin.myOverriddenFlag', false)
|
||||
).resolves.toEqual(true);
|
||||
expect(getBooleanValueSpy).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
test('returns overrides', () => {
|
||||
const { getOverrides } = featureFlagsService.setup();
|
||||
expect(getOverrides()).toStrictEqual({ 'my-overridden-flag': true });
|
||||
expect(getOverrides()).toStrictEqual({
|
||||
'my-overridden-flag': true,
|
||||
'myPlugin.myOverriddenFlag': true,
|
||||
myDestructuredObjPlugin: { myOverriddenFlag: true },
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -24,6 +24,7 @@ import {
|
|||
} from '@openfeature/server-sdk';
|
||||
import deepMerge from 'deepmerge';
|
||||
import { filter, switchMap, startWith, Subject } from 'rxjs';
|
||||
import { get } from 'lodash';
|
||||
import { type FeatureFlagsConfig, featureFlagsConfig } from './feature_flags_config';
|
||||
|
||||
/**
|
||||
|
@ -165,9 +166,10 @@ export class FeatureFlagsService {
|
|||
flagName: string,
|
||||
fallbackValue: T
|
||||
): Promise<T> {
|
||||
const override = get(this.overrides, flagName); // using lodash get because flagName can come with dots and the config parser might structure it in objects.
|
||||
const value =
|
||||
typeof this.overrides[flagName] !== 'undefined'
|
||||
? (this.overrides[flagName] as T)
|
||||
typeof override !== 'undefined'
|
||||
? (override as T)
|
||||
: // We have to bind the evaluation or the client will lose its internal context
|
||||
await evaluationFn.bind(this.featureFlagsClient)(flagName, fallbackValue);
|
||||
apm.addLabels({ [`flag_${flagName}`]: value });
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue