Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions packages/core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,7 @@
"./lib/es-utils/keys": "./src/lib/es-utils/keys.js",
"./lib/derecursify": "./src/lib/derecursify.js",
"./lib/callback-runner": "./src/lib/callback-runner.js",
"./lib/path-normalizer": "./src/lib/path-normalizer.js",
"./lib/feature-flag-delegate": "./src/lib/feature-flag-delegate.js"
"./lib/path-normalizer": "./src/lib/path-normalizer.js"
},
"description": "Core classes and utilities for Bugsnag notifiers",
"homepage": "https://www.bugsnag.com/",
Expand Down
12 changes: 6 additions & 6 deletions packages/core/src/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import runCallbacks from './lib/callback-runner'
import metadataDelegate from './lib/metadata-delegate'
import runSyncCallbacks from './lib/sync-callback-runner'
import BREADCRUMB_TYPES from './lib/breadcrumb-types'
import { add, clear, merge } from './lib/feature-flag-delegate'
import featureFlagDelegate from './lib/feature-flag-delegate'
import { BreadcrumbType, Config, Delivery, FeatureFlag, LoggerConfig, NotifiableError, Notifier, OnBreadcrumbCallback, OnErrorCallback, OnSessionCallback, Plugin, SessionDelegate, User } from './common'

const HUB_PREFIX = '00000'
Expand Down Expand Up @@ -128,15 +128,15 @@ export default class Client<T extends Config = Config> {
}

addFeatureFlag (name: string, variant: string | null = null) {
add(this._features, this._featuresIndex, name, variant)
featureFlagDelegate.add(this._features, this._featuresIndex, name, variant)
}

addFeatureFlags (featureFlags: FeatureFlag[]) {
merge(this._features, featureFlags, this._featuresIndex)
featureFlagDelegate.merge(this._features, featureFlags, this._featuresIndex)
}

clearFeatureFlag (name: string) {
clear(this._features, this._featuresIndex, name)
featureFlagDelegate.clear(this._features, this._featuresIndex, name)
}

