Featurevisor

Migration guides

Migrating from v1 to v2

Detailed guide for migrating existing Featurevisor projects (using Featurevisor CLI) and applications (using Featurevisor SDKs) to latest v2.0.


Defining attributes

Attribute as an object New

Attribute values in context can now also be flat objects.

You can still continue to use other existing attribute types without any changes. This change is only if you wish to define attributes as objects.

Defining attribute

Before
attributes/userId.yml
description: My userId attribute
type: string
Before
attributes/userCountry.yml
description: My userCountry attribute
type: string
After
attributes/user.yml
description: My user attribute description
type: object
properties:
id:
type: string
description: The user ID
country:
type: string
description: The country of the user

Passing attribute in context

When evaluating values in your application with SDKs, you can pass the value as an object:

Before
your-app/index.js
const f; // Featurevisor SDK instance
const context = {
userId: '123',
userCountry: 'nl',
browser: 'chrome',
}
const isFeatureEnabled = f.isEnabled(
'myFeature',
context
)
After
your-app/index.js
const f; // Featurevisor SDK instance
const context = {
user: {
id: '123',
country: 'nl',
},
browser: 'chrome',
}
const isFeatureEnabled = f.isEnabled(
'myFeature',
context
)

Dot separated path

You can make use of dot-separated paths to specify nested attributes.

For example, inside features:

Before
features/myFeature.yml
# ...
bucketBy: userId
After
features/myFeature.yml
# ...
bucketBy: user.id

And also in conditions:

Before
segments/netherlands.yml
description: Netherlands segment
conditions:
- attribute: userCountry
operator: equals
value: nl
After
segments/netherlands.yml
description: Netherlands segment
conditions:
- attribute: user.country
operator: equals
value: nl

Learn more in Attributes page.

Defining segments

Conditions targeting everyone New

We can now use asterisks (*) in conditions (either directly in segments or in features) to match any condition:

segments/mySegment.yml
description: My segment description
conditions: '*'

This is very handy when you wish to start with an empty segment, then later add conditions to it.

Operator: exists New

Checks if the attribute exists in the context:

segments/mySegment.yml
description: My segment description
conditions:
- attribute: browser
operator: exists
your-app/index.js
const f; // Featurevisor SDK instance
const context = {
userId: '123',
browser: 'chrome', // exists
}
const isFeatureEnabled = f.isEnabled(
'myFeature',
context
)

Operator: notExists New

Checks if the attribute does not exist in the context:

segments/mySegment.yml
description: My segment description
conditions:
- attribute: browser
operator: notExists
your-app/index.js
const f; // Featurevisor SDK instance
const context = {
userId: '123',
// `browser` does not exist
}
const isFeatureEnabled = f.isEnabled(
'myFeature',
context
)

Operator: includes New

Checks if a certain value is included in the attribute's array (of strings) value.

segments/mySegment.yml
description: My segment description
conditions:
- attribute: permissions
operator: includes
value: write
your-app/index.js
const f; // Featurevisor SDK instance
const context = {
userId: '123',
permissions: [
'read',
'write', // included
'delete',
],
}
const isFeatureEnabled = f.isEnabled(
'myFeature',
context
)

Operator: notIncludes New

Checks if a certain value is not included in the attribute's array (of strings) value.

segments/mySegment.yml
description: My segment description
conditions:
- attribute: permissions
operator: notIncludes
value: write
your-app/index.js
const f; // Featurevisor SDK instance
const context = {
userId: '123',
permissions: [
'read',
// 'write' is not included
'delete',
],
}
const isFeatureEnabled = f.isEnabled(
'myFeature',
context
)

Operator: matches New

Checks if the attribute's value matches a regular expression:

segments/mySegment.yml
description: My segment description
conditions:
- attribute: userAgent
operator: matches
value: '(Chrome|Firefox)\/([6-9]\d|\d{3,})'
# optional regex flags
regexFlags: i
your-app/index.js
const f; // Featurevisor SDK instance
const context = {
userId: '123',
userAgent: window.navigator.userAgent,
}
const isFeatureEnabled = f.isEnabled(
'myFeature',
context
)

