Skip to content

Commit ed70302

Browse files
authored
Merge pull request #2459 from bugsnag/PLAT-13690-feature-flag-delegate
Convert @bugsnag/core/src/lib/feature-flag-delegate to TypeScript
2 parents b134a3f + c0bbe17 commit ed70302

11 files changed

Lines changed: 123 additions & 106 deletions

File tree

packages/core/package.json

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,7 @@
1313
"./lib/es-utils/includes": "./src/lib/es-utils/includes.js",
1414
"./lib/derecursify": "./src/lib/derecursify.js",
1515
"./lib/callback-runner": "./src/lib/callback-runner.js",
16-
"./lib/path-normalizer": "./src/lib/path-normalizer.js",
17-
"./lib/feature-flag-delegate": "./src/lib/feature-flag-delegate.js"
16+
"./lib/path-normalizer": "./src/lib/path-normalizer.js"
1817
},
1918
"description": "Core classes and utilities for Bugsnag notifiers",
2019
"homepage": "https://www.bugsnag.com/",

packages/core/src/client.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import runCallbacks from './lib/callback-runner'
88
import metadataDelegate from './lib/metadata-delegate'
99
import runSyncCallbacks from './lib/sync-callback-runner'
1010
import BREADCRUMB_TYPES from './lib/breadcrumb-types'
11-
import { add, clear, merge } from './lib/feature-flag-delegate'
11+
import featureFlagDelegate from './lib/feature-flag-delegate'
1212
import { BreadcrumbType, Config, Delivery, FeatureFlag, LoggerConfig, NotifiableError, Notifier, OnBreadcrumbCallback, OnErrorCallback, OnSessionCallback, Plugin, SessionDelegate, User } from './common'
1313

1414
const HUB_PREFIX = '00000'
@@ -127,15 +127,15 @@ export default class Client<T extends Config = Config> {
127127
}
128128

129129
addFeatureFlag (name: string, variant: string | null = null) {
130-
add(this._features, this._featuresIndex, name, variant)
130+
featureFlagDelegate.add(this._features, this._featuresIndex, name, variant)
131131
}
132132

133133
addFeatureFlags (featureFlags: FeatureFlag[]) {
134-
merge(this._features, featureFlags, this._featuresIndex)
134+
featureFlagDelegate.merge(this._features, featureFlags, this._featuresIndex)
135135
}
136136

137137
clearFeatureFlag (name: string) {
138-
clear(this._features, this._featuresIndex, name)
138+
featureFlagDelegate.clear(this._features, this._featuresIndex, name)
139139
}
140140