clearFeatureFlags () {
Expand Down Expand Up @@ -206,7 +206,7 @@ export default class Client<T extends Config = Config> {

// update and elevate some options
this._metadata = assign({}, config.metadata)
merge(this._features, config.featureFlags, this._featuresIndex)
featureFlagDelegate.merge(this._features, config.featureFlags, this._featuresIndex)
this._user = assign({}, config.user)
this._context = config.context
if (config.logger) this._logger = config.logger
Expand Down Expand Up @@ -352,7 +352,7 @@ export default class Client<T extends Config = Config> {
event._metadata = assign({}, event._metadata, this._metadata)
event._user = assign({}, event._user, this._user)
event.breadcrumbs = this._breadcrumbs.slice()
merge(event._features, this._features, event._featuresIndex)
featureFlagDelegate.merge(event._features, this._features, event._featuresIndex)

// exit early if events should not be sent on the current releaseStage
if (this._config.enabledReleaseStages !== null && !includes(this._config.enabledReleaseStages, this._config.releaseStage)) {
Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/event.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ export default class Event {
public threads: Thread[]

public _metadata: { [key: string]: any }
public _features: FeatureFlag | null[]
public _features: (FeatureFlag | null)[]
public _featuresIndex: { [key: string]: number }

public _user: User
Expand Down
1 change: 1 addition & 0 deletions packages/core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export { default as listOfFunctions } from './lib/validators/list-of-functions'
export { default as stringWithLength } from './lib/validators/string-with-length'
export { default as metadataDelegate } from './lib/metadata-delegate'
export { default as nodeFallbackStack } from './lib/node-fallback-stack'
export { default as featureFlagDelegate } from './lib/feature-flag-delegate'
export { default as runSyncCallbacks } from './lib/sync-callback-runner'

export * from './common'
70 changes: 0 additions & 70 deletions packages/core/src/lib/feature-flag-delegate.js

This file was deleted.

88 changes: 88 additions & 0 deletions packages/core/src/lib/feature-flag-delegate.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import isArray from './es-utils/is-array'
import jsonStringify from '@bugsnag/safe-json-stringify'
import {FeatureFlag} from "../common";

type FeatureFlagEventApi = {
featureFlag: string
variant?: string
}

interface FeatureFlagDelegate{
add: (existingFeatures: Array<FeatureFlag | null>, existingFeatureKeys: { [key: string]: number }, name?: string | null, variant?: any ) => void
merge: (
existingFeatures: Array<{ name: string; variant?: any } | null>,
newFeatures: any,
existingFeatureKeys: { [key: string]: any }
) => Array<{ name: string; variant: any }> | undefined
toEventApi: (featureFlags: Array<FeatureFlag | null>) => FeatureFlagEventApi[]
clear: (features: (FeatureFlag | null)[], featuresIndex: { [key: string]: number }, name: string) => void
}

const featureFlagDelegate: FeatureFlagDelegate = {
add: (existingFeatures, existingFeatureKeys, name, variant) => {
if (typeof name !== 'string') {
return
}

if (variant === undefined) {
variant = null
} else if (variant !== null && typeof variant !== 'string') {
variant = jsonStringify(variant)
}

const existingIndex = existingFeatureKeys[name]
if (typeof existingIndex === 'number') {
existingFeatures[existingIndex] = {name, variant}
return
}

existingFeatures.push({name, variant})
existingFeatureKeys[name] = existingFeatures.length - 1
},

merge: (existingFeatures, newFeatures, existingFeatureKeys) => {
if (!isArray(newFeatures)) {
return
}

for (let i = 0; i < newFeatures.length; ++i) {
const feature = newFeatures[i]

if (feature === null || typeof feature !== 'object') {
continue
}

// 'add' will handle if 'name' doesn't exist & 'variant' is optional
featureFlagDelegate.add(existingFeatures, existingFeatureKeys, feature.name, feature.variant)
}

// Remove any nulls from the array to match the return type
return existingFeatures.filter(f => f) as Array<{ name: string; variant: any }>
},

// convert feature flags from a map of 'name -> variant' into the format required
// by the Bugsnag Event API:
// [{ featureFlag: 'name', variant: 'variant' }, { featureFlag: 'name 2' }]
toEventApi: (featureFlags) => {
return (featureFlags || [])
.filter((flag): flag is FeatureFlag => flag !== null && typeof flag === 'object')
.map((flag) => {
const result: FeatureFlagEventApi = { featureFlag: flag.name };
if (typeof flag.variant === 'string') {
result.variant = flag.variant;
}
return result;
});
},
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

toEventApi method was rewrite with assistance of Copilot.


clear: (features, featuresIndex, name) => {
const existingIndex = featuresIndex[name]
if (typeof existingIndex === 'number') {
features[existingIndex] = null
// eslint-disable-next-line @typescript-eslint/no-dynamic-delete
delete featuresIndex[name]
}
}
}

export default featureFlagDelegate
44 changes: 22 additions & 22 deletions packages/core/test/lib/feature-flag-delegate.test.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import delegate from '../../src/lib/feature-flag-delegate'
import featureFlagDelegate from '../../src/lib/feature-flag-delegate'

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

delegate.add(existingFeatures, existingFeaturesIndex)
featureFlagDelegate.add(existingFeatures, existingFeaturesIndex)

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

delegate.add(existingFeatures, existingFeaturesIndex, undefined, '???')
featureFlagDelegate.add(existingFeatures, existingFeaturesIndex, undefined, '???')

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

delegate.add(existingFeatures, existingFeaturesIndex, null, '???')
featureFlagDelegate.add(existingFeatures, existingFeaturesIndex, null, '???')

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

delegate.add(existingFeatures, existingFeaturesIndex, 'good_feature')
featureFlagDelegate.add(existingFeatures, existingFeaturesIndex, 'good_feature')

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

delegate.add(existingFeatures, existingFeaturesIndex, 'ok_feature', null)
featureFlagDelegate.add(existingFeatures, existingFeaturesIndex, 'ok_feature', null)

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

delegate.add(existingFeatures, existingFeaturesIndex, 'cool_feature', 'very ant')
featureFlagDelegate.add(existingFeatures, existingFeaturesIndex, 'cool_feature', 'very ant')

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

delegate.add(existingFeatures, existingFeaturesIndex, 'some_feature', variant)
featureFlagDelegate.add(existingFeatures, existingFeaturesIndex, 'some_feature', variant)

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

delegate.add(existingFeatures, existingFeaturesIndex, 'b', 'x')
delegate.add(existingFeatures, existingFeaturesIndex, 'e', 'y')
delegate.add(existingFeatures, existingFeaturesIndex, 'a', 'z')
featureFlagDelegate.add(existingFeatures, existingFeaturesIndex, 'b', 'x')
featureFlagDelegate.add(existingFeatures, existingFeaturesIndex, 'e', 'y')
featureFlagDelegate.add(existingFeatures, existingFeaturesIndex, 'a', 'z')

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

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

delegate.merge(existingFeatures, [
featureFlagDelegate.merge(existingFeatures, [
{ name: 'b', variant: 'x' },
{ name: 'e', variant: 'y' },
{ name: 'a', variant: 'z' }
Expand Down Expand Up @@ -143,7 +143,7 @@ describe('feature flag delegate', () => {
{ name: 'p', variant: 'p' }
]

delegate.merge(existingFeatures, [
featureFlagDelegate.merge(existingFeatures, [
{ name: 'a', variant: 12345 },
{ name: 'c', variant: 0 },
{ name: 'e', variant: true },
Expand Down Expand Up @@ -183,7 +183,7 @@ describe('feature flag delegate', () => {
const existingFeatures = [{ name: 'a', variant: 'a' }, { name: 'b', variant: 'b' }, { name: 'c', variant: 'c' }, { name: 'd', variant: 'd' }, { name: 'e', variant: 'e' }]
const existingFeaturesIndex = { a: 0, b: 1, c: 2, d: 3, e: 3 }

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

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

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

delegate.merge(features, [
featureFlagDelegate.merge(features, [
{ name: 'b', variant: 'x' },
'name: yes',
undefined,
Expand All @@ -230,16 +230,16 @@ describe('feature flag delegate', () => {
const features: any[] = []
const featuresIndex = {}

delegate.add(features, featuresIndex, 'a', 'b')
delegate.merge(features, [
featureFlagDelegate.add(features, featuresIndex, 'a', 'b')
featureFlagDelegate.merge(features, [
{ name: 'c', variant: 'd' },
{ name: 'e' },
{ name: 'f', variant: 'g' }
], featuresIndex)

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

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

it('should handle an empty array', () => {
expect(delegate.toEventApi([])).toStrictEqual([])
expect(featureFlagDelegate.toEventApi([])).toStrictEqual([])
})
})
})
Loading