Operator: notMatches New

Checks if the attribute's value does not match a regular expression:

segments/mySegment.yml
description: My segment description
conditions:
- attribute: userAgent
operator: notMatches
value: '(Chrome|Firefox)\/([6-9]\d|\d{3,})'
# optional regex flags
regexFlags: i
your-app/index.js
const f; // Featurevisor SDK instance
const context = {
userId: '123',
userAgent: window.navigator.userAgent,
}
const isFeatureEnabled = f.isEnabled(
'myFeature',
context
)

Learn more in Segments page.

Defining features

Defining variable schema Breaking

Before
features/myFeature.yml
# ...
variablesSchema:
- key: myVariableKey
type: string
defaultValue: 'default value'
After
features/myFeature.yml
# ...
variablesSchema:
myVariableKey:
type: string
defaultValue: 'default value'

Learn more in Variables section.

When feature is disabled, use default variable value New

When a feature itself is evaluated as disabled, its variable values by default always get evaluated as empty (undefined in v1, and null in v2).

Now, you can choose on a per variable basis whether to serve the default value if the feature is disabled or default to null.

Before
features/myFeature.yml
# ...
variablesSchema:
- key: myVariableKey
type: string
defaultValue: default value
After
features/myFeature.yml
# ...
variablesSchema:
myVariableKey:
type: string
defaultValue: default value
# optionally serve default value
# when feature is disabled
useDefaultWhenDisabled: true

Learn more in Variables section.

When feature is disabled, serve different variable value New

Instead of serving default value, if you want to a different value to be served for your variable whenthe feature itself is disabled, you can do this:

Before
features/myFeature.yml
# ...
variablesSchema:
- key: myVariableKey
type: string
defaultValue: default value
After
features/myFeature.yml
# ...
variablesSchema:
myVariableKey:
type: string
defaultValue: default value
# optionally serve different value
# when feature is disabled
disabledValue: different value for disabled feature

Learn more in Variables section.

When feature is disabled, serve a specific variation value New

If the feature itself is evaluated as disabled, then its variation value will be evaluated as null by default.

If you wish to serve a specific variation value in those cases, you can do this:

Before
features/myFeature.yml
# ...
variations:
- value: control
weight: 50
- value: treatment
weight: 50
After
features/myFeature.yml
# ...
variations:
- value: control
weight: 50
- value: treatment
weight: 50
disabledVariationValue: control

Learn more in Variations section.

Variable overrides from variations Breaking

Before
features/myFeature.yml
# ...
variations:
- value: control
weight: 50
- value: treatment
weight: 50
# had to be used together
variables:
- key: bgColor
value: blue
overrides:
- segments: netherlands
value: orange
After
features/myFeature.yml
# ...
variations:
- value: control
weight: 50
- value: treatment
weight: 50
# can be overridden independently
variables:
bgColor: blue
variableOverrides:
bgColor:
- segments: netherlands
value: orange

Learn more in Variables section.

Defining rules Breaking

Rules have moved to top level of the feature definition, and the environments property is no longer used.

This has resulted in less nesting and more clarity in defining rules.

Before
features/myFeature.yml
# ...
environments:
production:
rules:
- key: everyone
segments: '*'
percentage: 100
After
features/myFeature.yml
# ...
rules:
production:
- key: everyone
segments: '*'
percentage: 100

Learn more in Rules section.

Defining forced overrides Breaking

Similar to rules above, force entries have moved to top level of the feature definition as well.

Before
features/myFeature.yml
# ...
environments:
production:
force:
- segments: qa
enabled: true
After
features/myFeature.yml
# ...
force:
production:
- segments: qa
enabled: true

Learn more in Force section.

Exposing feature in datafile Breaking

The expose property had a very rare use case, that controlled the inclusion of a feature in generated datafiles targeting a specific environment and/or tag.