141141
clearFeatureFlags () {
@@ -205,7 +205,7 @@ export default class Client<T extends Config = Config> {
205205

206206
// update and elevate some options
207207
this._metadata = assign({}, config.metadata)
208-
merge(this._features, config.featureFlags, this._featuresIndex)
208+
featureFlagDelegate.merge(this._features, config.featureFlags, this._featuresIndex)
209209
this._user = assign({}, config.user)
210210
this._context = config.context
211211
if (config.logger) this._logger = config.logger
@@ -351,7 +351,7 @@ export default class Client<T extends Config = Config> {
351351
event._metadata = assign({}, event._metadata, this._metadata)
352352
event._user = assign({}, event._user, this._user)
353353
event.breadcrumbs = this._breadcrumbs.slice()
354-
merge(event._features, this._features, event._featuresIndex)
354+
featureFlagDelegate.merge(event._features, this._features, event._featuresIndex)
355355

356356
// exit early if events should not be sent on the current releaseStage
357357
if (this._config.enabledReleaseStages !== null && !includes(this._config.enabledReleaseStages, this._config.releaseStage)) {

packages/core/src/event.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ export default class Event {
3333
public threads: Thread[]
3434

3535
public _metadata: { [key: string]: any }
36-
public _features: FeatureFlag | null[]
36+
public _features: (FeatureFlag | null)[]
3737
public _featuresIndex: { [key: string]: number }
3838

3939
public _user: User

packages/core/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ export { default as listOfFunctions } from './lib/validators/list-of-functions'
1212
export { default as stringWithLength } from './lib/validators/string-with-length'
1313
export { default as metadataDelegate } from './lib/metadata-delegate'
1414
export { default as nodeFallbackStack } from './lib/node-fallback-stack'
15+
export { default as featureFlagDelegate } from './lib/feature-flag-delegate'
1516
export { default as runSyncCallbacks } from './lib/sync-callback-runner'
1617

1718
export * from './common'

packages/core/src/lib/feature-flag-delegate.js

Lines changed: 0 additions & 70 deletions
This file was deleted.
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
import isArray from './es-utils/is-array'
2+
import jsonStringify from '@bugsnag/safe-json-stringify'
3+
import {FeatureFlag} from "../common";
4+
5+
type FeatureFlagEventApi = {
6+
featureFlag: string
7+
variant?: string
8+
}
9+
10+
interface FeatureFlagDelegate{
11+
add: (existingFeatures: Array<FeatureFlag | null>, existingFeatureKeys: { [key: string]: number }, name?: string | null, variant?: any ) => void
12+
merge: (
13+
existingFeatures: Array<{ name: string; variant?: any } | null>,
14+
newFeatures: any,
15+
existingFeatureKeys: { [key: string]: any }
16+
) => Array<{ name: string; variant: any }> | undefined
17+
toEventApi: (featureFlags: Array<FeatureFlag | null>) => FeatureFlagEventApi[]
18+
clear: (features: (FeatureFlag | null)[], featuresIndex: { [key: string]: number }, name: string) => void
19+
}
20+
21+
const featureFlagDelegate: FeatureFlagDelegate = {
22+
add: (existingFeatures, existingFeatureKeys, name, variant) => {
23+
if (typeof name !== 'string') {
24+
return
25+
}
26+
27+
if (variant === undefined) {
28+
variant = null
29+
} else if (variant !== null && typeof variant !== 'string') {
30+
variant = jsonStringify(variant)
31+
}
32+
33+
const existingIndex = existingFeatureKeys[name]
34+
if (typeof existingIndex === 'number') {
35+
existingFeatures[existingIndex] = {name, variant}
36+
return
37+
}
38+
39+
existingFeatures.push({name, variant})
40+
existingFeatureKeys[name] = existingFeatures.length - 1
41+
},
42+
43+
merge: (existingFeatures, newFeatures, existingFeatureKeys) => {
44+
if (!isArray(newFeatures)) {
45+
return
46+
}
47+
48+
for (let i = 0; i < newFeatures.length; ++i) {
49+
const feature = newFeatures[i]
50+
51+
if (feature === null || typeof feature !== 'object') {
52+
continue
53+
}
54+
55+
// 'add' will handle if 'name' doesn't exist & 'variant' is optional
56+
featureFlagDelegate.add(existingFeatures, existingFeatureKeys, feature.name, feature.variant)
57+
}
58+
59+
// Remove any nulls from the array to match the return type
60+
return existingFeatures.filter(f => f) as Array<{ name: string; variant: any }>
61+
},
62+
63+
// convert feature flags from a map of 'name -> variant' into the format required
64+
// by the Bugsnag Event API:
65+
// [{ featureFlag: 'name', variant: 'variant' }, { featureFlag: 'name 2' }]
66+
toEventApi: (featureFlags) => {
67+
return (featureFlags || [])
68+
.filter((flag): flag is FeatureFlag => flag !== null && typeof flag === 'object')
69+
.map((flag) => {
70+
const result: FeatureFlagEventApi = { featureFlag: flag.name };
71+
if (typeof flag.variant === 'string') {
72+
result.variant = flag.variant;
73+
}
74+
return result;
75+
});
76+
},
77+
78+
clear: (features, featuresIndex, name) => {
79+
const existingIndex = featuresIndex[name]
80+
if (typeof existingIndex === 'number') {
81+
features[existingIndex] = null
82+
// eslint-disable-next-line @typescript-eslint/no-dynamic-delete
83+
delete featuresIndex[name]
84+
}
85+
}
86+
}
87+
88+
export default featureFlagDelegate

packages/core/test/lib/feature-flag-delegate.test.ts

Lines changed: 22 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
1-
import delegate from '../../src/lib/feature-flag-delegate'
1+
import featureFlagDelegate from '../../src/lib/feature-flag-delegate'
22

3-
describe('feature flag delegate', () => {
3+
describe('feature flag featureFlagDelegate', () => {
44
describe('#add', () => {
55
it('should do nothing if name is not passed', () => {
66
const existingFeatures = [{ name: 'abc', variant: 'xyz' }]
77
const existingFeaturesIndex = { abc: 0 }
88

9-
delegate.add(existingFeatures, existingFeaturesIndex)
9+
featureFlagDelegate.add(existingFeatures, existingFeaturesIndex)
1010

1111
expect(existingFeatures).toStrictEqual([{ name: 'abc', variant: 'xyz' }])
1212
expect(existingFeaturesIndex).toStrictEqual({ abc: 0 })
@@ -16,7 +16,7 @@ describe('feature flag delegate', () => {
1616
const existingFeatures = [{ name: 'abc', variant: 'xyz' }]
1717
const existingFeaturesIndex = { abc: 0 }
1818

19-
delegate.add(existingFeatures, existingFeaturesIndex, undefined, '???')
19+
featureFlagDelegate.add(existingFeatures, existingFeaturesIndex, undefined, '???')
2020

2121
expect(existingFeatures).toStrictEqual([{ name: 'abc', variant: 'xyz' }])
2222
expect(existingFeaturesIndex).toStrictEqual({ abc: 0 })
@@ -26,7 +26,7 @@ describe('feature flag delegate', () => {
2626
const existingFeatures = [{ name: 'abc', variant: 'xyz' }]
2727
const existingFeaturesIndex = { abc: 0 }
2828

29-
delegate.add(existingFeatures, existingFeaturesIndex, null, '???')
29+
featureFlagDelegate.add(existingFeatures, existingFeaturesIndex, null, '???')
3030

3131
expect(existingFeatures).toStrictEqual([{ name: 'abc', variant: 'xyz' }])
3232
expect(existingFeaturesIndex).toStrictEqual({ abc: 0 })
@@ -36,7 +36,7 @@ describe('feature flag delegate', () => {
3636
const existingFeatures: any[] = []
3737
const existingFeaturesIndex = {}
3838

39-
delegate.add(existingFeatures, existingFeaturesIndex, 'good_feature')
39+
featureFlagDelegate.add(existingFeatures, existingFeaturesIndex, 'good_feature')
4040

4141
expect(existingFeatures).toStrictEqual([{ name: 'good_feature', variant: null }])
4242
expect(existingFeaturesIndex).toStrictEqual({ good_feature: 0 })
@@ -46,7 +46,7 @@ describe('feature flag delegate', () => {
4646
const existingFeatures: [] = []
4747
const existingFeaturesIndex = {}
4848

49-
delegate.add(existingFeatures, existingFeaturesIndex, 'ok_feature', null)
49+
featureFlagDelegate.add(existingFeatures, existingFeaturesIndex, 'ok_feature', null)
5050

5151
expect(existingFeatures).toStrictEqual([{ name: 'ok_feature', variant: null }])
5252
expect(existingFeaturesIndex).toStrictEqual({ ok_feature: 0 })
@@ -56,7 +56,7 @@ describe('feature flag delegate', () => {
5656
const existingFeatures: any[] = []
5757
const existingFeaturesIndex = {}
5858

59-
delegate.add(existingFeatures, existingFeaturesIndex, 'cool_feature', 'very ant')
59+
featureFlagDelegate.add(existingFeatures, existingFeaturesIndex, 'cool_feature', 'very ant')
6060

6161
expect(existingFeatures).toStrictEqual([{ name: 'cool_feature', variant: 'very ant' }])
6262
expect(existingFeaturesIndex).toStrictEqual({ cool_feature: 0 })
@@ -75,7 +75,7 @@ describe('feature flag delegate', () => {
7575
const existingFeatures: any[] = []
7676
const existingFeaturesIndex = {}
7777

78-
delegate.add(existingFeatures, existingFeaturesIndex, 'some_feature', variant)
78+
featureFlagDelegate.add(existingFeatures, existingFeaturesIndex, 'some_feature', variant)
7979

8080
expect(existingFeatures).toStrictEqual([{ name: 'some_feature', variant: expected }])
8181
expect(existingFeaturesIndex).toStrictEqual({ some_feature: 0 })
@@ -85,9 +85,9 @@ describe('feature flag delegate', () => {
8585
const existingFeatures = [{ name: 'a', variant: 'a' }, { name: 'b', variant: 'b' }, { name: 'c', variant: 'c' }, { name: 'd', variant: 'd' }, { name: 'e', variant: 'e' }]
8686
const existingFeaturesIndex = { a: 0, b: 1, c: 2, d: 3, e: 4 }
8787

88-
delegate.add(existingFeatures, existingFeaturesIndex, 'b', 'x')
89-
delegate.add(existingFeatures, existingFeaturesIndex, 'e', 'y')
90-
delegate.add(existingFeatures, existingFeaturesIndex, 'a', 'z')
88+
featureFlagDelegate.add(existingFeatures, existingFeaturesIndex, 'b', 'x')
89+
featureFlagDelegate.add(existingFeatures, existingFeaturesIndex, 'e', 'y')
90+
featureFlagDelegate.add(existingFeatures, existingFeaturesIndex, 'a', 'z')
9191

9292
expect(existingFeatures).toStrictEqual([{ name: 'a', variant: 'z' }, { name: 'b', variant: 'x' }, { name: 'c', variant: 'c' }, { name: 'd', variant: 'd' }, { name: 'e', variant: 'y' }])
9393
expect(existingFeaturesIndex).toStrictEqual({ a: 0, b: 1, c: 2, d: 3, e: 4 })
@@ -99,7 +99,7 @@ describe('feature flag delegate', () => {
9999
const existingFeatures: any[] = []
100100
const existingFeaturesIndex = {}
101101

102-
delegate.merge(existingFeatures, [
102+
featureFlagDelegate.merge(existingFeatures, [
103103
{ name: 'a', variant: 'b' },
104104
{ name: 'c', variant: 'd' }
105105
], existingFeaturesIndex)
@@ -112,7 +112,7 @@ describe('feature flag delegate', () => {
112112
const existingFeatures = [{ name: 'a', variant: 'a' }, { name: 'b', variant: 'b' }, { name: 'c', variant: 'c' }, { name: 'd', variant: 'd' }, { name: 'e', variant: 'e' }]
113113
const existingFeaturesIndex = { a: 0, b: 1, c: 2, d: 3, e: 4 }
114114

115-
delegate.merge(existingFeatures, [
115+
featureFlagDelegate.merge(existingFeatures, [
116116
{ name: 'b', variant: 'x' },
117117
{ name: 'e', variant: 'y' },
118118
{ name: 'a', variant: 'z' }
@@ -143,7 +143,7 @@ describe('feature flag delegate', () => {
143143
{ name: 'p', variant: 'p' }
144144
]
145145

146-
delegate.merge(existingFeatures, [
146+
featureFlagDelegate.merge(existingFeatures, [
147147
{ name: 'a', variant: 12345 },
148148
{ name: 'c', variant: 0 },
149149
{ name: 'e', variant: true },
@@ -183,7 +183,7 @@ describe('feature flag delegate', () => {
183183
const existingFeatures = [{ name: 'a', variant: 'a' }, { name: 'b', variant: 'b' }, { name: 'c', variant: 'c' }, { name: 'd', variant: 'd' }, { name: 'e', variant: 'e' }]
184184
const existingFeaturesIndex = { a: 0, b: 1, c: 2, d: 3, e: 3 }
185185

186-
delegate.merge(existingFeatures, [
186+
featureFlagDelegate.merge(existingFeatures, [
187187
{ name: 'b', variant: 'x' },
188188
{ variant: 'y' },
189189
{ name: 'a', variant: 'z' },
@@ -200,7 +200,7 @@ describe('feature flag delegate', () => {
200200
const existingFeatures = [{ name: 'a', variant: 'a' }, { name: 'b', variant: 'b' }, { name: 'c', variant: 'c' }, { name: 'd', variant: 'd' }, { name: 'e', variant: 'e' }]
201201
const existingFeaturesIndex = { a: 'a', b: 'b', c: 'c', d: 'd', e: 'e' }
202202

203-
delegate.merge(existingFeatures, { a: 'a', b: 'b', c: 'c' }, existingFeaturesIndex)
203+
featureFlagDelegate.merge(existingFeatures, { a: 'a', b: 'b', c: 'c' }, existingFeaturesIndex)
204204

205205
expect(existingFeatures).toStrictEqual([{ name: 'a', variant: 'a' }, { name: 'b', variant: 'b' }, { name: 'c', variant: 'c' }, { name: 'd', variant: 'd' }, { name: 'e', variant: 'e' }])
206206
expect(existingFeaturesIndex).toStrictEqual({ a: 'a', b: 'b', c: 'c', d: 'd', e: 'e' })
@@ -210,7 +210,7 @@ describe('feature flag delegate', () => {
210210
const features = [{ name: 'a', variant: 'a' }, { name: 'b', variant: 'b' }, { name: 'c', variant: 'c' }, { name: 'd', variant: 'd' }, { name: 'e', variant: 'e' }]
211211
const featuresIndex = { a: 0, b: 1, c: 2, d: 3, e: 4 }
212212

213-
delegate.merge(features, [
213+
featureFlagDelegate.merge(features, [
214214
{ name: 'b', variant: 'x' },
215215
'name: yes',
216216
undefined,
@@ -230,16 +230,16 @@ describe('feature flag delegate', () => {
230230
const features: any[] = []
231231
const featuresIndex = {}
232232

233-
delegate.add(features, featuresIndex, 'a', 'b')
234-
delegate.merge(features, [
233+
featureFlagDelegate.add(features, featuresIndex, 'a', 'b')
234+
featureFlagDelegate.merge(features, [
235235
{ name: 'c', variant: 'd' },
236236
{ name: 'e' },
237237
{ name: 'f', variant: 'g' }
238238
], featuresIndex)
239239

240240
expect(features).toStrictEqual([{ name: 'a', variant: 'b' }, { name: 'c', variant: 'd' }, { name: 'e', variant: null }, { name: 'f', variant: 'g' }])
241241

242-
expect(delegate.toEventApi(features)).toStrictEqual([
242+
expect(featureFlagDelegate.toEventApi(features)).toStrictEqual([
243243
{ featureFlag: 'a', variant: 'b' },
244244
{ featureFlag: 'c', variant: 'd' },
245245
{ featureFlag: 'e' },
@@ -248,7 +248,7 @@ describe('feature flag delegate', () => {
248248
})
249249

250250
it('should handle an empty array', () => {
251-
expect(delegate.toEventApi([])).toStrictEqual([])
251+
expect(featureFlagDelegate.toEventApi([])).toStrictEqual([])
252252
})
253253
})
254254
})

0 commit comments

Comments
 (0)