Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[PLAT-8735] Preserve feature flag order #1802

Merged
merged 3 commits into from
Sep 2, 2022
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

- (react-native) Update bugsnag-cocoa from v6.20.0 to [v6.22.2](https://github.com/bugsnag/bugsnag-cocoa/blob/master/CHANGELOG.md#6222-2022-08-17)
- (react-native) Update bugsnag-android from v5.24.0 to [v5.26.0](https://github.com/bugsnag/bugsnag-android/blob/master/CHANGELOG.md#5260-2022-08-18)
- Refactor feature flags to maintain insertion order [#1802](https://github.com/bugsnag/bugsnag-js/pull/1802)

## v7.17.3 (2022-07-18)

Expand Down
6 changes: 3 additions & 3 deletions packages/core/client.d.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Client, OnErrorCallback, Config, Breadcrumb, Session, OnSessionCallback, OnBreadcrumbCallback, Plugin, Device, App, User } from './types'
import { Client, OnErrorCallback, Config, Breadcrumb, Session, OnSessionCallback, OnBreadcrumbCallback, Plugin, Device, App, User, FeatureFlag } from './types'
import EventWithInternals from './event'

interface LoggerConfig {
Expand Down Expand Up @@ -43,14 +43,14 @@ export default class ClientWithInternals<T extends Config = Config> extends Clie
_config: T
_depth: number
_logger: LoggerConfig
_breadcrumbs: Breadcrumb[];
_breadcrumbs: Breadcrumb[]
_delivery: Delivery
_setDelivery: (handler: (client: Client) => Delivery) => void

_user: User

_metadata: { [key: string]: any }
_features: { [key: string]: string | null }
_features: FeatureFlag | null[]

startSession(): ClientWithInternals
resumeSession(): ClientWithInternals
Expand Down
18 changes: 10 additions & 8 deletions packages/core/client.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,10 @@ const reduce = require('./lib/es-utils/reduce')
const keys = require('./lib/es-utils/keys')
const assign = require('./lib/es-utils/assign')
const runCallbacks = require('./lib/callback-runner')
const featureFlagDelegate = require('./lib/feature-flag-delegate')
const metadataDelegate = require('./lib/metadata-delegate')
const runSyncCallbacks = require('./lib/sync-callback-runner')
const BREADCRUMB_TYPES = require('./lib/breadcrumb-types')
const { add, clear, merge } = require('./lib/feature-flag-delegate')

const noop = () => {}

Expand All @@ -36,7 +36,8 @@ class Client {
this._breadcrumbs = []
this._session = null
this._metadata = {}
this._features = {}
this._featuresIndex = {}
this._features = []
this._context = undefined
this._user = {}

Expand Down Expand Up @@ -90,19 +91,20 @@ class Client {
}

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

addFeatureFlags (featureFlags) {
featureFlagDelegate.merge(this._features, featureFlags)
merge(this._features, featureFlags, this._featuresIndex)
}

clearFeatureFlag (name) {
delete this._features[name]
clear(this._features, this._featuresIndex, name)
}

clearFeatureFlags () {
this._features = {}
this._features = []
this._featuresIndex = {}
}

getContext () {
Expand Down Expand Up @@ -151,7 +153,7 @@ class Client {

// update and elevate some options
this._metadata = assign({}, config.metadata)
featureFlagDelegate.merge(this._features, config.featureFlags)
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 @@ -295,9 +297,9 @@ class Client {
})
event.context = event.context || this._context
event._metadata = assign({}, event._metadata, this._metadata)
event._features = assign({}, event._features, this._features)
event._user = assign({}, event._user, this._user)
event.breadcrumbs = this._breadcrumbs.slice()
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
5 changes: 3 additions & 2 deletions packages/core/event.d.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { App, Device, Event, Request, Breadcrumb, User, Session } from './types'
import { App, Device, Event, Request, Breadcrumb, User, Session, FeatureFlag } from './types'
import { Error } from './types/event'

interface HandledState {
Expand All @@ -21,7 +21,8 @@ interface FeatureFlagPayload {
export default class EventWithInternals extends Event {
constructor (errorClass: string, errorMessage: string, stacktrace: any[], handledState?: HandledState, originalError?: Error)
_metadata: { [key: string]: any }
_features: { [key: string]: string | null }
_features: FeatureFlag | null[]
_featuresIndex: { [key: string]: number }
_user: User
_handledState: HandledState
_session?: Session
Expand Down
12 changes: 7 additions & 5 deletions packages/core/event.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@ class Event {
this.threads = []

this._metadata = {}
this._features = {}
this._features = []
this._featuresIndex = {}
this._user = {}
this._session = undefined

Expand Down Expand Up @@ -56,19 +57,20 @@ class Event {
}

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

addFeatureFlags (featureFlags) {
featureFlagDelegate.merge(this._features, featureFlags)
featureFlagDelegate.merge(this._features, featureFlags, this._featuresIndex)
}

clearFeatureFlag (name) {
delete this._features[name]
featureFlagDelegate.clear(this._features, this._featuresIndex, name)
}

clearFeatureFlags () {
this._features = {}
this._features = []
this._featuresIndex = {}
}

getUser () {
Expand Down
3 changes: 2 additions & 1 deletion packages/core/lib/clone-client.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ module.exports = (client) => {
// so ensure they are are (shallow) cloned
clone._breadcrumbs = client._breadcrumbs.slice()
clone._metadata = assign({}, client._metadata)
clone._features = assign({}, client._features)
clone._features = [...client._features]
clone._featuresIndex = assign({}, client._featuresIndex)
clone._user = assign({}, client._user)
clone._context = client._context

Expand Down
37 changes: 27 additions & 10 deletions packages/core/lib/feature-flag-delegate.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
const map = require('./es-utils/map')
const keys = require('./es-utils/keys')
const filter = require('./es-utils/filter')
const isArray = require('./es-utils/is-array')
const jsonStringify = require('@bugsnag/safe-json-stringify')

function add (existingFeatures, name, variant) {
function add (existingFeatures, existingFeatureKeys, name, variant) {
if (typeof name !== 'string') {
return
}
Expand All @@ -14,10 +14,17 @@ function add (existingFeatures, name, variant) {
variant = jsonStringify(variant)
}

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

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

function merge (existingFeatures, newFeatures) {
function merge (existingFeatures, newFeatures, existingFeatureKeys) {
if (!isArray(newFeatures)) {
return
}
Expand All @@ -30,27 +37,37 @@ function merge (existingFeatures, newFeatures) {
}

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

return existingFeatures
}

// 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' }]
function toEventApi (featureFlags) {
return map(
keys(featureFlags),
name => {
filter(featureFlags, Boolean),
({ name, variant }) => {
const flag = { featureFlag: name }

// don't add a 'variant' property unless there's actually a value
if (typeof featureFlags[name] === 'string') {
flag.variant = featureFlags[name]
if (typeof variant === 'string') {
flag.variant = variant
}

return flag
}
)
}

module.exports = { add, merge, toEventApi }
function clear (features, featuresIndex, name) {
const existingIndex = featuresIndex[name]
if (typeof existingIndex === 'number') {
features[existingIndex] = null
delete featuresIndex[name]
}
}

module.exports = { add, clear, merge, toEventApi }
6 changes: 3 additions & 3 deletions packages/core/lib/test/clone-client.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -116,14 +116,14 @@ describe('clone-client', () => {

const clone = cloneClient(original)

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

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

expect(clone._features).toStrictEqual({ abc: '123', xyz: '999' })
expect(original._features).toStrictEqual({ abc: '123' })
expect(clone._features).toStrictEqual([{ name: 'abc', variant: '123' }, { name: 'xyz', variant: '999' }])
expect(original._features).toStrictEqual([{ name: 'abc', variant: '123' }])
})

it('should clone user information', () => {
Expand Down
Loading