Before
features/myFeature.yml
# ...
environments:
production:
expose: false
After
features/myFeature.yml
# ...
expose:
production: false

Learn more in Expose section.

Variation weight overrides New

If you are running experiments, you can now override the weights of your variations on a per rule basis:

features/myFeature.yml
# ...
variations:
# common weights for all rules
- value: control
weight: 50
- value: treatment
weight: 50
rules:
production:
- key: netherlands
segments: netherlands
percentage: 100
# override the weights here for this rule alone
variationWeights:
control: 10
treatment: 90
- key: everyone
segments: '*'
percentage: 100

Learn more in Variations section.


Project configuration

outputDirectoryPath Breaking

Default output directory path has been changed from dist to datafiles.

This is to better reflect the contents of the directory.

Before
featurevisor.config.js
module.exports = {
// defaulted to this directory
outputDirectoryPath: 'dist',
}
After
featurevisor.config.js
module.exports = {
// defaults to this directory
datafilesDirectoryPath: 'datafiles',
}

datafileNamePattern New

Previously defaulted to datafile-%s.json, it has been changed to featurevisor-%s.json.

Before
featurevisor.config.js
module.exports = {
// no option available to customize it
}
After
featurevisor.config.js
module.exports = {
datafileNamePattern: 'featurevisor-%s.json',
}

Learn more in Configuration page.


CLI usage

Upgrade to latest CLI New

In your Featurevisor project repository:

Command
$ npm install --save @featurevisor/cli@2

Building v1 datafiles New

It is understandable you may have applications that still consume v1 datafiles using v1 compatible SDKs.

To keep supporting both v1 and v2 from the same project in a backwards compatible way, you can build new v2 datafiles as usual:

Command
$ npx featurevisor build

and on top of that, also build v1 datafiles:

Command
$ npx featurevisor build \
--schema-version=1 \
--no-state-files \
--datafiles-dir=datafiles/v1

Using hash as datafile revision New

By default, every time you build datafiles, a new revision is generated which is an incremental number.

Command
$ npx featurevisor build

You may often have changes like updating a feature's description, which do not require a new revision number. To avoid that, you can pass --revisionFromHash option to the CLI:

Command
$ npx featurevisor build --revisionFromHash

If individual datafile contents do not change since last build, the revision will not change either. This helps implement caching when serving datafiles from CDN with ease.

Datafile naming convention Breaking

Naming convention of built datafiles has been changed from datafile-tag-<tag>.json to featurevisor-tag-<tag>.json to help distinguish between Featurevisor datafiles and other datafiles that may be used in your project:

Before
$ tree dist
.
├── production
│ └── datafile-tag-all.json
└── staging
└── datafile-tag-all.json
2 directories, 2 files
After
$ tree datafiles
.
├── production
│ └── featurevisor-tag-all.json
└── staging
└── featurevisor-tag-all.json
2 directories, 2 files

If you wish to maintain the old naming convention, you can update your project configuration:

featurevisor.config.js
module.exports = {
// ...
datafilesDirectoryPath: 'dist',
datafileNamePattern: 'datafile-%s.json',
}

JavaScript SDK usage

Upgrade to latest SDK New

In your application repository:

Command
$ npm install --save @featurevisor/sdk@2

Fetching datafile Breaking

This option has been removed from the SDK. You are now required to take care of fetching the datafile yourself and passing it to the SDK:

Before
your-app/index.js
import { createInstance } from '@featurevisor/sdk'
const DATAFILE_URL = '...'
const f = createInstance({
datafileUrl: DATAFILE_URL,
onReady: () => {
console.log('SDK is ready')
},
})
After
your-app/index.js
import { createInstance } from '@featurevisor/sdk'
const DATAFILE_URL = '...'
const datafileContent = await fetch(DATAFILE_URL)
.then((res) => res.json())
const f = createInstance({
datafile: datafileContent,
})

onReady callback is no longer needed, as the SDK is ready immediately after you pass the datafile.

Refreshing datafile Breaking

