Skip to content

Commit 62a27df

Browse files
committedAug 30, 2022
refactor feature flags
1 parent a129ec4 commit 62a27df

File tree

18 files changed

+261
-202
lines changed

18 files changed

+261
-202
lines changed
 

‎packages/core/client.d.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Client, OnErrorCallback, Config, Breadcrumb, Session, OnSessionCallback, OnBreadcrumbCallback, Plugin, Device, App, User } from './types'
1+
import { Client, OnErrorCallback, Config, Breadcrumb, Session, OnSessionCallback, OnBreadcrumbCallback, Plugin, Device, App, User, FeatureFlag } from './types'
22
import EventWithInternals from './event'
33

44
interface LoggerConfig {
@@ -43,14 +43,14 @@ export default class ClientWithInternals<T extends Config = Config> extends Clie
4343
_config: T
4444
_depth: number
4545
_logger: LoggerConfig
46-
_breadcrumbs: Breadcrumb[];
46+
_breadcrumbs: Breadcrumb[]
4747
_delivery: Delivery
4848
_setDelivery: (handler: (client: Client) => Delivery) => void
4949

5050
_user: User
5151

5252
_metadata: { [key: string]: any }
53-
_features: { [key: string]: string | null }
53+
_features: FeatureFlag | null[]
5454

5555
startSession(): ClientWithInternals
5656
resumeSession(): ClientWithInternals

‎packages/core/client.js

+10-8
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,10 @@ const reduce = require('./lib/es-utils/reduce')
99
const keys = require('./lib/es-utils/keys')
1010
const assign = require('./lib/es-utils/assign')
1111
const runCallbacks = require('./lib/callback-runner')
12-
const featureFlagDelegate = require('./lib/feature-flag-delegate')
1312
const metadataDelegate = require('./lib/metadata-delegate')
1413
const runSyncCallbacks = require('./lib/sync-callback-runner')
1514
const BREADCRUMB_TYPES = require('./lib/breadcrumb-types')
15+
const { add, clear, merge } = require('./lib/feature-flag-delegate')
1616

1717
const noop = () => {}
1818

@@ -36,7 +36,8 @@ class Client {
3636
this._breadcrumbs = []
3737
this._session = null
3838
this._metadata = {}
39-
this._features = {}
39+
this._featuresIndex = {}
40+
this._features = []
4041
this._context = undefined
4142
this._user = {}
4243

@@ -90,19 +91,20 @@ class Client {
9091
}
9192

9293
addFeatureFlag (name, variant = null) {
93-
featureFlagDelegate.add(this._features, name, variant)
94+
add(this._features, this._featuresIndex, name, variant)
9495
}
9596

9697
addFeatureFlags (featureFlags) {
97-
featureFlagDelegate.merge(this._features, featureFlags)
98+
merge(this._features, featureFlags, this._featuresIndex)
9899
}
99100

100101
clearFeatureFlag (name) {
101-
delete this._features[name]
102+
clear(this._features, this._featuresIndex, name)
102103
}
103104

104105
clearFeatureFlags () {
105-
this._features = {}
106+
this._features = []
107+
this._featuresIndex = {}
106108
}
107109

108110
getContext () {
@@ -151,7 +153,7 @@ class Client {
151153

152154
// update and elevate some options
153155
this._metadata = assign({}, config.metadata)
154-
featureFlagDelegate.merge(this._features, config.featureFlags)
156+
merge(this._features, config.featureFlags, this._featuresIndex)
155157
this._user = assign({}, config.user)
156158
this._context = config.context
157159
if (config.logger) this._logger = config.logger
@@ -295,9 +297,9 @@ class Client {
295297
})
296298
event.context = event.context || this._context
297299
event._metadata = assign({}, event._metadata, this._metadata)
298-
event._features = assign({}, event._features, this._features)
299300
event._user = assign({}, event._user, this._user)
300301
event.breadcrumbs = this._breadcrumbs.slice()
302+
merge(event._features, this._features, event._featuresIndex)
301303

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

‎packages/core/event.d.ts

+3-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { App, Device, Event, Request, Breadcrumb, User, Session } from './types'
1+
import { App, Device, Event, Request, Breadcrumb, User, Session, FeatureFlag } from './types'
22
import { Error } from './types/event'
33

44
interface HandledState {
@@ -21,7 +21,8 @@ interface FeatureFlagPayload {
2121
export default class EventWithInternals extends Event {
2222
constructor (errorClass: string, errorMessage: string, stacktrace: any[], handledState?: HandledState, originalError?: Error)
2323
_metadata: { [key: string]: any }
24-
_features: { [key: string]: string | null }
24+
_features: FeatureFlag | null[]
25+
_featuresIndex: { [key: string]: number }
2526
_user: User
2627
_handledState: HandledState
2728
_session?: Session

‎packages/core/event.js

+7-5
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,8 @@ class Event {
2828
this.threads = []
2929

3030
this._metadata = {}
31-
this._features = {}
31+
this._features = []
32+
this._featuresIndex = {}
3233
this._user = {}
3334
this._session = undefined
3435

@@ -56,19 +57,20 @@ class Event {
5657
}
5758

5859
addFeatureFlag (name, variant = null) {
59-
featureFlagDelegate.add(this._features, name, variant)
60+
featureFlagDelegate.add(this._features, this._featuresIndex, name, variant)
6061
}
6162

6263
addFeatureFlags (featureFlags) {
63-
featureFlagDelegate.merge(this._features, featureFlags)
64+
featureFlagDelegate.merge(this._features, featureFlags, this._featuresIndex)
6465
}
6566

6667
clearFeatureFlag (name) {
67-
delete this._features[name]
68+
featureFlagDelegate.clear(this._features, this._featuresIndex, name)
6869
}
6970

7071
clearFeatureFlags () {
71-
this._features = {}
72+
this._features = []
73+
this._featuresIndex = {}
7274
}
7375

7476
getUser () {

‎packages/core/lib/clone-client.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@ module.exports = (client) => {
99
// so ensure they are are (shallow) cloned
1010
clone._breadcrumbs = client._breadcrumbs.slice()
1111
clone._metadata = assign({}, client._metadata)
12-
clone._features = assign({}, client._features)
12+
clone._features = [...client._features]
13+
clone._featuresIndex = assign({}, client._featuresIndex)
1314
clone._user = assign({}, client._user)
1415
clone._context = client._context
1516

+27-10
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
const map = require('./es-utils/map')
2-
const keys = require('./es-utils/keys')
2+
const filter = require('./es-utils/filter')
33
const isArray = require('./es-utils/is-array')
44
const jsonStringify = require('@bugsnag/safe-json-stringify')
55

6-
function add (existingFeatures, name, variant) {
6+
function add (existingFeatures, existingFeatureKeys, name, variant) {
77
if (typeof name !== 'string') {
88
return
99
}
@@ -14,10 +14,17 @@ function add (existingFeatures, name, variant) {
1414
variant = jsonStringify(variant)
1515
}
1616

17-
existingFeatures[name] = variant
17+
const existingIndex = existingFeatureKeys[name]
18+
if (typeof existingIndex === 'number') {
19+
existingFeatures[existingIndex] = { name, variant }
20+
return
21+
}
22+
23+
existingFeatures.push({ name, variant })
24+
existingFeatureKeys[name] = existingFeatures.length - 1
1825
}
1926

20-
function merge (existingFeatures, newFeatures) {
27+
function merge (existingFeatures, newFeatures, existingFeatureKeys) {
2128
if (!isArray(newFeatures)) {
2229
return
2330
}
@@ -30,27 +37,37 @@ function merge (existingFeatures, newFeatures) {
3037
}
3138

3239
// 'add' will handle if 'name' doesn't exist & 'variant' is optional
33-
add(existingFeatures, feature.name, feature.variant)
40+
add(existingFeatures, existingFeatureKeys, feature.name, feature.variant)
3441
}
42+
43+
return existingFeatures
3544
}
3645

3746
// convert feature flags from a map of 'name -> variant' into the format required
3847
// by the Bugsnag Event API:
3948
// [{ featureFlag: 'name', variant: 'variant' }, { featureFlag: 'name 2' }]
4049
function toEventApi (featureFlags) {
4150
return map(
42-
keys(featureFlags),
43-
name => {
51+
filter(featureFlags, Boolean),
52+
({ name, variant }) => {
4453
const flag = { featureFlag: name }
4554

4655
// don't add a 'variant' property unless there's actually a value
47-
if (typeof featureFlags[name] === 'string') {
48-
flag.variant = featureFlags[name]
56+
if (typeof variant === 'string') {
57+
flag.variant = variant
4958
}
5059

5160
return flag
5261
}
5362
)
5463
}
5564

56-
module.exports = { add, merge, toEventApi }
65+
function clear (features, featuresIndex, name) {
66+
const existingIndex = featuresIndex[name]
67+
if (typeof existingIndex === 'number') {
68+
features[existingIndex] = null
69+
delete featuresIndex[name]
70+
}
71+
}
72+
73+
module.exports = { add, clear, merge, toEventApi }

‎packages/core/lib/test/clone-client.test.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -116,14 +116,14 @@ describe('clone-client', () => {
116116

117117
const clone = cloneClient(original)
118118

119-
expect(clone._features).toStrictEqual({ abc: '123' })
119+
expect(clone._features).toStrictEqual([{ name: 'abc', variant: '123' }])
120120
expect(clone._features).not.toBe(original._features)
121121

122122
// changing the clone's feature flags shouldn't affect the original
123123
clone.addFeatureFlag('xyz', '999')
124124

125-
expect(clone._features).toStrictEqual({ abc: '123', xyz: '999' })
126-
expect(original._features).toStrictEqual({ abc: '123' })
125+
expect(clone._features).toStrictEqual([{ name: 'abc', variant: '123' }, { name: 'xyz', variant: '999' }])
126+
expect(original._features).toStrictEqual([{ name: 'abc', variant: '123' }])
127127
})
128128

129129
it('should clone user information', () => {

0 commit comments

Comments
 (0)
Please sign in to comment.