Skip to content

Commit 06ddd83

Browse files
authored
FFM-12129 Add new event for default variation being returned (#145)
1 parent 6b8ded4 commit 06ddd83

File tree

7 files changed

+85
-16
lines changed

7 files changed

+85
-16
lines changed

Diff for: README.md

+18-1
Original file line numberDiff line numberDiff line change
@@ -213,7 +213,7 @@ client.on(Event.ERROR_STREAM, error => {
213213

214214
### Getting value for a particular feature flag
215215

216-
If you would like to know that the default variation was returned when getting the value, for example, if the provided flag identifier wasn't found then pass true for the third argument withDebug:
216+
If you would like to know that the default variation was returned when getting the value, for example, if the provided flag wasn't found in the cache then pass true for the third argument withDebug:
217217
```typescript
218218
const result = client.variation('Dark_Theme', false, true);
219219
```
@@ -248,6 +248,23 @@ For the example above:
248248

249249
- If the flag identifier 'Dark_Theme' exists in storage, variationValue would be the stored value for that identifier.
250250
- If the flag identifier 'Dark_Theme' does not exist, variationValue would be the default value provided, in this case, false
251+
252+
* Note the reasons for the default variation being returned can be
253+
1. SDK Not Initialized Yet
254+
2. Typo in Flag Identifier
255+
3. Wrong project API key being used
256+
257+
#### Listening for the `ERROR_DEFAULT_VARIATION_RETURNED` event
258+
You can also listen for the `ERROR_DEFAULT_VARIATION_RETURNED` event, which is emitted whenever a default variation is returned because the flag has not been found in the cache. This is useful for logging or taking other action when a flag is not found.
259+
260+
Example of listening for the event:
261+
262+
```typescript
263+
client.on(Event.ERROR_DEFAULT_VARIATION_RETURNED, ({ flag, defaultVariation }) => {
264+
console.warn(`Default variation returned for flag: ${flag}, value: ${defaultVariation}`)
265+
})
266+
```
267+
251268
### Cleaning up
252269

253270
Remove a listener of an event by `client.off`.

Diff for: package-lock.json

+2-2
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Diff for: package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@harnessio/ff-javascript-client-sdk",
3-
"version": "1.28.0",
3+
"version": "1.29.0",
44
"author": "Harness",
55
"license": "Apache-2.0",
66
"main": "dist/sdk.cjs.js",

Diff for: src/__tests__/variation.test.ts

+45-7
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,45 @@
11
import { getVariation } from '../variation'
2+
import type { Emitter } from 'mitt'
3+
import { type DefaultVariationEventPayload, Event } from '../types'
24

35
describe('getVariation', () => {
46
describe('without debug', () => {
57
it('should return the stored value when it exists', () => {
68
const storage = { testFlag: true, otherFlag: true, anotherFlag: false }
79
const mockMetricsHandler = jest.fn()
10+
const mockEventBus: Emitter = {
11+
emit: jest.fn(),
12+
on: jest.fn(),
13+
off: jest.fn(),
14+
all: new Map()
15+
}
816

9-
const result = getVariation('testFlag', false, storage, mockMetricsHandler)
17+
const result = getVariation('testFlag', false, storage, mockMetricsHandler, mockEventBus)
1018

1119
expect(result).toBe(true)
1220
expect(mockMetricsHandler).toHaveBeenCalledWith('testFlag', true)
21+
expect(mockEventBus.emit).not.toHaveBeenCalled()
1322
})
1423

15-
it('should return the default value when stored value is undefined', () => {
24+
it('should return the default value and emit event when it is missing', () => {
1625
const storage = {}
1726
const mockMetricsHandler = jest.fn()
27+
const mockEventBus: Emitter = {
28+
emit: jest.fn(),
29+
on: jest.fn(),
30+
off: jest.fn(),
31+
all: new Map()
32+
}
1833

19-
const result = getVariation('testFlag', false, storage, mockMetricsHandler)
34+
const defaultValue = false
35+
const result = getVariation('testFlag', defaultValue, storage, mockMetricsHandler, mockEventBus)
2036

21-
expect(result).toBe(false)
37+
expect(result).toBe(defaultValue)
2238
expect(mockMetricsHandler).not.toHaveBeenCalled()
39+
40+
const expectedEvent: DefaultVariationEventPayload = { flag: 'testFlag', defaultVariation: defaultValue }
41+
42+
expect(mockEventBus.emit).toHaveBeenCalledWith(Event.ERROR_DEFAULT_VARIATION_RETURNED, expectedEvent)
2343
})
2444
})
2545

@@ -29,21 +49,39 @@ describe('getVariation', () => {
2949
it('should return debug type with stored value', () => {
3050
const storage = { testFlag: true, otherFlag: true, anotherFlag: false }
3151
const mockMetricsHandler = jest.fn()
52+
const mockEventBus: Emitter = {
53+
emit: jest.fn(),
54+
on: jest.fn(),
55+
off: jest.fn(),
56+
all: new Map()
57+
}
3258

33-
const result = getVariation('testFlag', false, storage, mockMetricsHandler, true)
59+
const result = getVariation('testFlag', false, storage, mockMetricsHandler, mockEventBus, true)
3460

3561
expect(result).toEqual({ value: true, isDefaultValue: false })
3662
expect(mockMetricsHandler).toHaveBeenCalledWith(flagIdentifier, true)
63+
expect(mockEventBus.emit).not.toHaveBeenCalled()
3764
})
3865

3966
it('should return debug type with default value when flag is missing', () => {
4067
const storage = { otherFlag: true }
4168
const mockMetricsHandler = jest.fn()
69+
const mockEventBus: Emitter = {
70+
emit: jest.fn(),
71+
on: jest.fn(),
72+
off: jest.fn(),
73+
all: new Map()
74+
}
4275

43-
const result = getVariation('testFlag', false, storage, mockMetricsHandler, true)
76+
const defaultValue = false
77+
const result = getVariation('testFlag', defaultValue, storage, mockMetricsHandler, mockEventBus, true)
4478

45-
expect(result).toEqual({ value: false, isDefaultValue: true })
79+
expect(result).toEqual({ value: defaultValue, isDefaultValue: true })
4680
expect(mockMetricsHandler).not.toHaveBeenCalled()
81+
82+
const expectedEvent: DefaultVariationEventPayload = { flag: 'testFlag', defaultVariation: defaultValue }
83+
84+
expect(mockEventBus.emit).toHaveBeenCalledWith(Event.ERROR_DEFAULT_VARIATION_RETURNED, expectedEvent)
4785
})
4886
})
4987
})

Diff for: src/index.ts

+5-3
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,8 @@ import type {
1212
StreamEvent,
1313
Target,
1414
VariationFn,
15-
VariationValue
15+
VariationValue,
16+
DefaultVariationEventPayload
1617
} from './types'
1718
import { Event } from './types'
1819
import { defer, encodeTarget, getConfiguration } from './utils'
@@ -678,7 +679,7 @@ const initialize = (apiKey: string, target: Target, options?: Options): Result =
678679
}
679680

680681
const variation = (identifier: string, defaultValue: any, withDebug = false) => {
681-
return getVariation(identifier, defaultValue, storage, handleMetrics, withDebug)
682+
return getVariation(identifier, defaultValue, storage, handleMetrics, eventBus, withDebug)
682683
}
683684

684685
return {
@@ -702,5 +703,6 @@ export {
702703
EventOffBinding,
703704
Result,
704705
Evaluation,
705-
VariationValue
706+
VariationValue,
707+
DefaultVariationEventPayload
706708
}

Diff for: src/types.ts

+8-1
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,8 @@ export enum Event {
2929
ERROR_AUTH = 'auth error',
3030
ERROR_FETCH_FLAGS = 'fetch flags error',
3131
ERROR_FETCH_FLAG = 'fetch flag error',
32-
ERROR_STREAM = 'stream error'
32+
ERROR_STREAM = 'stream error',
33+
ERROR_DEFAULT_VARIATION_RETURNED = 'default variation returned'
3334
}
3435

3536
export type VariationValue = boolean | string | number | object | undefined
@@ -41,6 +42,11 @@ export interface VariationValueWithDebug {
4142
isDefaultValue: boolean
4243
}
4344

45+
export interface DefaultVariationEventPayload {
46+
flag: string
47+
defaultVariation: VariationValue
48+
}
49+
4450
export interface Evaluation {
4551
flag: string // Feature flag identifier
4652
identifier: string // variation identifier
@@ -67,6 +73,7 @@ export interface EventCallbackMapping {
6773
[Event.ERROR_FETCH_FLAG]: (error: unknown) => void
6874
[Event.ERROR_STREAM]: (error: unknown) => void
6975
[Event.ERROR_METRICS]: (error: unknown) => void
76+
[Event.ERROR_DEFAULT_VARIATION_RETURNED]: (payload: DefaultVariationEventPayload) => void
7077
}
7178

7279
export type EventOnBinding = <K extends keyof EventCallbackMapping>(event: K, callback: EventCallbackMapping[K]) => void

Diff for: src/variation.ts

+6-1
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,22 @@
1-
import type { VariationValue, VariationValueWithDebug } from './types'
1+
import { type DefaultVariationEventPayload, Event, type VariationValue, type VariationValueWithDebug } from './types'
2+
import type { Emitter } from 'mitt'
23

34
export function getVariation(
45
identifier: string,
56
defaultValue: any,
67
storage: Record<string, any>,
78
metricsHandler: (flag: string, value: any) => void,
9+
eventBus: Emitter,
810
withDebug?: boolean
911
): VariationValue | VariationValueWithDebug {
1012
const identifierExists = identifier in storage
1113
const value = identifierExists ? storage[identifier] : defaultValue
1214

1315
if (identifierExists) {
1416
metricsHandler(identifier, value)
17+
} else {
18+
const payload: DefaultVariationEventPayload = { flag: identifier, defaultVariation: defaultValue }
19+
eventBus.emit(Event.ERROR_DEFAULT_VARIATION_RETURNED, payload)
1520
}
1621

1722
return !withDebug ? value : { value, isDefaultValue: !identifierExists }

0 commit comments

Comments
 (0)