This option has been removed from the SDK. You are now required to take care of fetching the datafile and then set to it existing SDK instance:

Before
your-app/index.js
import { createInstance } from '@featurevisor/sdk'
const DATAFILE_URL = '...'
const f = createInstance({
datafileUrl: DATAFILE_URL,
refreshInterval: 60, // every 60 seconds
onRefresh: () => {
console.log('Datafile refreshed')
},
onUpdate: () => {
console.log('New datafile revision detected')
},
})
// manually refresh
f.refresh()
// stop/start refreshing
f.stopRefreshing()
f.startRefreshing()
After
your-app/index.js
import { createInstance } from '@featurevisor/sdk'
const DATAFILE_URL = '...'
const datafileContent = await fetch(DATAFILE_URL)
.then((res) => res.json())
const f = createInstance({
datafile: datafileContent,
})
const unsubscribe = f.on("datafile_set", ({
revision, // new revision
previousRevision,
revisionChanged, // true if revision has changed
features, // list of all affected feature keys
}) => {
console.log('Datafile set')
});
// custom interval
setInterval(function () {
const datafileContent = await fetch(DATAFILE_URL)
.then((res) => res.json())
f.setDatafile(datafileContent)
}, 60 * 1000);

refreshInterval, onRefresh and onUpdate options and refresh method are no longer supported.

Getting variation Soft breaking

When evaluating the variation of a feature that is disabled, the SDK used to return undefined in v1.

This was challenging to handle in non-JavaScript SDKs, since there is no concept of undefined as a type there.

Therefore, it has been changed to return null in v2.

Before
your-app/index.js
const f; // Featurevisor SDK instance
const context = { userId: '123' }
// could be either `string` or `undefined`
const variation = f.getVariation(
'myFeature',
context
)
After
your-app/index.js
const f; // Featurevisor SDK instance
const context = { userId: '123' }
// now either `string` or `null`
const variation = f.getVariation(
'myFeature',
context
)

Getting variable Soft breaking

Similar to above for getting variation, when evaluating a variable of a feature that is disabled, the SDK will now return null instead of undefined.

Before
your-app/index.js
const f; // Featurevisor SDK instance
const context = { userId: '123' }
// could be either value or `undefined`
const variableValue = f.getVariable(
'myFeature',
'myVariableKey',
context
)
After
your-app/index.js
const f; // Featurevisor SDK instance
const context = { userId: '123' }
// now either value or `null`
const variation = f.getVariable(
'myFeature',
'myVariableKey',
context
)

This is applicable for type specific SDK methods as well for variables:

  • getVariableString
  • getVariableBoolean
  • getVariableInteger
  • getVariableDouble
  • getVariableArray
  • getVariableObject
  • getVariableJSON

Activation Breaking

Experiment activations are not handled by the SDK any more.

Before
your-app/index.js
import { createInstance } from '@featurevisor/sdk'
const f = createInstance({
// ...
onActivate: function ({
featureKey,
variationValue,
fullContext,
captureContext,
}) {
// send to your analytics service here
track('activation', {
experiment: featureKey,
variation: variationValue,
userId: fullContext.userId,
})
},
})
const context = { userId: '123' }
f.activate('featureKey', context)
After
your-app/index.js
import { createInstance } from '@featurevisor/sdk'
const f; // Featurevisor SDK instance
const context = { userId: '123' }
const variation = f.getVariation("mFeature", context);
// send to your analytics service here
track('activation', {
experiment: 'myFeature',
variation: variation.value,
userId: context.userId,
})

activate method and onActivate option are no longer supported.

You can also make use of new Hooks API.

Sticky features Breaking

Before
your-app/index.js
import { createInstance } from '@featurevisor/sdk'
const stickyFeatures = {
myFeatureKey: {
enabled: true,
variation: 'control',
variables: {
myVariableKey: 'myVariableValue',
},
},
}
// when creating instance
const f = createInstance({
stickyFeatures: stickyFeatures,
})
// replacing sticky features later
f.setStickyFeatures(stickyFeatures)
After
your-app/index.js
import { createInstance } from '@featurevisor/sdk'
const stickyFeatures = {
myFeatureKey: {
enabled: true,
variation: 'control',
variables: {
myVariableKey: 'myVariableValue',
},
},
}
// when creating instance
const f = createInstance({
sticky: stickyFeatures,
})
// replacing sticky features later
f.setSticky(stickyFeatures, true)

