Skip to content

Commit

Permalink
[Feature Flags] Add APM transaction + better example code (elastic#19…
Browse files Browse the repository at this point in the history
…9671)

Co-authored-by: kibanamachine <[email protected]>
  • Loading branch information
2 people authored and tkajtoch committed Nov 12, 2024
1 parent ec50f2a commit d55ce92
Show file tree
Hide file tree
Showing 5 changed files with 122 additions and 42 deletions.
62 changes: 21 additions & 41 deletions examples/feature_flags_example/public/components/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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>
Expand All @@ -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>
Expand Down
Original file line number Diff line number Diff line change
@@ -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>
</>
);
};
1 change: 1 addition & 0 deletions examples/feature_flags_example/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,5 +20,6 @@
"@kbn/core-plugins-server",
"@kbn/config-schema",
"@kbn/developer-examples-plugin",
"@kbn/core-feature-flags-browser",
]
}
2 changes: 1 addition & 1 deletion packages/core/feature-flags/README.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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),
};
Expand Down

0 comments on commit d55ce92

Please sign in to comment.