[Feature Flags Service] Hello world 👋 (#188562)

Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
Co-authored-by: Jean-Louis Leysens <jloleysens@gmail.com>
This commit is contained in:
Alejandro Fernández Haro 2024-09-18 18:02:55 +02:00 committed by GitHub
parent 38d6143f72
commit 02ce1b9101
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
164 changed files with 3605 additions and 1941 deletions

View file

@ -0,0 +1,5 @@
# featureFlagsExample
This plugin's goal is to demonstrate how to use the core feature flags service.
Refer to [the docs](../../packages/core/feature-flags/README.mdx) to know more.

View file

@ -0,0 +1,12 @@
/*
* 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".
*/
export const FeatureFlagExampleBoolean = 'example-boolean';
export const FeatureFlagExampleString = 'example-string';
export const FeatureFlagExampleNumber = 'example-number';

View file

@ -0,0 +1,11 @@
/*
* 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".
*/
export const PLUGIN_ID = 'featureFlagsExample';
export const PLUGIN_NAME = 'Feature Flags Example';

View file

@ -0,0 +1,13 @@
{
"type": "plugin",
"id": "@kbn/feature-flags-example-plugin",
"owner": "@elastic/kibana-core",
"description": "Plugin that shows how to make use of the feature flags core service.",
"plugin": {
"id": "featureFlagsExample",
"server": true,
"browser": true,
"requiredPlugins": ["developerExamples"],
"optionalPlugins": []
}
}

View file

@ -0,0 +1,33 @@
/*
* 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 React from 'react';
import ReactDOM from 'react-dom';
import { AppMountParameters, CoreStart } from '@kbn/core/public';
import { KibanaPageTemplate } from '@kbn/shared-ux-page-kibana-template';
import { KibanaRootContextProvider } from '@kbn/react-kibana-context-root';
import { FeatureFlagsExampleApp } from './components/app';
export const renderApp = (coreStart: CoreStart, { element }: AppMountParameters) => {
const { notifications, http, featureFlags } = coreStart;
ReactDOM.render(
<KibanaRootContextProvider {...coreStart}>
<KibanaPageTemplate>
<FeatureFlagsExampleApp
featureFlags={featureFlags}
notifications={notifications}
http={http}
/>
</KibanaPageTemplate>
</KibanaRootContextProvider>,
element
);
return () => ReactDOM.unmountComponentAtNode(element);
};

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 React from 'react';
import {
EuiHorizontalRule,
EuiPageTemplate,
EuiTitle,
EuiText,
EuiLink,
EuiListGroup,
EuiListGroupItem,
} 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';
interface FeatureFlagsExampleAppDeps {
featureFlags: FeatureFlagsStart;
notifications: CoreStart['notifications'];
http: CoreStart['http'];
}
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>
<EuiPageTemplate.Header>
<EuiTitle size="l">
<h1>{PLUGIN_NAME}</h1>
</EuiTitle>
</EuiPageTemplate.Header>
<EuiPageTemplate.Section>
<EuiTitle>
<h2>Demo of the feature flags service</h2>
</EuiTitle>
<EuiText>
<p>
To learn more, refer to{' '}
<EuiLink
href={'https://docs.elastic.dev/kibana-dev-docs/tutorials/feature-flags-service'}
>
the docs
</EuiLink>
.
</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>
</EuiText>
</EuiPageTemplate.Section>
</EuiPageTemplate>
</>
);
};

View file

@ -0,0 +1,14 @@
/*
* 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 { FeatureFlagsExamplePlugin } from './plugin';
export function plugin() {
return new FeatureFlagsExamplePlugin();
}

View file

@ -0,0 +1,40 @@
/*
* 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 { AppMountParameters, CoreSetup, CoreStart, Plugin } from '@kbn/core/public';
import { AppPluginSetupDependencies } from './types';
import { PLUGIN_NAME } from '../common';
export class FeatureFlagsExamplePlugin implements Plugin {
public setup(core: CoreSetup, deps: AppPluginSetupDependencies) {
// Register an application into the side navigation menu
core.application.register({
id: 'featureFlagsExample',
title: PLUGIN_NAME,
async mount(params: AppMountParameters) {
// Load application bundle
const { renderApp } = await import('./application');
// Get start services as specified in kibana.json
const [coreStart] = await core.getStartServices();
// Render the application
return renderApp(coreStart, params);
},
});
deps.developerExamples.register({
appId: 'featureFlagsExample',
title: PLUGIN_NAME,
description: 'Plugin that shows how to make use of the feature flags core service.',
});
}
public start(core: CoreStart) {}
public stop() {}
}

View file

@ -0,0 +1,14 @@
/*
* 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 type { DeveloperExamplesSetup } from '@kbn/developer-examples-plugin/public';
export interface AppPluginSetupDependencies {
developerExamples: DeveloperExamplesSetup;
}

View file

@ -0,0 +1,77 @@
/*
* 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 type { FeatureFlagDefinitions } from '@kbn/core-feature-flags-server';
import type { PluginInitializerContext } from '@kbn/core-plugins-server';
import {
FeatureFlagExampleBoolean,
FeatureFlagExampleNumber,
FeatureFlagExampleString,
} from '../common/feature_flags';
export const featureFlags: FeatureFlagDefinitions = [
{
key: FeatureFlagExampleBoolean,
name: 'Example boolean',
description: 'This is a demo of a boolean flag',
tags: ['example', 'my-plugin'],
variationType: 'boolean',
variations: [
{
name: 'On',
description: 'Auto-hides the bar',
value: true,
},
{
name: 'Off',
description: 'Static always-on',
value: false,
},
],
},
{
key: FeatureFlagExampleString,
name: 'Example string',
description: 'This is a demo of a string flag',
tags: ['example', 'my-plugin'],
variationType: 'string',
variations: [
{
name: 'Pink',
value: '#D75489',
},
{
name: 'Turquoise',
value: '#65BAAF',
},
],
},
{
key: FeatureFlagExampleNumber,
name: 'Example Number',
description: 'This is a demo of a number flag',
tags: ['example', 'my-plugin'],
variationType: 'number',
variations: [
{
name: 'Five',
value: 5,
},
{
name: 'Ten',
value: 10,
},
],
},
];
export async function plugin(initializerContext: PluginInitializerContext) {
const { FeatureFlagsExamplePlugin } = await import('./plugin');
return new FeatureFlagsExamplePlugin(initializerContext);
}

View file

@ -0,0 +1,69 @@
/*
* 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 type {
PluginInitializerContext,
CoreSetup,
CoreStart,
Plugin,
Logger,
} from '@kbn/core/server';
import { combineLatest } from 'rxjs';
import {
FeatureFlagExampleBoolean,
FeatureFlagExampleNumber,
FeatureFlagExampleString,
} from '../common/feature_flags';
import { defineRoutes } from './routes';
export class FeatureFlagsExamplePlugin implements Plugin {
private readonly logger: Logger;
constructor(initializerContext: PluginInitializerContext) {
this.logger = initializerContext.logger.get();
}
public setup(core: CoreSetup) {
const router = core.http.createRouter();
// Register server side APIs
defineRoutes(router);
}
public start(core: CoreStart) {
// Promise form: when we need to fetch it once, like in an HTTP request
void Promise.all([
core.featureFlags.getBooleanValue(FeatureFlagExampleBoolean, false),
core.featureFlags.getStringValue(FeatureFlagExampleString, 'white'),
core.featureFlags.getNumberValue(FeatureFlagExampleNumber, 1),
]).then(([bool, str, num]) => {
this.logger.info(`The feature flags are:
- ${FeatureFlagExampleBoolean}: ${bool}
- ${FeatureFlagExampleString}: ${str}
- ${FeatureFlagExampleNumber}: ${num}
`);
});
// Observable form: when we need to react to the changes
combineLatest([
core.featureFlags.getBooleanValue$(FeatureFlagExampleBoolean, false),
core.featureFlags.getStringValue$(FeatureFlagExampleString, 'red'),
core.featureFlags.getNumberValue$(FeatureFlagExampleNumber, 1),
]).subscribe(([bool, str, num]) => {
this.logger.info(`The observed feature flags are:
- ${FeatureFlagExampleBoolean}: ${bool}
- ${FeatureFlagExampleString}: ${str}
- ${FeatureFlagExampleNumber}: ${num}
`);
});
}
public stop() {}
}

View file

@ -0,0 +1,44 @@
/*
* 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 type { IRouter } from '@kbn/core/server';
import { schema } from '@kbn/config-schema';
import { FeatureFlagExampleNumber } from '../../common/feature_flags';
export function defineRoutes(router: IRouter) {
router.versioned
.get({
path: '/api/feature_flags_example/example',
access: 'public',
})
.addVersion(
{
version: '2023-10-31',
validate: {
response: {
200: {
body: () =>
schema.object({
number: schema.number(),
}),
},
},
},
},
async (context, request, response) => {
const { featureFlags } = await context.core;
return response.ok({
body: {
number: await featureFlags.getNumberValue(FeatureFlagExampleNumber, 1),
},
});
}
);
}

View file

@ -0,0 +1,24 @@
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"outDir": "target/types"
},
"include": [
"index.ts",
"common/**/*.ts",
"public/**/*.ts",
"public/**/*.tsx",
"server/**/*.ts",
"../../typings/**/*"
],
"exclude": ["target/**/*"],
"kbn_references": [
"@kbn/core",
"@kbn/shared-ux-page-kibana-template",
"@kbn/react-kibana-context-root",
"@kbn/core-feature-flags-server",
"@kbn/core-plugins-server",
"@kbn/config-schema",
"@kbn/developer-examples-plugin",
]
}