Unless true is passed as the second argument, the sticky features will be merged with the existing ones.

Initial features Breaking

Initial features used to be handy for setting some early values before the SDK fetched datafile and got ready.

But since datafile fetching responsibility is now on you, the initial features are no longer needed.

Before
your-app/index.js
import { createInstance } from '@featurevisor/sdk'
const initialFeatures = {
myFeatureKey: {
enabled: true,
variation: 'control',
variables: {
myVariableKey: 'myVariableValue',
},
},
}
// when creating instance
const f = createInstance({
initialFeatures: initialFeatures,
})
After
your-app/index.js
import { createInstance } from '@featurevisor/sdk'
const initialFeatures = {
myFeatureKey: {
enabled: true,
variation: 'control',
variables: {
myVariableKey: 'myVariableValue',
},
},
}
// you can pass them as sticky instead
const f = createInstance({
sticky: initialFeatures,
})
// fetch and set datafile after
f.setDatafile(datafileContent)
// remove sticky features after
f.setSticky(
{},
// replacing with empty object
true
)

Setting context New

Before
your-app/index.js
import { createInstance } from '@featurevisor/sdk'
const f = createInstance({
// ...
})
const isFeatureEnabled = f.isEnabled(
'myFeature',
// pass context directly only
{ userId: '123' },
)
After
your-app/index.js
import { createInstance } from '@featurevisor/sdk'
const f = createInstance({
// ...
// optional initial context
context: { browser: 'chrome' },
})
// set more context later (append)
f.setContext({
userId: '123',
})
// replace currently set context entirely
f.setContext(
{
userId: '123',
browser: 'firefox',
},
true, // replace
)
// already set context will be used automatically
const isFeatureEnabled = f.isEnabled('myFeature')
// you can still pass context directly
// for overriding specific attributes
const isFeatureEnabled = f.isEnabled(
'myFeature',
// still allows passing context directly
{ browser: 'edge' },
)

Logging Breaking

Instead of passing all log levels individually, you can now pass a single level to the SDK.

The set level will cover all the levels below it, so you can pass debug to cover all the levels together.

Creating logger instance Breaking

Before
your-app/index.js
import {
createInstance,
createLogger
} from '@featurevisor/sdk'
const f = createInstance({
logger: createLogger({
levels: [
'debug',
'info',
'warn',
'error',
],
})
})
After
your-app/index.js
import {
createInstance,
createLogger
} from '@featurevisor/sdk'
const f = createInstance({
logger: createLogger({
level: 'debug',
})
})

Setting debug will now cover all the levels together, instead of having to pass them all individually.

Passing log level when creating SDK instance New

Alternatively, you can also pass the log level directly when creating the SDK instance:

your-app/index.js
import { createInstance } from '@featurevisor/sdk'
const f = createInstance({
logLevel: 'debug',
})

Setting log level after creating SDK instance Breaking

You can also change the log level after creating the SDK instance:

Before
your-app/index.js
f.setLogLevels([
'error',
'warn',
'info',
'debug',
])
After
your-app/index.js
f.setLogLevel('debug')

Read more in Logging section.

Hooks New

Hooks are a set of new APIs allowing you to intercept the evaluation process and customize it.

A hook can be defined as follows:

