-
Notifications
You must be signed in to change notification settings - Fork 158
✨ FFL-16 Precomputed flags evaluation #3580
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
Open
leoromanovsky
wants to merge
25
commits into
main
Choose a base branch
from
rasendubi/ffe-openfeature
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from 24 commits
Commits
Show all changes
25 commits
Select commit
Hold shift + click to select a range
044fd8c
✨ [FFL-24] add openfeature dependency and datadog provider
leoromanovsky 59e15cf
remove openfeature/core
leoromanovsky ad0a754
refactor: move no-restricted-syntax closer to use
rasendubi 975d58d
chore: mark @openfeature/core as side-effect free
rasendubi c8b780a
chore: add a comment on why classes are not allowed
rasendubi bab9a98
refactor: only depend on @openfeature/web-sdk
rasendubi 143c64a
chore: properly use peer dependencies
rasendubi 1044dc1
feat: precomputed configuration evaluation
rasendubi 4d6c744
chore: remove duplicate dependency from LICENSE-3rdparty file
rasendubi 6c7ab1b
doc: fix example key -> targetingKey
rasendubi cf67794
feat: expose API for initializing with precomputed configuration
rasendubi 43c2239
feat: add configuration fetching
rasendubi 18c4398
chore: update lock file
rasendubi 9d77f2d
chore: revert change to check-licenses.js
rasendubi 3af203f
docs: add example integration with RUM
rasendubi fe6b7e4
comply with JSON:API
rasendubi 361d4f2
feat: use staging as default base url
rasendubi 34a446e
fix linter errors
rasendubi 143dcaf
chore: rename browser-flagging to openfeature-provider
rasendubi 013e074
polyfill getGlobalObject
leoromanovsky e5103f7
webpack polyfill
leoromanovsky e855293
lint
leoromanovsky 9a562e8
disable side effect
leoromanovsky a58b867
✅ change 'globalThis' to 'window' in @openfeature/web-sdk
BenoitZugmeyer 9437999
Merge branch 'main' into rasendubi/ffe-openfeature
leoromanovsky File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,50 @@ | ||
# Flagging SDK (Prerelease) | ||
|
||
This package supports flagging and experimentation by performing evaluation in the browser. | ||
|
||
## Initialize | ||
|
||
```typescript | ||
import { DatadogProvider } from '@datadog/openfeature-provider' | ||
|
||
const datadogFlaggingProvider = new DatadogProvider() | ||
|
||
// provide the subject | ||
const subject = { | ||
targetingKey: 'subject-key-1', | ||
} | ||
await OpenFeature.setContext(subject) | ||
|
||
// initialize | ||
await OpenFeature.setProviderAndWait(datadogFlaggingProvider) | ||
``` | ||
|
||
## Evaluation | ||
|
||
```typescript | ||
const client = OpenFeature.getClient() | ||
|
||
// provide the flag key and a default value which is returned for exceptional conditions. | ||
const flagEval = client.getBooleanValue('<FLAG_KEY>', false) | ||
``` | ||
|
||
## Integration with RUM feature flag tracking | ||
|
||
```typescript | ||
// Initialize RUM with experimental feature flags tracking | ||
import { datadogRum } from '@datadog/browser-rum'; | ||
|
||
// Initialize Datadog Browser SDK | ||
datadogRum.init({ | ||
... | ||
enableExperimentalFeatures: ["feature_flags"], | ||
... | ||
}); | ||
|
||
// Add OpenFeature hook | ||
OpenFeature.addHooks({ | ||
after(_hookContext: HookContext, details: EvaluationDetails<FlagValue>) { | ||
datadogRum.addFeatureFlagEvaluation(details.flagKey, details.value) | ||
} | ||
}) | ||
``` |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
import type { EvaluationContext, FlagValueType, JsonValue, ResolutionDetails } from '@openfeature/web-sdk' | ||
/** | ||
* Internal configuration for DatadogProvider. | ||
*/ | ||
export type Configuration = { | ||
/** @internal */ | ||
precomputed?: PrecomputedConfiguration | ||
} | ||
|
||
/** @internal */ | ||
export type PrecomputedConfiguration = { | ||
response: PrecomputedConfigurationResponse | ||
context?: EvaluationContext | ||
fetchedAt?: UnixTimestamp | ||
} | ||
|
||
// Fancy way to map FlagValueType to expected FlagValue. | ||
/** @internal */ | ||
export type FlagTypeToValue<T extends FlagValueType> = { | ||
['boolean']: boolean | ||
['string']: string | ||
['number']: number | ||
['object']: JsonValue | ||
}[T] | ||
|
||
/** @internal | ||
* Timestamp in milliseconds since Unix Epoch. | ||
*/ | ||
export type UnixTimestamp = number | ||
|
||
/** @internal */ | ||
export type PrecomputedConfigurationResponse = { | ||
data: { | ||
attributes: { | ||
/** When configuration was generated. */ | ||
createdAt: number | ||
flags: Record<string, PrecomputedFlag> | ||
} | ||
} | ||
} | ||
|
||
/** @internal */ | ||
export type PrecomputedFlag<T extends FlagValueType = FlagValueType> = { | ||
type: T | ||
resolution: ResolutionDetails<FlagTypeToValue<T>> | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
export type * from './configuration' | ||
export * from './wire' |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
import type { EvaluationContext } from '@openfeature/web-sdk' | ||
|
||
import type { Configuration, UnixTimestamp } from './configuration' | ||
|
||
type ConfigurationWire = { | ||
version: 2 | ||
precomputed?: { | ||
context?: EvaluationContext | ||
response: string | ||
fetchedAt?: UnixTimestamp | ||
} | ||
} | ||
|
||
/** | ||
* Create configuration from a string created with `configurationToString`. | ||
*/ | ||
export function configurationFromString(s: string): Configuration { | ||
try { | ||
const wire: ConfigurationWire = JSON.parse(s) | ||
|
||
if (wire.version !== 2) { | ||
// Unknown version | ||
return {} | ||
} | ||
|
||
const configuration: Configuration = {} | ||
if (wire.precomputed) { | ||
configuration.precomputed = { | ||
...wire.precomputed, | ||
response: JSON.parse(wire.precomputed.response), | ||
} | ||
} | ||
|
||
return configuration | ||
} catch { | ||
return {} | ||
} | ||
} | ||
|
||
/** | ||
* Serialize configuration to string that can be deserialized with | ||
* `configurationFromString`. The serialized string format is | ||
* unspecified. | ||
*/ | ||
export function configurationToString(configuration: Configuration): string { | ||
const wire: ConfigurationWire = { | ||
version: 2, | ||
} | ||
|
||
if (configuration.precomputed) { | ||
wire.precomputed = { | ||
...configuration.precomputed, | ||
response: JSON.stringify(configuration.precomputed), | ||
} | ||
} | ||
|
||
return JSON.stringify(wire) | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,9 +1,14 @@ | ||
import { defineGlobal, getGlobalObject } from '@datadog/browser-core' | ||
import { flagging as importedFlagging } from '../hello' | ||
|
||
export const datadogFlagging = importedFlagging | ||
import { DatadogProvider } from '../openfeature/provider' | ||
|
||
export { DatadogProvider } | ||
export { configurationFromString, configurationToString } from '../configuration' | ||
|
||
interface BrowserWindow extends Window { | ||
DD_FLAGGING?: typeof datadogFlagging | ||
DD_FLAGGING?: { | ||
Provider: typeof DatadogProvider | ||
} | ||
} | ||
defineGlobal(getGlobalObject<BrowserWindow>(), 'DD_FLAGGING', datadogFlagging) | ||
|
||
defineGlobal(getGlobalObject<BrowserWindow>(), 'DD_FLAGGING', { Provider: DatadogProvider }) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
import configurationWire from '../test/data/precomputed-v2-wire.json' | ||
|
||
import { configurationFromString } from './configuration' | ||
import { evaluate } from './evaluation' | ||
|
||
const configuration = configurationFromString( | ||
// Adding stringify because import has parsed JSON | ||
JSON.stringify(configurationWire) | ||
) | ||
|
||
describe('evaluate', () => { | ||
it('returns default for missing configuration', () => { | ||
const result = evaluate({}, 'boolean', 'boolean-flag', true, {}) | ||
expect(result).toEqual({ | ||
value: true, | ||
reason: 'DEFAULT', | ||
}) | ||
}) | ||
|
||
it('returns default for unknown flag', () => { | ||
const result = evaluate(configuration, 'string', 'unknown-flag', 'default', {}) | ||
expect(result).toEqual({ | ||
value: 'default', | ||
reason: 'ERROR', | ||
errorCode: 'FLAG_NOT_FOUND' as any, | ||
}) | ||
}) | ||
|
||
it('resolves string flag', () => { | ||
const result = evaluate(configuration, 'string', 'string-flag', 'default', {}) | ||
expect(result).toEqual({ | ||
value: 'red', | ||
variant: 'variation-123', | ||
flagMetadata: { | ||
allocationKey: 'allocation-123', | ||
experiment: true, | ||
}, | ||
}) | ||
}) | ||
|
||
it('resolves object flag', () => { | ||
const result = evaluate<any>(configuration, 'object', 'json-flag', { hello: 'world' }, {}) | ||
expect(result).toEqual({ | ||
value: { key: 'value', prop: 123 }, | ||
variant: 'variation-127', | ||
flagMetadata: { | ||
allocationKey: 'allocation-127', | ||
experiment: true, | ||
}, | ||
}) | ||
}) | ||
}) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
import type { ErrorCode, EvaluationContext, FlagValueType, ResolutionDetails } from '@openfeature/web-sdk' | ||
|
||
import type { Configuration, PrecomputedConfiguration, FlagTypeToValue } from './configuration' | ||
|
||
export function evaluate<T extends FlagValueType>( | ||
configuration: Configuration, | ||
type: T, | ||
flagKey: string, | ||
defaultValue: FlagTypeToValue<T>, | ||
context: EvaluationContext | ||
): ResolutionDetails<FlagTypeToValue<T>> { | ||
if (configuration.precomputed) { | ||
return evaluatePrecomputed(configuration.precomputed, type, flagKey, defaultValue, context) | ||
} | ||
|
||
return { | ||
value: defaultValue, | ||
reason: 'DEFAULT', | ||
} | ||
} | ||
|
||
function evaluatePrecomputed<T extends FlagValueType>( | ||
precomputed: PrecomputedConfiguration, | ||
type: T, | ||
flagKey: string, | ||
defaultValue: FlagTypeToValue<T>, | ||
_context: EvaluationContext | ||
): ResolutionDetails<FlagTypeToValue<T>> { | ||
const flag = precomputed.response.data.attributes.flags[flagKey] | ||
if (!flag) { | ||
return { | ||
value: defaultValue, | ||
reason: 'ERROR', | ||
errorCode: 'FLAG_NOT_FOUND' as ErrorCode, | ||
} | ||
} | ||
|
||
if (flag.type !== type) { | ||
return { | ||
value: defaultValue, | ||
reason: 'ERROR', | ||
errorCode: 'TYPE_MISMATCH' as ErrorCode, | ||
} | ||
} | ||
|
||
return flag.resolution as ResolutionDetails<FlagTypeToValue<T>> | ||
} |
This file was deleted.
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
suggestion: (as discussed) it could be nice to rename the package folder as well
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My notes from the conversation was we left it without consensus but I am ok revisiting it as the SDK scope becomes more clear; there might be more than just the openfeature provided being exported; perhaps where we will land is a core flagging package (here or in a shared javascript repository with react native) and openfeature provider as a wrapper on it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sounds good to me.