From fe59dd48c7bb817f5e6bbe0ae70652c771fa445a Mon Sep 17 00:00:00 2001 From: Christos Nasikas Date: Thu, 13 Jun 2024 13:11:32 +0300 Subject: [PATCH] [ResponseOps] Package for feature flags (#185000) ## Summary This PR creates a package to expose tools for managing feature flags in the ResponseOps codebase. Usage: ``` const featureFlagService = createFeatureFlagService(['test.myFeature', 'test.myFeature.subFeature']); if (featureFlagService.isFeatureFlagSet('test.myFeature')) { // my feature code } ``` The code is typed so if you do `featureFlagService.isFeatureFlagSet('foo')` and `foo` is not part of the feature flags set TS will report an error. ### Checklist Delete any items that are not applicable to this PR. - [x] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios ### For maintainers - [x] This was checked for breaking API changes and was [labeled appropriately](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process) --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> --- .github/CODEOWNERS | 1 + package.json | 1 + .../feature_flag_service/README.md | 27 +++++++++++++++++ .../feature_flag_service.test.ts | 29 +++++++++++++++++++ .../feature_flag_service.ts | 25 ++++++++++++++++ .../feature_flag_service/index.ts | 10 +++++++ .../feature_flag_service/jest.config.js | 13 +++++++++ .../feature_flag_service/kibana.jsonc | 5 ++++ .../feature_flag_service/package.json | 6 ++++ .../feature_flag_service/tsconfig.json | 17 +++++++++++ tsconfig.base.json | 2 ++ yarn.lock | 4 +++ 12 files changed, 140 insertions(+) create mode 100644 packages/response-ops/feature_flag_service/README.md create mode 100644 packages/response-ops/feature_flag_service/feature_flag_service.test.ts create mode 100644 packages/response-ops/feature_flag_service/feature_flag_service.ts create mode 100644 packages/response-ops/feature_flag_service/index.ts create mode 100644 packages/response-ops/feature_flag_service/jest.config.js create mode 100644 packages/response-ops/feature_flag_service/kibana.jsonc create mode 100644 packages/response-ops/feature_flag_service/package.json create mode 100644 packages/response-ops/feature_flag_service/tsconfig.json diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index adcf03b5c653c9..5f2e9ed4992d4b 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -681,6 +681,7 @@ packages/kbn-reporting/server @elastic/appex-sharedux packages/kbn-resizable-layout @elastic/kibana-data-discovery examples/resizable_layout_examples @elastic/kibana-data-discovery x-pack/test/plugin_functional/plugins/resolver_test @elastic/security-solution +packages/response-ops/feature_flag_service @elastic/response-ops examples/response_stream @elastic/ml-ui packages/kbn-rison @elastic/kibana-operations x-pack/plugins/rollup @elastic/kibana-management diff --git a/package.json b/package.json index d549d412218119..ccf6429b22738b 100644 --- a/package.json +++ b/package.json @@ -692,6 +692,7 @@ "@kbn/resizable-layout": "link:packages/kbn-resizable-layout", "@kbn/resizable-layout-examples-plugin": "link:examples/resizable_layout_examples", "@kbn/resolver-test-plugin": "link:x-pack/test/plugin_functional/plugins/resolver_test", + "@kbn/response-ops-feature-flag-service": "link:packages/response-ops/feature_flag_service", "@kbn/response-stream-plugin": "link:examples/response_stream", "@kbn/rison": "link:packages/kbn-rison", "@kbn/rollup-plugin": "link:x-pack/plugins/rollup", diff --git a/packages/response-ops/feature_flag_service/README.md b/packages/response-ops/feature_flag_service/README.md new file mode 100644 index 00000000000000..e1f2cc2d9e3870 --- /dev/null +++ b/packages/response-ops/feature_flag_service/README.md @@ -0,0 +1,27 @@ +# @kbn/response-ops-feature-flags + +This packages exposes a feature flag service that is used in the ResponseOps plugins and packages to handle feature flags. + +## Usage + +### Create feature flag service + +``` +const featureFlagService = createFeatureFlagService(['test.myFeature', 'test.myFeature.subFeature']); // TS will infer the types automatically +``` + +or + +``` +type FeatureFlagValues = 'test.myFeature' | 'test.myOtherFeature' +const featureFlagService = createFeatureFlagService(['test.myFeature']); +``` + +### Checking the existence of a feature flag +``` +const featureFlagService = createFeatureFlagService(['test.myFeature', 'test.myFeature.subFeature']); + +if (featureFlagService.isFeatureFlagSet('test.myFeature')) { + // my feature code +} +``` \ No newline at end of file diff --git a/packages/response-ops/feature_flag_service/feature_flag_service.test.ts b/packages/response-ops/feature_flag_service/feature_flag_service.test.ts new file mode 100644 index 00000000000000..55ccc64ffdd9e4 --- /dev/null +++ b/packages/response-ops/feature_flag_service/feature_flag_service.test.ts @@ -0,0 +1,29 @@ +/* + * 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 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 or the Server + * Side Public License, v 1. + */ + +import { createFeatureFlagService } from './feature_flag_service'; + +type FeatureFlagValues = 'test.myFeature' | 'test.myOtherFeature'; + +describe('FeatureFlagService', () => { + it('returns true if the feature exists', () => { + const featureFlagService = createFeatureFlagService(['test.myFeature']); + expect(featureFlagService.isFeatureFlagSet('test.myFeature')).toBe(true); + }); + + it('returns false if the feature does not exist', () => { + const featureFlagService = createFeatureFlagService(['test.myFeature']); + // @ts-expect-error: foo is not part of the valid feature flags + expect(featureFlagService.isFeatureFlagSet('foo')).toBe(false); + }); + + it('returns true if the feature exists (as typed)', () => { + const featureFlagService = createFeatureFlagService(['test.myFeature']); + expect(featureFlagService.isFeatureFlagSet('test.myFeature')).toBe(true); + }); +}); diff --git a/packages/response-ops/feature_flag_service/feature_flag_service.ts b/packages/response-ops/feature_flag_service/feature_flag_service.ts new file mode 100644 index 00000000000000..857378d75341ff --- /dev/null +++ b/packages/response-ops/feature_flag_service/feature_flag_service.ts @@ -0,0 +1,25 @@ +/* + * 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 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 or the Server + * Side Public License, v 1. + */ + +class FeatureFlag { + private readonly featureFlags = new Set(); + + constructor(featureFlags: T[]) { + featureFlags.forEach((featureFlag) => this.featureFlags.add(featureFlag)); + } + + public isFeatureFlagSet(featureFlag: T): boolean { + return this.featureFlags.has(featureFlag); + } +} + +export const createFeatureFlagService = (featureFlags: T[]) => { + return new FeatureFlag(featureFlags); +}; + +export type FeatureFlagService = InstanceType>; diff --git a/packages/response-ops/feature_flag_service/index.ts b/packages/response-ops/feature_flag_service/index.ts new file mode 100644 index 00000000000000..8677b72deb0450 --- /dev/null +++ b/packages/response-ops/feature_flag_service/index.ts @@ -0,0 +1,10 @@ +/* + * 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 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 or the Server + * Side Public License, v 1. + */ + +export { createFeatureFlagService } from './feature_flag_service'; +export type { FeatureFlagService } from './feature_flag_service'; diff --git a/packages/response-ops/feature_flag_service/jest.config.js b/packages/response-ops/feature_flag_service/jest.config.js new file mode 100644 index 00000000000000..c85570b29dff2c --- /dev/null +++ b/packages/response-ops/feature_flag_service/jest.config.js @@ -0,0 +1,13 @@ +/* + * 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 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 or the Server + * Side Public License, v 1. + */ + +module.exports = { + preset: '@kbn/test/jest_node', + rootDir: '../../..', + roots: ['/packages/response-ops/feature_flag_service'], +}; diff --git a/packages/response-ops/feature_flag_service/kibana.jsonc b/packages/response-ops/feature_flag_service/kibana.jsonc new file mode 100644 index 00000000000000..9d52b84f64ec24 --- /dev/null +++ b/packages/response-ops/feature_flag_service/kibana.jsonc @@ -0,0 +1,5 @@ +{ + "type": "shared-common", + "id": "@kbn/response-ops-feature-flag-service", + "owner": "@elastic/response-ops" +} diff --git a/packages/response-ops/feature_flag_service/package.json b/packages/response-ops/feature_flag_service/package.json new file mode 100644 index 00000000000000..6ad0848efbdca7 --- /dev/null +++ b/packages/response-ops/feature_flag_service/package.json @@ -0,0 +1,6 @@ +{ + "name": "@kbn/response-ops-feature-flag-service", + "private": true, + "version": "1.0.0", + "license": "SSPL-1.0 OR Elastic License 2.0" +} diff --git a/packages/response-ops/feature_flag_service/tsconfig.json b/packages/response-ops/feature_flag_service/tsconfig.json new file mode 100644 index 00000000000000..6d27b06d5f8ba8 --- /dev/null +++ b/packages/response-ops/feature_flag_service/tsconfig.json @@ -0,0 +1,17 @@ +{ + "extends": "../../../tsconfig.base.json", + "compilerOptions": { + "outDir": "target/types", + "types": [ + "jest", + "node" + ] + }, + "include": [ + "**/*.ts", + ], + "exclude": [ + "target/**/*" + ], + "kbn_references": [] +} diff --git a/tsconfig.base.json b/tsconfig.base.json index 31f05377e4d4c7..cd3deff3a294ed 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -1356,6 +1356,8 @@ "@kbn/resizable-layout-examples-plugin/*": ["examples/resizable_layout_examples/*"], "@kbn/resolver-test-plugin": ["x-pack/test/plugin_functional/plugins/resolver_test"], "@kbn/resolver-test-plugin/*": ["x-pack/test/plugin_functional/plugins/resolver_test/*"], + "@kbn/response-ops-feature-flag-service": ["packages/response-ops/feature_flag_service"], + "@kbn/response-ops-feature-flag-service/*": ["packages/response-ops/feature_flag_service/*"], "@kbn/response-stream-plugin": ["examples/response_stream"], "@kbn/response-stream-plugin/*": ["examples/response_stream/*"], "@kbn/rison": ["packages/kbn-rison"], diff --git a/yarn.lock b/yarn.lock index 19f93bdff56403..27e604d8f4b6ae 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5880,6 +5880,10 @@ version "0.0.0" uid "" +"@kbn/response-ops-feature-flag-service@link:packages/response-ops/feature_flag_service": + version "0.0.0" + uid "" + "@kbn/response-stream-plugin@link:examples/response_stream": version "0.0.0" uid ""