Defining a hook
your-app/index.ts
import { Hook } from "@featurevisor/sdk"
const myCustomHook: Hook = {
// only required property
name: 'my-custom-hook',
// rest of the properties below are all optional per hook
// before evaluation
before: function (options) {
const {
type, // `feature` | `variation` | `variable`
featureKey,
variableKey, // if type is `variable`
context
} options;
// update context before evaluation
options.context = {
...options.context,
someAdditionalAttribute: 'value',
}
return options
},
// after evaluation
after: function (evaluation, options) {
const {
reason // `error` | `feature_not_found` | `variable_not_found` | ...
} = evaluation
if (reason === "error") {
// log error
return
}
},
// configure bucket key
bucketKey: function (options) {
const {
featureKey,
context,
bucketBy,
bucketKey, // default bucket key
} = options;
// return custom bucket key
return bucketKey
},
// configure bucket value (between 0 and 100,000)
bucketValue: function (options) {
const {
featureKey,
context,
bucketKey,
bucketValue, // default bucket value
} = options;
// return custom bucket value
return bucketValue
},
}

You can register the hook when creating SDK instance:

When creating instance
your-app/index.js
import { createInstance } from '@featurevisor/sdk'
const f = createInstance({
// ...
hooks: [myCustomHook],
})

You can also register the hook after creating the SDK instance:

After creating instance
your-app/index.js
const f; // Featurevisor SDK instance
const removeHook = f.addHook(myCustomHook)
// remove the hook later
removeHook()

Intercepting context Breaking

Before
your-app/index.js
import { createInstance } from '@featurevisor/sdk'
const f = createInstance({
// ...
interceptContext: function (context) {
// modify context before evaluation
return {
...context,
someAdditionalAttribute: 'value',
}
},
})
After
your-app/index.js
import { createInstance } from '@featurevisor/sdk'
const f = createInstance({
// ...
hooks: [
{
name: 'intercept-context',
before: function (options) {
// modify context before evaluation
options.context = {
...options.context,
someAdditionalAttribute: 'value',
}
return options
},
},
],
})

Events Breaking

All the known events from v1 SDK have been removed in v2 SDK:

  • Readiness: see fetching datafile
    • onReady option and method
    • ready event
  • Refreshing: see refreshing datafile
    • refresh event and method
    • startRefreshing method
    • stopRefreshing method
    • onRefresh option
    • update event
    • onUpdate option
  • Activation: see activation
    • activate event and method
    • onActivate option

A new set of events has been introduced which are more generic.

Because of these changes, reactivity is vastly improved allowing you to listen to the changes of specific features and react to them in a highly efficient way without having to reload or restart your application.

datafile_set New

Will trigger when a datafile is set to the SDK instance:

your-app/index.js
const f; // Featurevisor SDK instance
const unsubscribe = f.on("datafile_set", ({
revision, // new revision
previousRevision,
revisionChanged, // true if revision has changed
features, // list of all affected feature keys
}) => {
console.log('Datafile set')
})
unsubscribe();

context_set New

Will trigger when context is set to the SDK instance:

your-app/index.js
const f; // Featurevisor SDK instance
const unsubscribe = f.on("context_set", ({
replaced, // true if context was replaced
context, // the new context
}) => {
console.log('Context set')
})
unsubscribe();

sticky_set New

Will trigger when sticky features are set to the SDK instance:

your-app/index.js
const f; // Featurevisor SDK instance
const unsubscribe = f.on("sticky_set", ({
replaced, // true if sticky features got replaced
features, // list of all affected feature keys
}) => {
console.log('Sticky features set')
})
unsubscribe();

Child instance New

It's one thing to deal with the same SDK instance when you are building a client-side application (think web or mobile app) where only one user is accessing the application.

But when you are building a server-side application (think a REST API) serving many different users simultaneously, you may want to have different SDK instances with user or request specific context.

Child instances make it very easy to achieve that now:

Primary instance
import { createInstance } from '@featurevisor/sdK'
const f = createInstance({
datafile: datafileContent,
})
// set common context for all
f.setContext({
apiVersion: '5.0.0',
})

Afterwards, you can spawn child instances from it:

Child instance
// creating a child instance with its own context
// (will get merged with parent context if available before evaluations)
const childF = f.spawn({
userId: '234',
country: 'nl',
})
// evaluate via spawned child instance
const isFeatureEnabled = childF.isEnabled('myFeature')

