Skip to content

Commit e972912

Browse files
feat(react): add FeatureFlags component for contextual feature flags (#8276)
* chore: check-in work * feat(react): add FeatureFlags component for contextual feature flags * Delete usePrevious.js Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
1 parent ecb1cab commit e972912

File tree

5 files changed

+479
-83
lines changed

5 files changed

+479
-83
lines changed
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
/**
2+
* Copyright IBM Corp. 2015, 2020
3+
*
4+
* This source code is licensed under the Apache-2.0 license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*/
7+
8+
export class FeatureFlagScope {
9+
constructor(flags) {
10+
this.flags = new Map();
11+
12+
if (flags) {
13+
Object.keys(flags).forEach((key) => {
14+
this.flags.set(key, flags[key]);
15+
});
16+
}
17+
}
18+
19+
/**
20+
* Check to see if a flag exists
21+
* @param {string} name
22+
*/
23+
checkForFlag(name) {
24+
if (!this.flags.has(name)) {
25+
throw new Error(
26+
`Unable to find a feature flag with the name: \`${name}\``
27+
);
28+
}
29+
}
30+
31+
/**
32+
* Add a feature flag
33+
* @param {string} name
34+
* @param {boolean} enabled
35+
*/
36+
add(name, enabled) {
37+
if (this.flags.has(name)) {
38+
throw new Error(`The feature flag: ${name} already exists`);
39+
}
40+
this.flags.set(name, enabled);
41+
}
42+
43+
/**
44+
* Enable a feature flag
45+
* @param {string} name
46+
*/
47+
enable(name) {
48+
this.checkForFlag(name);
49+
this.flags.set(name, true);
50+
}
51+
52+
/**
53+
* Disable a feature flag
54+
* @param {string} name
55+
*/
56+
disable(name) {
57+
this.checkForFlag(name);
58+
this.flags.set(name, false);
59+
}
60+
61+
/**
62+
* Merge the given feature flags with the current set of feature flags.
63+
* Duplicate keys will be set to the value in the given feature flags.
64+
* @param {object} flags
65+
*/
66+
merge(flags) {
67+
Object.keys(flags).forEach((key) => {
68+
this.flags.set(key, flags[key]);
69+
});
70+
}
71+
72+
/**
73+
* @param {FeatureFlagScope} scope
74+
*/
75+
mergeWithScope(scope) {
76+
for (const [key, value] of scope.flags) {
77+
if (this.flags.has(key)) {
78+
continue;
79+
}
80+
this.flags.set(key, value);
81+
}
82+
}
83+
84+
/**
85+
* Check if a feature flag is enabled
86+
* @param {string} name
87+
* @returns {boolean}
88+
*/
89+
enabled(name) {
90+
this.checkForFlag(name);
91+
return this.flags.get(name);
92+
}
93+
}

packages/feature-flags/src/__tests__/feature-flags-test.js

Lines changed: 30 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -6,99 +6,99 @@
66
*/
77

88
describe('@carbon/feature-flags', () => {
9-
let FeatureFlag;
9+
let FeatureFlags;
1010

1111
beforeEach(() => {
1212
jest.resetModules();
13-
FeatureFlag = require('../');
13+
FeatureFlags = require('../');
1414
});
1515

1616
describe('add', () => {
1717
it('should add the given flag and set whether its enabled', () => {
18-
FeatureFlag.add('flag-a', true);
19-
FeatureFlag.add('flag-b', false);
18+
FeatureFlags.add('flag-a', true);
19+
FeatureFlags.add('flag-b', false);
2020

21-
expect(FeatureFlag.enabled('flag-a')).toBe(true);
22-
expect(FeatureFlag.enabled('flag-b')).toBe(false);
21+
expect(FeatureFlags.enabled('flag-a')).toBe(true);
22+
expect(FeatureFlags.enabled('flag-b')).toBe(false);
2323
});
2424

2525
it('should throw if a duplicate flag name is given', () => {
26-
FeatureFlag.add('flag-a', true);
26+
FeatureFlags.add('flag-a', true);
2727

2828
expect(() => {
29-
FeatureFlag.add('flag-a', true);
29+
FeatureFlags.add('flag-a', true);
3030
}).toThrow();
3131
});
3232
});
3333

3434
describe('enable', () => {
3535
it('should enable the given feature flag', () => {
36-
FeatureFlag.add('flag-a', false);
37-
expect(FeatureFlag.enabled('flag-a')).toBe(false);
36+
FeatureFlags.add('flag-a', false);
37+
expect(FeatureFlags.enabled('flag-a')).toBe(false);
3838

39-
FeatureFlag.enable('flag-a');
40-
expect(FeatureFlag.enabled('flag-a')).toBe(true);
39+
FeatureFlags.enable('flag-a');
40+
expect(FeatureFlags.enabled('flag-a')).toBe(true);
4141
});
4242

4343
it('should throw if the given flag does not exist', () => {
4444
expect(() => {
45-
FeatureFlag.enable('flag-a');
45+
FeatureFlags.enable('flag-a');
4646
}).toThrow();
4747
});
4848
});
4949

5050
describe('disable', () => {
5151
it('should disable the given feature flag', () => {
52-
FeatureFlag.add('flag-a', true);
53-
expect(FeatureFlag.enabled('flag-a')).toBe(true);
52+
FeatureFlags.add('flag-a', true);
53+
expect(FeatureFlags.enabled('flag-a')).toBe(true);
5454

55-
FeatureFlag.disable('flag-a');
56-
expect(FeatureFlag.enabled('flag-a')).toBe(false);
55+
FeatureFlags.disable('flag-a');
56+
expect(FeatureFlags.enabled('flag-a')).toBe(false);
5757
});
5858

5959
it('should throw if the given flag does not exist', () => {
6060
expect(() => {
61-
FeatureFlag.disable('flag-a');
61+
FeatureFlags.disable('flag-a');
6262
}).toThrow();
6363
});
6464
});
6565

6666
describe('enabled', () => {
6767
it('should return whether a flag is enabled or disabled', () => {
68-
FeatureFlag.add('flag-a', true);
69-
FeatureFlag.add('flag-b', false);
68+
FeatureFlags.add('flag-a', true);
69+
FeatureFlags.add('flag-b', false);
7070

71-
expect(FeatureFlag.enabled('flag-a')).toBe(true);
72-
expect(FeatureFlag.enabled('flag-b')).toBe(false);
71+
expect(FeatureFlags.enabled('flag-a')).toBe(true);
72+
expect(FeatureFlags.enabled('flag-b')).toBe(false);
7373
});
7474

7575
it('should throw if the given flag does not exist', () => {
7676
expect(() => {
77-
FeatureFlag.enabled('flag-a');
77+
FeatureFlags.enabled('flag-a');
7878
}).toThrow();
7979
});
8080
});
8181

8282
describe('merge', () => {
8383
it('should set each feature flag given', () => {
84-
FeatureFlag.merge({
84+
FeatureFlags.merge({
8585
'flag-a': true,
8686
'flag-b': false,
8787
});
8888

89-
expect(FeatureFlag.enabled('flag-a')).toBe(true);
90-
expect(FeatureFlag.enabled('flag-b')).toBe(false);
89+
expect(FeatureFlags.enabled('flag-a')).toBe(true);
90+
expect(FeatureFlags.enabled('flag-b')).toBe(false);
9191
});
9292

9393
it('should override duplicate keys with the given flag', () => {
94-
FeatureFlag.add('flag-b', true);
95-
FeatureFlag.merge({
94+
FeatureFlags.add('flag-b', true);
95+
FeatureFlags.merge({
9696
'flag-a': true,
9797
'flag-b': false,
9898
});
9999

100-
expect(FeatureFlag.enabled('flag-a')).toBe(true);
101-
expect(FeatureFlag.enabled('flag-b')).toBe(false);
100+
expect(FeatureFlags.enabled('flag-a')).toBe(true);
101+
expect(FeatureFlags.enabled('flag-b')).toBe(false);
102102
});
103103
});
104104
});

packages/feature-flags/src/index.js

Lines changed: 17 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -6,73 +6,37 @@
66
*/
77

88
import { featureFlagInfo } from './generated/feature-flags';
9+
import { FeatureFlagScope } from './FeatureFlagScope';
910

10-
const featureFlags = new Map();
11+
const FeatureFlags = createScope();
1112

1213
for (let i = 0; i < featureFlagInfo.length; i++) {
1314
const featureFlag = featureFlagInfo[i];
14-
featureFlags.set(featureFlag.name, featureFlag.enabled);
15+
FeatureFlags.add(featureFlag.name, featureFlag.enabled);
1516
}
1617

17-
/**
18-
* Check to see if a flag exists
19-
* @param {string} name
20-
*/
21-
function checkForFlag(name) {
22-
if (!featureFlags.has(name)) {
23-
throw new Error(`Unable to find a feature flag with the name \`${name}\``);
24-
}
25-
}
18+
export { FeatureFlags };
2619

27-
/**
28-
* Add a feature flag
29-
* @param {string} name
30-
* @param {boolean} enabled
31-
*/
32-
export function add(name, enabled) {
33-
if (featureFlags.has(name)) {
34-
throw new Error(`The feature flag: ${name} already exists`);
35-
}
36-
featureFlags.set(name, enabled);
20+
export function createScope(flags) {
21+
return new FeatureFlagScope(flags);
3722
}
3823

39-
/**
40-
* Enable a feature flag
41-
* @param {string} name
42-
*/
43-
export function enable(name) {
44-
checkForFlag(name);
45-
featureFlags.set(name, true);
24+
export function add(...args) {
25+
return FeatureFlags.add(...args);
4626
}
4727

48-
/**
49-
* Disable a feature flag
50-
* @param {string} name
51-
*/
52-
export function disable(name) {
53-
checkForFlag(name);
54-
featureFlags.set(name, false);
28+
export function enable(...args) {
29+
return FeatureFlags.enable(...args);
5530
}
5631

57-
/**
58-
* Merge the given feature flags with the current set of feature flags.
59-
* Duplicate keys will be set to the value in the given feature flags.
60-
* @param {object} flags
61-
*/
62-
export function merge(flags) {
63-
Object.keys(flags).forEach((key) => {
64-
featureFlags.set(key, flags[key]);
65-
});
32+
export function disable(...args) {
33+
return FeatureFlags.disable(...args);
6634
}
6735

68-
/**
69-
* Check if a feature flag is enabled
70-
* @param {string} name
71-
* @returns {boolean}
72-
*/
73-
export function enabled(name) {
74-
checkForFlag(name);
75-
return featureFlags.get(name);
36+
export function enabled(...args) {
37+
return FeatureFlags.enabled(...args);
7638
}
7739

78-
export { featureFlagInfo as unstable_featureFlagInfo };
40+
export function merge(...args) {
41+
return FeatureFlags.merge(...args);
42+
}

0 commit comments

Comments
 (0)