Similar to primary instance, you can also set context and sticky features in child instances:

Child instance: setting context
// override child context later if needed
childF.setContext({
country: 'de',
})
// when evaluating, you can still pass additional context
const isFeatureEnabled = childF.isEnabled('myFeature', {
browser: 'firefox',
})

Methods similar to primary instance are all available on child instances:

  • isEnabled
  • getVariation
  • getVariable
  • getVariableBoolean
  • getVariableString
  • getVariableInteger
  • getVariableDouble
  • getVariableArray
  • getVariableObject
  • getVariableJSON
  • getAllEvaluations
  • setContext
  • setSticky
  • on

Get all evaluations New

You can get evaluation results of all your features currently loaded via datafile in the SDK instance:

your-app/index.js
const f; // Featurevisor SDK instance
const allEvaluations = f.getAllEvaluations(context = {})
console.log(allEvaluations)
// {
// myFeature: {
// enabled: true,
// variation: "control",
// variables: {
// myVariableKey: "myVariableValue",
// },
// },
//
// anotherFeature: {
// enabled: true,
// variation: "treatment",
// }
// }

This can be very useful when you want to serialize all evaluations, and hand it off from backend to frontend for example.

Configuring bucket key Breaking

Before
your-app/index.js
import { createInstance } from '@featurevisor/sdk'
const f = createInstance({
configureBucketKey: function (options) {
const {
featureKey,
context,
// default bucket key
bucketKey,
} = options
return bucketKey
},
})
After
your-app/index.js
import { createInstance } from '@featurevisor/sdk'
const f = createInstance({
hooks: [
{
name: 'my-custom-hook',
bucketKey: function (options) {
const {
featureKey,
context,
bucketBy,
// default bucket key
bucketKey,
} = options
return bucketKey
},
},
],
})

Configuring bucket value Breaking

Before
your-app/index.js
import { createInstance } from '@featurevisor/sdk'
const f = createInstance({
configureBucketValue: function (options) {
const {
featureKey,
context,
// default bucket value
bucketValue,
} = options
return bucketValue
},
})
After
your-app/index.js
import { createInstance } from '@featurevisor/sdk'
const f = createInstance({
hooks: [
{
name: 'my-custom-hook',
bucketValue: function (options) {
const {
featureKey,
context,
bucketKey
// default bucket value
bucketValue,
} = options
return bucketValue
},
},
],
})

Learn more in JavaScript SDK page.

React SDK usage

All the hooks are now reactive. Meaning, your components will automatically re-render when:

  • a newew datafile is set
  • context is set or updated
  • sticky features are set or updated

Learn more in React SDK page.


Testing features

sticky New

Test specs of features can now also include sticky features, similar to SDK's API:

tests/features/myFeature.spec.yml
feature: myFeature
assertions:
- description: My feature is enabled
environment: production
at: 100
context:
country: nl
sticky:
myFeatureKey:
enabled: true
variation: control
variables:
myVariableKey: myVariableValue
expectedToBeEnabled: true

expectedEvaluations New

You can go deep with testing feature evaluations, including their evaluation reasons for example:

tests/features/myFeature.spec.yml
feature: myFeature
assertions:
- description: My feature is enabled
environment: production
at: 100
context:
country: nl
expectedToBeEnabled: true
expectedEvaluations:
flag:
enabled: true
reason: rule # see available rules in Evaluation type from SDK
variation:
variationValue: control
reason: rule
variables:
myVariableKey:
value: myVariableValue
reason: rule

children New

Based on the new child instance API in SDK, you can also imitate testing against them via test specs:

tests/features/myFeature.spec.yml
feature: myFeature
assertions:
- description: My feature is enabled
environment: production
at: 100
context:
apiVersion: 5.0.0
children:
- context:
userId: '123'
country: nl
expectedToBeEnabled: true
- context:
userId: '456'
country: de
expectedToBeEnabled: false

Learn more in Testing page.

Previous
Deprecating features